1{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE TemplateHaskell #-}
4{-# LANGUAGE QuasiQuotes #-}
5module Data.Swagger.SchemaSpec where
13import qualified Data.HashMap.Strict as HashMap
16import qualified Data.Set as Set
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 Set.fromList (HashMap.keys defs) `shouldBe` Set.fromList (map Text.pack names)
39 defs = execDeclare (declareNamedSchema proxy) mempty
41checkInlinedSchema :: ToSchema a => Proxy a -> Value -> Spec
42checkInlinedSchema proxy js = toInlinedSchema proxy <=> js
44checkInlinedSchemas :: ToSchema a => [String] -> Proxy a -> Value -> Spec
45checkInlinedSchemas names proxy js = inlineSchemas (map Text.pack names) defs s <=> js
47 (defs, s) = runDeclare (declareSchema proxy) mempty
49checkInlinedRecSchema :: ToSchema a => Proxy a -> Value -> Spec
50checkInlinedRecSchema proxy js = inlineNonRecursiveSchemas defs s <=> js
52 (defs, s) = runDeclare (declareSchema proxy) mempty
56 describe "Generic ToSchema" $ do
57 context "Unit" $ checkToSchema (Proxy :: Proxy Unit) unitSchemaJSON
58 context "Person" $ checkToSchema (Proxy :: Proxy Person) personSchemaJSON
59 context "ISPair" $ checkToSchema (Proxy :: Proxy ISPair) ispairSchemaJSON
60 context "Point (fieldLabelModifier)" $ checkToSchema (Proxy :: Proxy Point) pointSchemaJSON
61 context "Color (bounded enum)" $ checkToSchema (Proxy :: Proxy Color) colorSchemaJSON
62 context "Shade (paramSchemaToNamedSchema)" $ checkToSchema (Proxy :: Proxy Shade) shadeSchemaJSON
63 context "Paint (record with bounded enum field)" $ checkToSchema (Proxy :: Proxy Paint) paintSchemaJSON
64 context "UserGroup (set newtype)" $ checkToSchema (Proxy :: Proxy UserGroup) userGroupSchemaJSON
65 context "Unary records" $ do
66 context "Email (unwrapUnaryRecords)" $ checkToSchema (Proxy :: Proxy Email) emailSchemaJSON
67 context "UserId (non-record newtype)" $ checkToSchema (Proxy :: Proxy UserId) userIdSchemaJSON
68 context "Player (unary record)" $ checkToSchema (Proxy :: Proxy Player) playerSchemaJSON
69 context "Players (inlining schema)" $ checkToSchema (Proxy :: Proxy Players) playersSchemaJSON
70 context "MyRoseTree (datatypeNameModifier)" $ checkToSchema (Proxy :: Proxy MyRoseTree) myRoseTreeSchemaJSON
71 context "Sum types" $ do
72 context "Status (sum of unary constructors)" $ checkToSchema (Proxy :: Proxy Status) statusSchemaJSON
73 context "Character (ref and record sum)" $ checkToSchema (Proxy :: Proxy Character) characterSchemaJSON
74 context "Light (sum with unwrapUnaryRecords)" $ checkToSchema (Proxy :: Proxy Light) lightSchemaJSON
75 context "Schema name" $ do
76 context "String" $ checkSchemaName Nothing (Proxy :: Proxy String)
77 context "(Int, Float)" $ checkSchemaName Nothing (Proxy :: Proxy (Int, Float))
78 context "Person" $ checkSchemaName (Just "Person") (Proxy :: Proxy Person)
79 context "Shade" $ checkSchemaName (Just "Shade") (Proxy :: Proxy Shade)
80 describe "Generic Definitions" $ do
81 context "Unit" $ checkDefs (Proxy :: Proxy Unit) []
82 context "Paint" $ checkDefs (Proxy :: Proxy Paint) ["Color"]
83 context "Light" $ checkDefs (Proxy :: Proxy Light) ["Color"]
84 context "Character" $ checkDefs (Proxy :: Proxy Character) ["Player", "Point"]
85 context "MyRoseTree" $ checkDefs (Proxy :: Proxy MyRoseTree) ["RoseTree"]
86 context "[Set (Unit, Maybe Color)]" $ checkDefs (Proxy :: Proxy [Set (Unit, Maybe Color)]) ["Unit", "Color"]
87 context "ResourceId" $ checkDefs (Proxy :: Proxy ResourceId) []
88 describe "Inlining Schemas" $ do
89 context "Paint" $ checkInlinedSchema (Proxy :: Proxy Paint) paintInlinedSchemaJSON
90 context "Character" $ checkInlinedSchema (Proxy :: Proxy Character) characterInlinedSchemaJSON
91 context "Character (inlining only Player)" $ checkInlinedSchemas ["Player"] (Proxy :: Proxy Character) characterInlinedPlayerSchemaJSON
92 context "Light" $ checkInlinedSchema (Proxy :: Proxy Light) lightInlinedSchemaJSON
93 context "MyRoseTree (inlineNonRecursiveSchemas)" $ checkInlinedRecSchema (Proxy :: Proxy MyRoseTree) myRoseTreeSchemaJSON
98-- ========================================================================
99-- Person (simple record with optional fields)
100-- ========================================================================
104 , email :: Maybe String
107instance ToSchema Person
109personSchemaJSON :: Value
110personSchemaJSON = [aesonQQ|
115 "name": { "type": "string" },
116 "phone": { "type": "integer" },
117 "email": { "type": "string" }
119 "required": ["name", "phone"]
123-- ========================================================================
124-- ISPair (non-record product data type)
125-- ========================================================================
126data ISPair = ISPair Integer String
129instance ToSchema ISPair
131ispairSchemaJSON :: Value
132ispairSchemaJSON = [aesonQQ|
137 { "type": "integer" },
143-- ========================================================================
144-- Point (record data type with custom fieldLabelModifier)
145-- ========================================================================
151instance ToSchema Point where
152 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
153 { fieldLabelModifier = map toLower . drop (length "point") }
155pointSchemaJSON :: Value
156pointSchemaJSON = [aesonQQ|
161 "x": { "type": "number", "format": "double" },
162 "y": { "type": "number", "format": "double" }
164 "required": ["x", "y"]
169-- ========================================================================
171-- ========================================================================
177instance ToSchema Color
179colorSchemaJSON :: Value
180colorSchemaJSON = [aesonQQ|
183 "enum": ["Red", "Green", "Blue"]
187-- ========================================================================
188-- Shade (paramSchemaToNamedSchema)
189-- ========================================================================
191data Shade = Dim | Bright deriving (Generic)
192instance ToParamSchema Shade
194instance ToSchema Shade where declareNamedSchema = pure . paramSchemaToNamedSchema defaultSchemaOptions
196shadeSchemaJSON :: Value
197shadeSchemaJSON = [aesonQQ|
200 "enum": ["Dim", "Bright"]
204-- ========================================================================
205-- Paint (record with bounded enum property)
206-- ========================================================================
208newtype Paint = Paint { color :: Color }
210instance ToSchema Paint
212paintSchemaJSON :: Value
213paintSchemaJSON = [aesonQQ|
220 "$ref": "#/definitions/Color"
223 "required": ["color"]
227paintInlinedSchemaJSON :: Value
228paintInlinedSchemaJSON = [aesonQQ|
236 "enum": ["Red", "Green", "Blue"]
239 "required": ["color"]
243-- ========================================================================
244-- Email (newtype with unwrapUnaryRecords set to True)
245-- ========================================================================
247newtype Email = Email { getEmail :: String }
250instance ToSchema Email where
251 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
252 { unwrapUnaryRecords = True }
254emailSchemaJSON :: Value
255emailSchemaJSON = [aesonQQ|
261-- ========================================================================
262-- UserId (non-record newtype)
263-- ========================================================================
265newtype UserId = UserId Integer
266 deriving (Eq, Ord, Generic)
267instance ToSchema UserId
269userIdSchemaJSON :: Value
270userIdSchemaJSON = [aesonQQ|
276-- ========================================================================
277-- UserGroup (set newtype)
278-- ========================================================================
280newtype UserGroup = UserGroup (Set UserId)
282instance ToSchema UserGroup
284userGroupSchemaJSON :: Value
285userGroupSchemaJSON = [aesonQQ|
288 "items": { "$ref": "#/definitions/UserId" },
293-- ========================================================================
294-- Player (record newtype)
295-- ========================================================================
297newtype Player = Player
300instance ToSchema Player
302playerSchemaJSON :: Value
303playerSchemaJSON = [aesonQQ|
310 "$ref": "#/definitions/Point"
313 "required": ["position"]
317-- ========================================================================
318-- MyRoseTree (custom datatypeNameModifier)
319-- ========================================================================
321data MyRoseTree = MyRoseTree
323 , trees :: [MyRoseTree]
326instance ToSchema MyRoseTree where
327 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
328 { datatypeNameModifier = drop (length "My") }
330myRoseTreeSchemaJSON :: Value
331myRoseTreeSchemaJSON = [aesonQQ|
336 "root": { "type": "string" },
342 "$ref": "#/definitions/RoseTree"
346 "required": ["root", "trees"]
350-- ========================================================================
351-- Inlined (newtype for inlining schemas)
352-- ========================================================================
354newtype Inlined a = Inlined { getInlined :: a }
356instance ToSchema a => ToSchema (Inlined a) where
357 declareNamedSchema _ = unname <$> declareNamedSchema (Proxy :: Proxy a)
359 unname (NamedSchema _ s) = NamedSchema Nothing s
361newtype Players = Players [Inlined Player]
363instance ToSchema Players
365playersSchemaJSON :: Value
366playersSchemaJSON = [aesonQQ|
376 "$ref": "#/definitions/Point"
379 "required": ["position"]
384-- ========================================================================
385-- Status (sum type with unary constructors)
386-- ========================================================================
393instance ToSchema Status where
394 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
395 { constructorTagModifier = map toLower . drop (length "Status") }
397statusSchemaJSON :: Value
398statusSchemaJSON = [aesonQQ|
403 "ok": { "type": "string" },
404 "error": { "type": "string" }
411-- ========================================================================
413-- ========================================================================
415data Unit = Unit deriving (Generic)
416instance ToSchema Unit
418unitSchemaJSON :: Value
419unitSchemaJSON = [aesonQQ|
427-- ========================================================================
428-- Character (sum type with ref and record in alternative)
429-- ========================================================================
433 | NPC { npcName :: String, npcPosition :: Point }
435instance ToSchema Character
437characterSchemaJSON :: Value
438characterSchemaJSON = [aesonQQ|
443 "PC": { "$ref": "#/definitions/Player" },
449 "npcName": { "type": "string" },
450 "npcPosition": { "$ref": "#/definitions/Point" }
452 "required": ["npcName", "npcPosition"]
460characterInlinedSchemaJSON :: Value
461characterInlinedSchemaJSON = [aesonQQ|
476 "x": { "type": "number", "format": "double" },
477 "y": { "type": "number", "format": "double" }
479 "required": ["x", "y"]
482 "required": ["position"]
489 "npcName": { "type": "string" },
495 "x": { "type": "number", "format": "double" },
496 "y": { "type": "number", "format": "double" }
498 "required": ["x", "y"]
501 "required": ["npcName", "npcPosition"]
509characterInlinedPlayerSchemaJSON :: Value
510characterInlinedPlayerSchemaJSON = [aesonQQ|
522 "$ref": "#/definitions/Point"
525 "required": ["position"]
532 "npcName": { "type": "string" },
533 "npcPosition": { "$ref": "#/definitions/Point" }
535 "required": ["npcName", "npcPosition"]
543-- ========================================================================
544-- Light (sum type with unwrapUnaryRecords)
545-- ========================================================================
551 | LightWaveLength { waveLength :: Double }
554instance ToSchema Light where
555 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
556 { unwrapUnaryRecords = True }
558lightSchemaJSON :: Value
559lightSchemaJSON = [aesonQQ|
564 "NoLight": { "type": "array", "items": [] },
565 "LightFreq": { "type": "number", "format": "double" },
566 "LightColor": { "$ref": "#/definitions/Color" },
567 "LightWaveLength": { "type": "number", "format": "double" }
574lightInlinedSchemaJSON :: Value
575lightInlinedSchemaJSON = [aesonQQ|
580 "NoLight": { "type": "array", "items": [] },
581 "LightFreq": { "type": "number", "format": "double" },
585 "enum": ["Red", "Green", "Blue"]
587 "LightWaveLength": { "type": "number", "format": "double" }
594-- ========================================================================
595-- ResourceId (series of newtypes)
596-- ========================================================================
598newtype Id = Id String deriving (Generic)
601newtype ResourceId = ResourceId Id deriving (Generic)
602instance 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