1{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE TemplateHaskell #-}
4{-# LANGUAGE QuasiQuotes #-}
5module Data.Swagger.SchemaSpec where
10import Control.Lens ((^.))
11import Data.Aeson (Value)
14import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
17import qualified Data.Text as Text
21import Data.Swagger.Declare
26checkToSchema :: ToSchema a => Proxy a -> Value -> Spec
27checkToSchema proxy js = toSchema proxy <=> js
29checkSchemaName :: ToSchema a => Maybe String -> Proxy a -> Spec
30checkSchemaName sname proxy =
31 it ("schema name is " ++ show sname) $
32 schemaName proxy `shouldBe` fmap Text.pack sname
34checkDefs :: ToSchema a => Proxy a -> [String] -> Spec
35checkDefs proxy names =
36 it ("uses these definitions " ++ show names) $
37 InsOrdHashMap.keys defs `shouldBe` map Text.pack names
39 defs = execDeclare (declareNamedSchema proxy) mempty
41checkProperties :: ToSchema a => Proxy a -> [String] -> Spec
42checkProperties proxy names =
43 it ("has these fields in order " ++ show names) $
44 InsOrdHashMap.keys fields `shouldBe` map Text.pack names
46 fields = toSchema proxy ^. properties
48checkInlinedSchema :: ToSchema a => Proxy a -> Value -> Spec
49checkInlinedSchema proxy js = toInlinedSchema proxy <=> js
51checkInlinedSchemas :: ToSchema a => [String] -> Proxy a -> Value -> Spec
52checkInlinedSchemas names proxy js = inlineSchemas (map Text.pack names) defs s <=> js
54 (defs, s) = runDeclare (declareSchema proxy) mempty
56checkInlinedRecSchema :: ToSchema a => Proxy a -> Value -> Spec
57checkInlinedRecSchema proxy js = inlineNonRecursiveSchemas defs s <=> js
59 (defs, s) = runDeclare (declareSchema proxy) mempty
63 describe "Generic ToSchema" $ do
64 context "Unit" $ checkToSchema (Proxy :: Proxy Unit) unitSchemaJSON
65 context "Person" $ checkToSchema (Proxy :: Proxy Person) personSchemaJSON
66 context "ISPair" $ checkToSchema (Proxy :: Proxy ISPair) ispairSchemaJSON
67 context "Point (fieldLabelModifier)" $ checkToSchema (Proxy :: Proxy Point) pointSchemaJSON
68 context "Point5 (many field record)" $ do
69 checkToSchema (Proxy :: Proxy Point5) point5SchemaJSON
70 checkProperties (Proxy :: Proxy Point5) point5Properties
71 context "Color (bounded enum)" $ checkToSchema (Proxy :: Proxy Color) colorSchemaJSON
72 context "Shade (paramSchemaToNamedSchema)" $ checkToSchema (Proxy :: Proxy Shade) shadeSchemaJSON
73 context "Paint (record with bounded enum field)" $ checkToSchema (Proxy :: Proxy Paint) paintSchemaJSON
74 context "UserGroup (set newtype)" $ checkToSchema (Proxy :: Proxy UserGroup) userGroupSchemaJSON
75 context "Unary records" $ do
76 context "Email (unwrapUnaryRecords)" $ checkToSchema (Proxy :: Proxy Email) emailSchemaJSON
77 context "UserId (non-record newtype)" $ checkToSchema (Proxy :: Proxy UserId) userIdSchemaJSON
78 context "Player (unary record)" $ checkToSchema (Proxy :: Proxy Player) playerSchemaJSON
79 context "Players (inlining schema)" $ checkToSchema (Proxy :: Proxy Players) playersSchemaJSON
80 context "MyRoseTree (datatypeNameModifier)" $ checkToSchema (Proxy :: Proxy MyRoseTree) myRoseTreeSchemaJSON
81 context "Sum types" $ do
82 context "Status (sum of unary constructors)" $ checkToSchema (Proxy :: Proxy Status) statusSchemaJSON
83 context "Character (ref and record sum)" $ checkToSchema (Proxy :: Proxy Character) characterSchemaJSON
84 context "Light (sum with unwrapUnaryRecords)" $ checkToSchema (Proxy :: Proxy Light) lightSchemaJSON
85 context "Schema name" $ do
86 context "String" $ checkSchemaName Nothing (Proxy :: Proxy String)
87 context "(Int, Float)" $ checkSchemaName Nothing (Proxy :: Proxy (Int, Float))
88 context "Person" $ checkSchemaName (Just "Person") (Proxy :: Proxy Person)
89 context "Shade" $ checkSchemaName (Just "Shade") (Proxy :: Proxy Shade)
90 describe "Generic Definitions" $ do
91 context "Unit" $ checkDefs (Proxy :: Proxy Unit) []
92 context "Paint" $ checkDefs (Proxy :: Proxy Paint) ["Color"]
93 context "Light" $ checkDefs (Proxy :: Proxy Light) ["Color"]
94 context "Character" $ checkDefs (Proxy :: Proxy Character) ["Player", "Point"]
95 context "MyRoseTree" $ checkDefs (Proxy :: Proxy MyRoseTree) ["RoseTree"]
96 context "[Set (Unit, Maybe Color)]" $ checkDefs (Proxy :: Proxy [Set (Unit, Maybe Color)]) ["Unit", "Color"]
97 context "ResourceId" $ checkDefs (Proxy :: Proxy ResourceId) []
98 describe "Inlining Schemas" $ do
99 context "Paint" $ checkInlinedSchema (Proxy :: Proxy Paint) paintInlinedSchemaJSON
100 context "Character" $ checkInlinedSchema (Proxy :: Proxy Character) characterInlinedSchemaJSON
101 context "Character (inlining only Player)" $ checkInlinedSchemas ["Player"] (Proxy :: Proxy Character) characterInlinedPlayerSchemaJSON
102 context "Light" $ checkInlinedSchema (Proxy :: Proxy Light) lightInlinedSchemaJSON
103 context "MyRoseTree (inlineNonRecursiveSchemas)" $ checkInlinedRecSchema (Proxy :: Proxy MyRoseTree) myRoseTreeSchemaJSON
108-- ========================================================================
109-- Person (simple record with optional fields)
110-- ========================================================================
114 , email :: Maybe String
117instance ToSchema Person
119personSchemaJSON :: Value
120personSchemaJSON = [aesonQQ|
125 "name": { "type": "string" },
126 "phone": { "type": "integer" },
127 "email": { "type": "string" }
129 "required": ["name", "phone"]
133-- ========================================================================
134-- ISPair (non-record product data type)
135-- ========================================================================
136data ISPair = ISPair Integer String
139instance ToSchema ISPair
141ispairSchemaJSON :: Value
142ispairSchemaJSON = [aesonQQ|
147 { "type": "integer" },
155-- ========================================================================
156-- Point (record data type with custom fieldLabelModifier)
157-- ========================================================================
164instance ToSchema Point where
165 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
166 { fieldLabelModifier = map toLower . drop (length "point") }
168pointSchemaJSON :: Value
169pointSchemaJSON = [aesonQQ|
174 "x": { "type": "number", "format": "double" },
175 "y": { "type": "number", "format": "double" }
177 "required": ["x", "y"]
181-- ========================================================================
182-- Point (record data type with multiple fields)
183-- ========================================================================
190 , point5V :: Double -- 5 dimensional!
193instance ToSchema Point5 where
194 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
195 { fieldLabelModifier = map toLower . drop (length "point5") }
197point5SchemaJSON :: Value
198point5SchemaJSON = [aesonQQ|
203 "x": { "type": "number", "format": "double" },
204 "y": { "type": "number", "format": "double" },
205 "z": { "type": "number", "format": "double" },
206 "u": { "type": "number", "format": "double" },
207 "v": { "type": "number", "format": "double" }
209 "required": ["x", "y", "z", "u", "v"]
213point5Properties :: [String]
214point5Properties = ["x", "y", "z", "u", "v"]
216-- ========================================================================
218-- ========================================================================
224instance ToSchema Color
226colorSchemaJSON :: Value
227colorSchemaJSON = [aesonQQ|
230 "enum": ["Red", "Green", "Blue"]
234-- ========================================================================
235-- Shade (paramSchemaToNamedSchema)
236-- ========================================================================
238data Shade = Dim | Bright deriving (Generic)
239instance ToParamSchema Shade
241instance ToSchema Shade where declareNamedSchema = pure . paramSchemaToNamedSchema defaultSchemaOptions
243shadeSchemaJSON :: Value
244shadeSchemaJSON = [aesonQQ|
247 "enum": ["Dim", "Bright"]
251-- ========================================================================
252-- Paint (record with bounded enum property)
253-- ========================================================================
255newtype Paint = Paint { color :: Color }
257instance ToSchema Paint
259paintSchemaJSON :: Value
260paintSchemaJSON = [aesonQQ|
267 "$ref": "#/definitions/Color"
270 "required": ["color"]
274paintInlinedSchemaJSON :: Value
275paintInlinedSchemaJSON = [aesonQQ|
283 "enum": ["Red", "Green", "Blue"]
286 "required": ["color"]
290-- ========================================================================
291-- Email (newtype with unwrapUnaryRecords set to True)
292-- ========================================================================
294newtype Email = Email { getEmail :: String }
297instance ToSchema Email where
298 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
299 { unwrapUnaryRecords = True }
301emailSchemaJSON :: Value
302emailSchemaJSON = [aesonQQ|
308-- ========================================================================
309-- UserId (non-record newtype)
310-- ========================================================================
312newtype UserId = UserId Integer
313 deriving (Eq, Ord, Generic)
314instance ToSchema UserId
316userIdSchemaJSON :: Value
317userIdSchemaJSON = [aesonQQ|
323-- ========================================================================
324-- UserGroup (set newtype)
325-- ========================================================================
327newtype UserGroup = UserGroup (Set UserId)
329instance ToSchema UserGroup
331userGroupSchemaJSON :: Value
332userGroupSchemaJSON = [aesonQQ|
335 "items": { "$ref": "#/definitions/UserId" },
340-- ========================================================================
341-- Player (record newtype)
342-- ========================================================================
344newtype Player = Player
347instance ToSchema Player
349playerSchemaJSON :: Value
350playerSchemaJSON = [aesonQQ|
357 "$ref": "#/definitions/Point"
360 "required": ["position"]
364-- ========================================================================
365-- MyRoseTree (custom datatypeNameModifier)
366-- ========================================================================
368data MyRoseTree = MyRoseTree
370 , trees :: [MyRoseTree]
373instance ToSchema MyRoseTree where
374 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
375 { datatypeNameModifier = drop (length "My") }
377myRoseTreeSchemaJSON :: Value
378myRoseTreeSchemaJSON = [aesonQQ|
383 "root": { "type": "string" },
389 "$ref": "#/definitions/RoseTree"
393 "required": ["root", "trees"]
397-- ========================================================================
398-- Inlined (newtype for inlining schemas)
399-- ========================================================================
401newtype Inlined a = Inlined { getInlined :: a }
403instance ToSchema a => ToSchema (Inlined a) where
404 declareNamedSchema _ = unname <$> declareNamedSchema (Proxy :: Proxy a)
406 unname (NamedSchema _ s) = NamedSchema Nothing s
408newtype Players = Players [Inlined Player]
410instance ToSchema Players
412playersSchemaJSON :: Value
413playersSchemaJSON = [aesonQQ|
423 "$ref": "#/definitions/Point"
426 "required": ["position"]
431-- ========================================================================
432-- Status (sum type with unary constructors)
433-- ========================================================================
440instance ToSchema Status where
441 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
442 { constructorTagModifier = map toLower . drop (length "Status") }
444statusSchemaJSON :: Value
445statusSchemaJSON = [aesonQQ|
450 "ok": { "type": "string" },
451 "error": { "type": "string" }
458-- ========================================================================
460-- ========================================================================
462data Unit = Unit deriving (Generic)
463instance ToSchema Unit
465unitSchemaJSON :: Value
466unitSchemaJSON = [aesonQQ|
474-- ========================================================================
475-- Character (sum type with ref and record in alternative)
476-- ========================================================================
480 | NPC { npcName :: String, npcPosition :: Point }
482instance ToSchema Character
484characterSchemaJSON :: Value
485characterSchemaJSON = [aesonQQ|
490 "PC": { "$ref": "#/definitions/Player" },
496 "npcName": { "type": "string" },
497 "npcPosition": { "$ref": "#/definitions/Point" }
499 "required": ["npcName", "npcPosition"]
507characterInlinedSchemaJSON :: Value
508characterInlinedSchemaJSON = [aesonQQ|
523 "x": { "type": "number", "format": "double" },
524 "y": { "type": "number", "format": "double" }
526 "required": ["x", "y"]
529 "required": ["position"]
536 "npcName": { "type": "string" },
542 "x": { "type": "number", "format": "double" },
543 "y": { "type": "number", "format": "double" }
545 "required": ["x", "y"]
548 "required": ["npcName", "npcPosition"]
556characterInlinedPlayerSchemaJSON :: Value
557characterInlinedPlayerSchemaJSON = [aesonQQ|
569 "$ref": "#/definitions/Point"
572 "required": ["position"]
579 "npcName": { "type": "string" },
580 "npcPosition": { "$ref": "#/definitions/Point" }
582 "required": ["npcName", "npcPosition"]
590-- ========================================================================
591-- Light (sum type with unwrapUnaryRecords)
592-- ========================================================================
598 | LightWaveLength { waveLength :: Double }
601instance ToSchema Light where
602 declareNamedSchema = genericDeclareNamedSchemaUnrestricted defaultSchemaOptions
603 { unwrapUnaryRecords = True }
605lightSchemaJSON :: Value
606lightSchemaJSON = [aesonQQ|
611 "NoLight": { "type": "array", "items": [] },
612 "LightFreq": { "type": "number", "format": "double" },
613 "LightColor": { "$ref": "#/definitions/Color" },
614 "LightWaveLength": { "type": "number", "format": "double" }
621lightInlinedSchemaJSON :: Value
622lightInlinedSchemaJSON = [aesonQQ|
627 "NoLight": { "type": "array", "items": [] },
628 "LightFreq": { "type": "number", "format": "double" },
632 "enum": ["Red", "Green", "Blue"]
634 "LightWaveLength": { "type": "number", "format": "double" }
641-- ========================================================================
642-- ResourceId (series of newtypes)
643-- ========================================================================
645newtype Id = Id String deriving (Generic)
648newtype ResourceId = ResourceId Id deriving (Generic)
649instance ToSchema ResourceId
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list