{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Main (main) where
import Control.Lens (At(at), (&), set)
import Data.Aeson (FromJSON, ToJSON, encode, toJSON)
import Data.JsonSpec (Field(Field), HasJsonDecodingSpec(DecodingSpec,
fromJSONStructure), HasJsonEncodingSpec(EncodingSpec, toJSONStructure),
SpecJSON(SpecJSON), Specification(JsonArray, JsonBool, JsonDateTime,
JsonEither, JsonNum, JsonObject, JsonString, JsonTag))
import Data.JsonSpec.OpenApi (EncodingSchema, toOpenApiSchema)
import Data.OpenApi (ToSchema)
import Data.Proxy (Proxy(Proxy))
import Data.Text (Text)
import Data.Time (UTCTime)
import Prelude (Applicative(pure), Bool(False), Maybe(Just),
Monoid(mempty), ($), Eq, IO, Show)
import Test.Hspec (describe, hspec, it, shouldBe)
import qualified Data.OpenApi as OA
main :: IO ()
main =
hspec $ do
describe "toOpenApiSchema" $ do
it "string" $
let
actual :: OA.Schema
actual =
toOpenApiSchema (Proxy @JsonString)
expected :: OA.Schema
expected = stringSchema
in
actual `shouldBe` expected
it "sum" $
let
actual :: OA.Schema
actual =
toOpenApiSchema (Proxy @(
JsonEither
(
JsonObject
'[ '("tag", JsonTag "a")
, '("content", JsonString)
]
)
(
JsonObject
'[ '("tag", JsonTag "b")
, '("content", JsonString)
]
)
))
expected :: OA.Schema
expected =
mempty
& set OA.oneOf (Just
[ OA.Inline $
mempty
& set OA.type_ (Just OA.OpenApiObject)
& set OA.properties (
mempty
& set (at "tag") (Just (OA.Inline (
mempty & set OA.enum_ (Just [toJSON ("a" :: Text)])
)))
& set (at "content") (Just (OA.Inline stringSchema))
)
& set OA.required ["tag", "content"]
& set
OA.additionalProperties
(Just (OA.AdditionalPropertiesAllowed False))
, OA.Inline $
mempty
& set OA.type_ (Just OA.OpenApiObject)
& set OA.properties (
mempty
& set (at "tag") (Just (OA.Inline (
mempty & set OA.enum_ (Just [toJSON ("b" :: Text)])
)))
& set (at "content") (Just (OA.Inline stringSchema))
)
& set OA.required ["tag", "content"]
& set
OA.additionalProperties
(Just (OA.AdditionalPropertiesAllowed False))
]
)
in
actual `shouldBe` expected
it "object" $
let
actual :: OA.Schema
actual =
toOpenApiSchema (Proxy @(
JsonObject '[
'("Foo", JsonString),
'("Bar", JsonString)
]
))
expected :: OA.Schema
expected =
mempty
& set OA.type_ (Just OA.OpenApiObject)
& set OA.properties (
mempty
& set (at "Foo") (Just (OA.Inline stringSchema))
& set (at "Bar") (Just (OA.Inline stringSchema))
)
& set OA.required ["Foo", "Bar"]
& set
OA.additionalProperties
(Just (OA.AdditionalPropertiesAllowed False))
in
actual `shouldBe` expected
it "num" $
let
actual :: OA.Schema
actual =
toOpenApiSchema (Proxy @JsonNum)
expected :: OA.Schema
expected = numSchema
in
actual `shouldBe` expected
it "complex array" $
let
actual :: OA.Schema
actual =
toOpenApiSchema (Proxy @(
JsonArray (
JsonObject '[
'("Foo", JsonString),
'("Bar", JsonString)
]
)
))
expected :: OA.Schema
expected =
let
elementSchema :: OA.Schema
elementSchema =
mempty
& set OA.type_ (Just OA.OpenApiObject)
& set OA.properties (
mempty
& set (at "Foo") (Just (OA.Inline stringSchema))
& set (at "Bar") (Just (OA.Inline stringSchema))
)
& set OA.required ["Foo", "Bar"]
& set
OA.additionalProperties
(Just (OA.AdditionalPropertiesAllowed False))
in
mempty
& set OA.type_ (Just OA.OpenApiArray)
& set OA.items (Just (OA.OpenApiItemsObject (OA.Inline elementSchema)))
in
actual `shouldBe` expected
it "bool" $
let
actual :: OA.Schema
actual =
toOpenApiSchema (Proxy @JsonBool)
expected :: OA.Schema
expected = boolSchema
in
actual `shouldBe` expected
it "date-time" $
let
actual :: OA.Schema
actual =
toOpenApiSchema (Proxy @JsonDateTime)
expected :: OA.Schema
expected =
stringSchema
& set OA.format (Just "date-time")
in
actual `shouldBe` expected
describe "EncodingSpec" $
it "works" $
let
actual :: OA.Schema
actual = OA.toSchema (Proxy @User)
expected :: OA.Schema
expected =
mempty
& set OA.type_ (Just OA.OpenApiObject)
& set OA.properties (
mempty
& set (at "name") (Just (OA.Inline stringSchema))
& set (at "last-login") (Just (OA.Inline dateSchema))
)
& set OA.required ["name", "last-login"]
& set
OA.additionalProperties
(Just (OA.AdditionalPropertiesAllowed False))
in
encode actual `shouldBe` encode expected
data User = User
{ name :: Text
, lastLogin :: UTCTime
}
deriving stock (Show, Eq)
deriving ToSchema via (EncodingSchema User)
deriving (ToJSON, FromJSON) via (SpecJSON User)
instance HasJsonEncodingSpec User where
type EncodingSpec User =
JsonObject
'[ '("name", JsonString)
, '("last-login", JsonDateTime)
]
toJSONStructure user =
(Field @"name" (name user),
(Field @"last-login" (lastLogin user),
()))
instance HasJsonDecodingSpec User where
type DecodingSpec User = EncodingSpec User
fromJSONStructure
(Field @"name" name,
(Field @"last-login" lastLogin,
()))
=
pure User { name , lastLogin }
stringSchema :: OA.Schema
stringSchema = mempty & set OA.type_ (Just OA.OpenApiString)
numSchema :: OA.Schema
numSchema = mempty & set OA.type_ (Just OA.OpenApiNumber)
boolSchema :: OA.Schema
boolSchema = mempty & set OA.type_ (Just OA.OpenApiBoolean)
dateSchema :: OA.Schema
dateSchema =
mempty
& set OA.type_ (Just OA.OpenApiString)
& set OA.format (Just "date-time")