swagger2-0.4: test/Data/Swagger/SchemaSpec.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
module Data.Swagger.SchemaSpec where
import Data.Aeson
import Data.Aeson.QQ
import Data.Char
import Data.Proxy
import Data.Set (Set)
import GHC.Generics
import Data.Swagger
import SpecCommon
import Test.Hspec
checkToSchema :: ToSchema a => Proxy a -> Value -> Spec
checkToSchema proxy js = toSchema proxy <~> js
checkSchemaName :: ToSchema a => Maybe String -> Proxy a -> Spec
checkSchemaName sname proxy =
it ("schema name is " ++ show sname) $
schemaName proxy `shouldBe` sname
spec :: Spec
spec = do
describe "Generic ToSchema" $ do
context "Unit" $ checkToSchema (Proxy :: Proxy Unit) unitSchemaJSON
context "Person" $ checkToSchema (Proxy :: Proxy Person) personSchemaJSON
context "ISPair" $ checkToSchema (Proxy :: Proxy ISPair) ispairSchemaJSON
context "Point (fieldLabelModifier)" $ checkToSchema (Proxy :: Proxy Point) pointSchemaJSON
context "Color (bounded enum)" $ checkToSchema (Proxy :: Proxy Color) colorSchemaJSON
context "Paint (record with bounded enum field)" $ checkToSchema (Proxy :: Proxy Paint) paintSchemaJSON
context "UserGroup (set newtype)" $ checkToSchema (Proxy :: Proxy UserGroup) userGroupSchemaJSON
context "Unary records" $ do
context "Email (unwrapUnaryRecords)" $ checkToSchema (Proxy :: Proxy Email) emailSchemaJSON
context "UserId (non-record newtype)" $ checkToSchema (Proxy :: Proxy UserId) userIdSchemaJSON
context "Player (unary record)" $ checkToSchema (Proxy :: Proxy Player) playerSchemaJSON
context "Players (inlining schema)" $ checkToSchema (Proxy :: Proxy Players) playersSchemaJSON
context "MyRoseTree (datatypeNameModifier)" $ checkToSchema (Proxy :: Proxy MyRoseTree) myRoseTreeSchemaJSON
context "Sum types" $ do
context "Status (sum of unary constructors)" $ checkToSchema (Proxy :: Proxy Status) statusSchemaJSON
context "Character (ref and record sum)" $ checkToSchema (Proxy :: Proxy Character) characterSchemaJSON
context "Light (sum with unwrapUnaryRecords)" $ checkToSchema (Proxy :: Proxy Light) lightSchemaJSON
context "Schema name" $ do
context "String" $ checkSchemaName Nothing (Proxy :: Proxy String)
context "(Int, Float)" $ checkSchemaName Nothing (Proxy :: Proxy (Int, Float))
context "Person" $ checkSchemaName (Just "Person") (Proxy :: Proxy Person)
main :: IO ()
main = hspec spec
-- ========================================================================
-- Person (simple record with optional fields)
-- ========================================================================
data Person = Person
{ name :: String
, phone :: Integer
, email :: Maybe String
} deriving (Generic, ToSchema)
personSchemaJSON :: Value
personSchemaJSON = [aesonQQ|
{
"type": "object",
"properties":
{
"name": { "type": "string" },
"phone": { "type": "integer" },
"email": { "type": "string" }
},
"required": ["name", "phone"]
}
|]
-- ========================================================================
-- ISPair (non-record product data type)
-- ========================================================================
data ISPair = ISPair Integer String
deriving (Generic, ToSchema)
ispairSchemaJSON :: Value
ispairSchemaJSON = [aesonQQ|
{
"type": "array",
"items":
[
{ "type": "integer" },
{ "type": "string" }
]
}
|]
-- ========================================================================
-- Point (record data type with custom fieldLabelModifier)
-- ========================================================================
data Point = Point
{ pointX :: Double
, pointY :: Double
} deriving (Generic)
instance ToSchema Point where
toNamedSchema = genericToNamedSchema defaultSchemaOptions
{ fieldLabelModifier = map toLower . drop (length "point") }
pointSchemaJSON :: Value
pointSchemaJSON = [aesonQQ|
{
"type": "object",
"properties":
{
"x": { "type": "number" },
"y": { "type": "number" }
},
"required": ["x", "y"]
}
|]
-- ========================================================================
-- Color (enum)
-- ========================================================================
data Color
= Red
| Green
| Blue
deriving (Generic, ToSchema)
colorSchemaJSON :: Value
colorSchemaJSON = [aesonQQ|
{
"type": "string",
"enum": ["Red", "Green", "Blue"]
}
|]
-- ========================================================================
-- Paint (record with bounded enum property)
-- ========================================================================
newtype Paint = Paint { color :: Color }
deriving (Generic, ToSchema)
paintSchemaJSON :: Value
paintSchemaJSON = [aesonQQ|
{
"type": "object",
"properties":
{
"color":
{
"$ref": "#/definitions/Color"
}
},
"required": ["color"]
}
|]
-- ========================================================================
-- Email (newtype with unwrapUnaryRecords set to True)
-- ========================================================================
newtype Email = Email { getEmail :: String }
deriving (Generic)
instance ToSchema Email where
toNamedSchema = genericToNamedSchema defaultSchemaOptions
{ unwrapUnaryRecords = True }
emailSchemaJSON :: Value
emailSchemaJSON = [aesonQQ|
{
"type": "string"
}
|]
-- ========================================================================
-- UserId (non-record newtype)
-- ========================================================================
newtype UserId = UserId Integer
deriving (Eq, Ord, Generic, ToSchema)
userIdSchemaJSON :: Value
userIdSchemaJSON = [aesonQQ|
{
"type": "integer"
}
|]
-- ========================================================================
-- UserGroup (set newtype)
-- ========================================================================
newtype UserGroup = UserGroup (Set UserId)
deriving (Generic, ToSchema)
userGroupSchemaJSON :: Value
userGroupSchemaJSON = [aesonQQ|
{
"type": "array",
"items": { "$ref": "#/definitions/UserId" },
"uniqueItems": true
}
|]
-- ========================================================================
-- Player (record newtype)
-- ========================================================================
newtype Player = Player
{ position :: Point
} deriving (Generic, ToSchema)
playerSchemaJSON :: Value
playerSchemaJSON = [aesonQQ|
{
"type": "object",
"properties":
{
"position":
{
"$ref": "#/definitions/Point"
}
},
"required": ["position"]
}
|]
-- ========================================================================
-- MyRoseTree (custom datatypeNameModifier)
-- ========================================================================
data MyRoseTree = MyRoseTree
{ root :: String
, trees :: [MyRoseTree]
} deriving (Generic)
instance ToSchema MyRoseTree where
toNamedSchema = genericToNamedSchema defaultSchemaOptions
{ datatypeNameModifier = drop (length "My") }
myRoseTreeSchemaJSON :: Value
myRoseTreeSchemaJSON = [aesonQQ|
{
"type": "object",
"properties":
{
"root": { "type": "string" },
"trees":
{
"type": "array",
"items":
{
"$ref": "#/definitions/RoseTree"
}
}
},
"required": ["root", "trees"]
}
|]
-- ========================================================================
-- Inlined (newtype for inlining schemas)
-- ========================================================================
newtype Inlined a = Inlined { getInlined :: a }
instance ToSchema a => ToSchema (Inlined a) where
toNamedSchema _ = (Nothing, toSchema (Proxy :: Proxy a))
newtype Players = Players [Inlined Player]
deriving (Generic, ToSchema)
playersSchemaJSON :: Value
playersSchemaJSON = [aesonQQ|
{
"type": "array",
"items":
{
"type": "object",
"properties":
{
"position":
{
"$ref": "#/definitions/Point"
}
},
"required": ["position"]
}
}
|]
-- ========================================================================
-- Status (sum type with unary constructors)
-- ========================================================================
data Status
= StatusOk String
| StatusError String
deriving (Generic)
instance ToSchema Status where
toNamedSchema = genericToNamedSchema defaultSchemaOptions
{ constructorTagModifier = map toLower . drop (length "Status") }
statusSchemaJSON :: Value
statusSchemaJSON = [aesonQQ|
{
"type": "object",
"properties":
{
"ok": { "type": "string" },
"error": { "type": "string" }
},
"maxProperties": 1,
"minProperties": 1
}
|]
-- ========================================================================
-- Unit type
-- ========================================================================
data Unit = Unit deriving (Generic, ToSchema)
unitSchemaJSON :: Value
unitSchemaJSON = [aesonQQ|
{
"type": "array",
"enum": [[]]
}
|]
-- ========================================================================
-- Character (sum type with ref and record in alternative)
-- ========================================================================
data Character
= PC Player
| NPC { npcName :: String, npcPosition :: Point }
deriving (Generic, ToSchema)
characterSchemaJSON :: Value
characterSchemaJSON = [aesonQQ|
{
"type": "object",
"properties":
{
"PC": { "$ref": "#/definitions/Player" },
"NPC":
{
"type": "object",
"properties":
{
"npcName": { "type": "string" },
"npcPosition": { "$ref": "#/definitions/Point" }
},
"required": ["npcName", "npcPosition"]
}
},
"maxProperties": 1,
"minProperties": 1
}
|]
-- ========================================================================
-- Light (sum type with unwrapUnaryRecords)
-- ========================================================================
data Light
= LightFreq Double
| LightColor Color
| LightWaveLength { waveLength :: Double }
deriving (Generic)
instance ToSchema Light where
toNamedSchema = genericToNamedSchema defaultSchemaOptions
{ unwrapUnaryRecords = True }
lightSchemaJSON :: Value
lightSchemaJSON = [aesonQQ|
{
"type": "object",
"properties":
{
"LightFreq": { "type": "number" },
"LightColor": { "$ref": "#/definitions/Color" },
"LightWaveLength": { "type": "number" }
},
"maxProperties": 1,
"minProperties": 1
}
|]