git.iko.soy/typeable/openapi3
on master help sign in
.github/workflows/haskell-ci.yml+96−30
since splitsnapshots
unifiedsplit
@@ -8,67 +8,117 @@
8#
8#
9# For more information, see https://github.com/haskell-CI/haskell-ci
9# For more information, see https://github.com/haskell-CI/haskell-ci
10#
10#
11−# version: 0.12
11+# version: 0.14.3
12#
12#
13−# REGENDATA ("0.12",["github","cabal.project"])
13+# REGENDATA ("0.14.3",["github","cabal.project"])
14#
14#
15name: Haskell-CI
15name: Haskell-CI
16on:
16on:
17− - push
17+ push:
18+ branches:
19+ - master
18− - pull_request
20+ pull_request:
21+ branches:
22+ - master
19jobs:
23jobs:
20 linux:
24 linux:
21 name: Haskell-CI - Linux - ${{ matrix.compiler }}
25 name: Haskell-CI - Linux - ${{ matrix.compiler }}
22 runs-on: ubuntu-18.04
26 runs-on: ubuntu-18.04
27+ timeout-minutes:
28+ 60
23 container:
29 container:
24− image: buildpack-deps:bionic
30+ image: buildpack-deps:focal
25 continue-on-error: ${{ matrix.allow-failure }}
31 continue-on-error: ${{ matrix.allow-failure }}
26 strategy:
32 strategy:
27 matrix:
33 matrix:
28 include:
34 include:
29− - compiler: ghc-9.0.1
35+ - compiler: ghc-9.2.2
36+ compilerKind: ghc
37+ compilerVersion: 9.2.2
38+ setup-method: ghcup
30 allow-failure: true
39 allow-failure: true
31− - compiler: ghc-8.10.4
40+ - compiler: ghc-9.0.2
41+ compilerKind: ghc
42+ compilerVersion: 9.0.2
43+ setup-method: ghcup
44+ allow-failure: false
45+ - compiler: ghc-8.10.7
46+ compilerKind: ghc
47+ compilerVersion: 8.10.7
48+ setup-method: ghcup
32 allow-failure: false
49 allow-failure: false
33 - compiler: ghc-8.8.4
50 - compiler: ghc-8.8.4
51+ compilerKind: ghc
52+ compilerVersion: 8.8.4
53+ setup-method: hvr-ppa
34 allow-failure: false
54 allow-failure: false
35 - compiler: ghc-8.6.5
55 - compiler: ghc-8.6.5
56+ compilerKind: ghc
57+ compilerVersion: 8.6.5
58+ setup-method: hvr-ppa
36 allow-failure: false
59 allow-failure: false
37 - compiler: ghc-8.4.4
60 - compiler: ghc-8.4.4
61+ compilerKind: ghc
62+ compilerVersion: 8.4.4
63+ setup-method: hvr-ppa
38 allow-failure: false
64 allow-failure: false
39 fail-fast: false
65 fail-fast: false
40 steps:
66 steps:
41 - name: apt
67 - name: apt
42 run: |
68 run: |
43 apt-get update
69 apt-get update
44− apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common
70+ apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
71+ if [ "${{ matrix.setup-method }}" = ghcup ]; then
72+ mkdir -p "$HOME/.ghcup/bin"
73+ curl -sL https://downloads.haskell.org/ghcup/0.1.17.5/x86_64-linux-ghcup-0.1.17.5 > "$HOME/.ghcup/bin/ghcup"
74+ chmod a+x "$HOME/.ghcup/bin/ghcup"
75+ "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER"
76+ "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
77+ else
45− apt-add-repository -y 'ppa:hvr/ghc'
78+ apt-add-repository -y 'ppa:hvr/ghc'
46− apt-get update
79+ apt-get update
47− apt-get install -y $CC cabal-install-3.4
80+ apt-get install -y "$HCNAME"
81+ mkdir -p "$HOME/.ghcup/bin"
82+ curl -sL https://downloads.haskell.org/ghcup/0.1.17.5/x86_64-linux-ghcup-0.1.17.5 > "$HOME/.ghcup/bin/ghcup"
83+ chmod a+x "$HOME/.ghcup/bin/ghcup"
84+ "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
85+ fi
48 env:
86 env:
87+ HCKIND: ${{ matrix.compilerKind }}
49− CC: ${{ matrix.compiler }}
88+ HCNAME: ${{ matrix.compiler }}
89+ HCVER: ${{ matrix.compilerVersion }}
50 - name: Set PATH and environment variables
90 - name: Set PATH and environment variables
51 run: |
91 run: |
52 echo "$HOME/.cabal/bin" >> $GITHUB_PATH
92 echo "$HOME/.cabal/bin" >> $GITHUB_PATH
53− echo "LANG=C.UTF-8" >> $GITHUB_ENV
93+ echo "LANG=C.UTF-8" >> "$GITHUB_ENV"
54− echo "CABAL_DIR=$HOME/.cabal" >> $GITHUB_ENV
94+ echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
55− echo "CABAL_CONFIG=$HOME/.cabal/config" >> $GITHUB_ENV
95+ echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
56− HCDIR=$(echo "/opt/$CC" | sed 's/-/\//')
96+ HCDIR=/opt/$HCKIND/$HCVER
57− HCNAME=ghc
97+ if [ "${{ matrix.setup-method }}" = ghcup ]; then
58− HC=$HCDIR/bin/$HCNAME
98+ HC=$HOME/.ghcup/bin/$HCKIND-$HCVER
59− echo "HC=$HC" >> $GITHUB_ENV
99+ echo "HC=$HC" >> "$GITHUB_ENV"
60− echo "HCPKG=$HCDIR/bin/$HCNAME-pkg" >> $GITHUB_ENV
100+ echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
61− echo "HADDOCK=$HCDIR/bin/haddock" >> $GITHUB_ENV
101+ echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
102+ echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
103+ else
104+ HC=$HCDIR/bin/$HCKIND
105+ echo "HC=$HC" >> "$GITHUB_ENV"
106+ echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
107+ echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
62− echo "CABAL=/opt/cabal/3.4/bin/cabal -vnormal+nowrap" >> $GITHUB_ENV
108+ echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
109+ fi
110+
63 HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
111 HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
64− echo "HCNUMVER=$HCNUMVER" >> $GITHUB_ENV
112+ echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
65− echo "ARG_TESTS=--enable-tests" >> $GITHUB_ENV
113+ echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
66− echo "ARG_BENCH=--enable-benchmarks" >> $GITHUB_ENV
114+ echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
67− echo "HEADHACKAGE=false" >> $GITHUB_ENV
115+ echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
68− echo "ARG_COMPILER=--$HCNAME --with-compiler=$HC" >> $GITHUB_ENV
116+ echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
69− echo "GHCJSARITH=0" >> $GITHUB_ENV
117+ echo "GHCJSARITH=0" >> "$GITHUB_ENV"
70 env:
118 env:
119+ HCKIND: ${{ matrix.compilerKind }}
71− CC: ${{ matrix.compiler }}
120+ HCNAME: ${{ matrix.compiler }}
121+ HCVER: ${{ matrix.compilerVersion }}
72 - name: env
122 - name: env
73 run: |
123 run: |
74 env
124 env
@@ -91,6 +141,10 @@ jobs:
91 repository hackage.haskell.org
141 repository hackage.haskell.org
92 url: http://hackage.haskell.org/
142 url: http://hackage.haskell.org/
93 EOF
143 EOF
144+ cat >> $CABAL_CONFIG <<EOF
145+ program-default-options
146+ ghc-options: $GHCJOBS +RTS -M3G -RTS
147+ EOF
94 cat $CABAL_CONFIG
148 cat $CABAL_CONFIG
95 - name: versions
149 - name: versions
96 run: |
150 run: |
@@ -129,7 +183,8 @@ jobs:
129 - name: generate cabal.project
183 - name: generate cabal.project
130 run: |
184 run: |
131 PKGDIR_openapi3="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/openapi3-[0-9.]*')"
185 PKGDIR_openapi3="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/openapi3-[0-9.]*')"
132− echo "PKGDIR_openapi3=${PKGDIR_openapi3}" >> $GITHUB_ENV
186+ echo "PKGDIR_openapi3=${PKGDIR_openapi3}" >> "$GITHUB_ENV"
187+ rm -f cabal.project cabal.project.local
133 touch cabal.project
188 touch cabal.project
134 touch cabal.project.local
189 touch cabal.project.local
135 echo "packages: ${PKGDIR_openapi3}" >> cabal.project
190 echo "packages: ${PKGDIR_openapi3}" >> cabal.project
@@ -175,3 +230,14 @@ jobs:
175 run: |
230 run: |
176 rm -f cabal.project.local
231 rm -f cabal.project.local
177 $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
232 $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
233+ - name: prepare for constraint sets
234+ run: |
235+ rm -f cabal.project.local
236+ - name: constraint set aeson-2
237+ run: |
238+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --constraint='aeson >=2.0' --dependencies-only -j2 all
239+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --constraint='aeson >=2.0' all
240+ - name: constraint set aeson-1
241+ run: |
242+ if [ $((HCNUMVER < 90200)) -ne 0 ] ; then $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --constraint='aeson <2.0' --dependencies-only -j2 all ; fi
243+ if [ $((HCNUMVER < 90200)) -ne 0 ] ; then $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --constraint='aeson <2.0' all ; fi
CHANGELOG.md+26−1
@@ -1,7 +1,32 @@
1Unreleased
1Unreleased
2----------
2----------
3
3
4+3.2.2
5+-----
6+
7+- Fix bug with infinite recursion in GToSchema [#37](https://github.com/biocad/openapi3/pull/37).
8+
9+3.2.1
10+-----
11+
12+- Support aeson-2.0.3.
13+- Support GHC-9.2 (doctests still fail).
14+
15+3.2.0
16+-----
17+
18+- Support aeson-2 [#34](https://github.com/biocad/openapi3/pull/34).
19+- Use `SecurityDefinitions` type for `_componentsSecuritySchemes`
20+ [#32](https://github.com/biocad/openapi3/pull/32).
21+
22+3.1.0
23+-----
24+
25+- Use `format: date-time` for `ToParamSchema ZonedTime` instance (see
26+ [#20](https://github.com/biocad/openapi3/pull/20)).
27+- Support generating schema for any polymorphic types via `Typeable` (see
28+ [#19](https://github.com/biocad/openapi3/pull/19)).
4−- Allow `hashable-1.3.1`, prettify doctests.
29+- Allow `hashable-1.3.1`, prettify doctests (see [#18](https://github.com/biocad/openapi3/pull/18)).
5
30
63.0.2.0
313.0.2.0
7-------
32-------
cabal.haskell-cinew file+11
@@ -0,0 +1,11 @@
1+branches: master
2+
3+distribution: focal
4+allow-failures: >=9.2
5+
6+constraint-set aeson-1
7+ constraints: aeson <2.0
8+ ghc: <9.2
9+
10+constraint-set aeson-2
11+ constraints: aeson >=2.0
openapi3.cabal+17−14
@@ -1,6 +1,6 @@
1cabal-version: >=1.10
1cabal-version: >=1.10
2name: openapi3
2name: openapi3
3−version: 3.0.2.0
3+version: 3.2.2
4
4
5synopsis: OpenAPI 3.0 data model
5synopsis: OpenAPI 3.0 data model
6category: Web, Swagger, OpenApi
6category: Web, Swagger, OpenApi
@@ -26,8 +26,9 @@ tested-with:
26 GHC ==8.4.4
26 GHC ==8.4.4
27 || ==8.6.5
27 || ==8.6.5
28 || ==8.8.4
28 || ==8.8.4
29− || ==8.10.4
29+ || ==8.10.7
30− || ==9.0.1
30+ || ==9.0.2
31+ || ==9.2.2
31
32
32custom-setup
33custom-setup
33 setup-depends:
34 setup-depends:
@@ -56,31 +57,33 @@ library
56 Data.OpenApi.Internal.AesonUtils
57 Data.OpenApi.Internal.AesonUtils
57 Data.OpenApi.Internal.TypeShape
58 Data.OpenApi.Internal.TypeShape
58
59
60+ Data.OpenApi.Aeson.Compat
61+
59 -- GHC boot libraries
62 -- GHC boot libraries
60 build-depends:
63 build-depends:
61− base >=4.11.1.0 && <4.16
64+ base >=4.11.1.0 && <4.17
62− , bytestring >=0.10.8.2 && <0.11
65+ , bytestring >=0.10.8.2 && <0.12
63 , containers >=0.5.11.0 && <0.7
66 , containers >=0.5.11.0 && <0.7
64− , template-haskell >=2.13.0.0 && <2.18
67+ , template-haskell >=2.13.0.0 && <2.19
65− , time >=1.8.0.2 && <1.10
68+ , time >=1.8.0.2 && <1.14
66 , transformers >=0.5.5.0 && <0.6
69 , transformers >=0.5.5.0 && <0.6
67
70
68 build-depends:
71 build-depends:
69 mtl >=2.2.2 && <2.3
72 mtl >=2.2.2 && <2.3
70− , text >=1.2.3.1 && <1.3
73+ , text >=1.2.3.1 && <2.1
71
74
72 -- other dependencies
75 -- other dependencies
73 build-depends:
76 build-depends:
74− base-compat-batteries >=0.11.1 && <0.12
77+ base-compat-batteries >=0.11.1 && <0.13
75− , aeson >=1.4.2.0 && <1.6
78+ , aeson >=1.4.2.0 && <1.6 || >=2.0.1.0 && < 2.1
76 , aeson-pretty >=0.8.7 && <0.9
79 , aeson-pretty >=0.8.7 && <0.9
77 -- cookie 0.4.3 is needed by GHC 7.8 due to time>=1.4 constraint
80 -- cookie 0.4.3 is needed by GHC 7.8 due to time>=1.4 constraint
78 , cookie >=0.4.3 && <0.5
81 , cookie >=0.4.3 && <0.5
79 , generics-sop >=0.5.1.0 && <0.6
82 , generics-sop >=0.5.1.0 && <0.6
80− , hashable >=1.2.7.0 && <1.4
83+ , hashable >=1.2.7.0 && <1.5
81 , http-media >=0.8.0.0 && <0.9
84 , http-media >=0.8.0.0 && <0.9
82 , insert-ordered-containers >=0.2.3 && <0.3
85 , insert-ordered-containers >=0.2.3 && <0.3
83− , lens >=4.16.1 && <5.1
86+ , lens >=4.16.1 && <5.2
84 , network >=2.6.3.5 && <3.2
87 , network >=2.6.3.5 && <3.2
85 , optics-core >=0.2 && <0.5
88 , optics-core >=0.2 && <0.5
86 , optics-th >=0.2 && <0.5
89 , optics-th >=0.2 && <0.5
@@ -119,14 +122,14 @@ test-suite spec
119
122
120 -- test-suite only dependencies
123 -- test-suite only dependencies
121 build-depends:
124 build-depends:
122− hspec >=2.5.5 && <2.8
125+ hspec >=2.5.5 && <2.10
123 , HUnit >=1.6.0.0 && <1.7
126 , HUnit >=1.6.0.0 && <1.7
124 , quickcheck-instances >=0.3.19 && <0.14
127 , quickcheck-instances >=0.3.19 && <0.14
125 , utf8-string >=1.0.1.1 && <1.1
128 , utf8-string >=1.0.1.1 && <1.1
126
129
127 -- https://github.com/haskell/cabal/issues/3708
130 -- https://github.com/haskell/cabal/issues/3708
128 build-tool-depends:
131 build-tool-depends:
129− hspec-discover:hspec-discover >=2.5.5 && <2.8
132+ hspec-discover:hspec-discover >=2.5.5 && <2.10
130
133
131 other-modules:
134 other-modules:
132 SpecCommon
135 SpecCommon
src/Data/OpenApi/Aeson/Compat.hsnew file+76
@@ -0,0 +1,76 @@
1+{-# LANGUAGE CPP #-}
2+
3+module Data.OpenApi.Aeson.Compat where
4+
5+#if MIN_VERSION_aeson(2,0,0)
6+import Data.Aeson (Key)
7+import qualified Data.Aeson.Key as Key
8+import qualified Data.Aeson.KeyMap as KeyMap
9+#else
10+import qualified Data.HashMap.Strict as HM
11+#endif
12+import Data.Bifunctor (first)
13+import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
14+import qualified Data.Text as T
15+
16+#if MIN_VERSION_aeson(2,0,0)
17+deleteKey :: Key -> KeyMap.KeyMap v -> KeyMap.KeyMap v
18+deleteKey = KeyMap.delete
19+
20+objectToList :: KeyMap.KeyMap v -> [(Key, v)]
21+objectToList = KeyMap.toList
22+
23+objectKeys :: KeyMap.KeyMap v -> [T.Text]
24+objectKeys = map Key.toText . KeyMap.keys
25+
26+stringToKey :: String -> Key
27+stringToKey = Key.fromString
28+
29+keyToString :: Key -> String
30+keyToString = Key.toString
31+
32+keyToText :: Key -> T.Text
33+keyToText = Key.toText
34+
35+toInsOrdHashMap :: KeyMap.KeyMap v -> InsOrdHashMap.InsOrdHashMap T.Text v
36+toInsOrdHashMap = InsOrdHashMap.fromList . fmap (first Key.toText) . KeyMap.toList
37+
38+fromInsOrdHashMap :: InsOrdHashMap.InsOrdHashMap T.Text v -> KeyMap.KeyMap v
39+fromInsOrdHashMap = KeyMap.fromList . fmap (first Key.fromText) . InsOrdHashMap.toList
40+
41+lookupKey :: T.Text -> KeyMap.KeyMap v -> Maybe v
42+lookupKey = KeyMap.lookup . Key.fromText
43+
44+hasKey :: T.Text -> KeyMap.KeyMap a -> Bool
45+hasKey = KeyMap.member . Key.fromText
46+#else
47+deleteKey :: T.Text -> HM.HashMap T.Text v -> HM.HashMap T.Text v
48+deleteKey = HM.delete
49+
50+objectToList :: HM.HashMap T.Text v -> [(T.Text, v)]
51+objectToList = HM.toList
52+
53+objectKeys :: HM.HashMap T.Text v -> [T.Text]
54+objectKeys = HM.keys
55+
56+stringToKey :: String -> T.Text
57+stringToKey = T.pack
58+
59+keyToString :: T.Text -> String
60+keyToString = T.unpack
61+
62+keyToText :: T.Text -> T.Text
63+keyToText = id
64+
65+toInsOrdHashMap :: HM.HashMap T.Text v -> InsOrdHashMap.InsOrdHashMap T.Text v
66+toInsOrdHashMap = InsOrdHashMap.fromHashMap
67+
68+fromInsOrdHashMap :: InsOrdHashMap.InsOrdHashMap T.Text v -> HM.HashMap T.Text v
69+fromInsOrdHashMap = InsOrdHashMap.toHashMap
70+
71+lookupKey :: T.Text -> HM.HashMap T.Text v -> Maybe v
72+lookupKey = HM.lookup
73+
74+hasKey :: T.Text -> HM.HashMap T.Text a -> Bool
75+hasKey = HM.member
76+#endif
src/Data/OpenApi/Internal.hs+18−15
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE DeriveGeneric #-}
@@ -21,6 +22,9 @@ import Prelude.Compat
21import Control.Applicative
22import Control.Applicative
22import Control.Lens ((&), (.~), (?~))
23import Control.Lens ((&), (.~), (?~))
23import Data.Aeson hiding (Encoding)
24import Data.Aeson hiding (Encoding)
25+#if MIN_VERSION_aeson(2,0,0)
26+import qualified Data.Aeson.KeyMap as KeyMap
27+#endif
24import qualified Data.Aeson.Types as JSON
28import qualified Data.Aeson.Types as JSON
25import Data.Data (Constr, Data (..), DataType, Fixity (..), Typeable,
29import Data.Data (Constr, Data (..), DataType, Fixity (..), Typeable,
26 constrIndex, mkConstr, mkDataType)
30 constrIndex, mkConstr, mkDataType)
@@ -45,17 +49,13 @@ import Text.Read (readMaybe)
45import Data.HashMap.Strict.InsOrd (InsOrdHashMap)
49import Data.HashMap.Strict.InsOrd (InsOrdHashMap)
46import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
50import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
47
51
48−import Generics.SOP.TH (deriveGeneric)
52+import Data.OpenApi.Aeson.Compat (deleteKey)
49−import Data.OpenApi.Internal.AesonUtils (sopSwaggerGenericToJSON
53+import Data.OpenApi.Internal.AesonUtils (AesonDefaultValue (..), HasSwaggerAesonOptions (..),
50− ,sopSwaggerGenericToJSONWithOpts
51− ,sopSwaggerGenericParseJSON
52− ,HasSwaggerAesonOptions(..)
53− ,AesonDefaultValue(..)
54− ,mkSwaggerAesonOptions
54+ mkSwaggerAesonOptions, saoAdditionalPairs, saoSubObject,
55− ,saoAdditionalPairs
55+ sopSwaggerGenericParseJSON, sopSwaggerGenericToEncoding,
56− ,saoSubObject)
56+ sopSwaggerGenericToJSON, sopSwaggerGenericToJSONWithOpts)
57import Data.OpenApi.Internal.Utils
57import Data.OpenApi.Internal.Utils
58−import Data.OpenApi.Internal.AesonUtils (sopSwaggerGenericToEncoding)
58+import Generics.SOP.TH (deriveGeneric)
59
59
60-- $setup
60-- $setup
61-- >>> :seti -XDataKinds
61-- >>> :seti -XDataKinds
@@ -196,7 +196,7 @@ data Components = Components
196 , _componentsExamples :: Definitions Example
196 , _componentsExamples :: Definitions Example
197 , _componentsRequestBodies :: Definitions RequestBody
197 , _componentsRequestBodies :: Definitions RequestBody
198 , _componentsHeaders :: Definitions Header
198 , _componentsHeaders :: Definitions Header
199− , _componentsSecuritySchemes :: Definitions SecurityScheme
199+ , _componentsSecuritySchemes :: SecurityDefinitions
200 , _componentsLinks :: Definitions Link
200 , _componentsLinks :: Definitions Link
201 , _componentsCallbacks :: Definitions Callback
201 , _componentsCallbacks :: Definitions Callback
202 } deriving (Eq, Show, Generic, Data, Typeable)
202 } deriving (Eq, Show, Generic, Data, Typeable)
@@ -337,7 +337,7 @@ data RequestBody = RequestBody
337 -- | The content of the request body.
337 -- | The content of the request body.
338 -- The key is a media type or media type range and the value describes it.
338 -- The key is a media type or media type range and the value describes it.
339 -- For requests that match multiple keys, only the most specific key is applicable.
339 -- For requests that match multiple keys, only the most specific key is applicable.
340− -- e.g. @text/plain@ overrides @text/*@
340+ -- e.g. @text/plain@ overrides @text/\*@
341 , _requestBodyContent :: InsOrdHashMap MediaType MediaTypeObject
341 , _requestBodyContent :: InsOrdHashMap MediaType MediaTypeObject
342
342
343 -- | Determines if the request body is required in the request.
343 -- | Determines if the request body is required in the request.
@@ -394,7 +394,7 @@ data Encoding = Encoding
394 -- for other primitive types – @text/plain@; for object - @application/json@;
394 -- for other primitive types – @text/plain@; for object - @application/json@;
395 -- for array – the default is defined based on the inner type.
395 -- for array – the default is defined based on the inner type.
396 -- The value can be a specific media type (e.g. @application/json@),
396 -- The value can be a specific media type (e.g. @application/json@),
397− -- a wildcard media type (e.g. @image/*@), or a comma-separated list of the two types.
397+ -- a wildcard media type (e.g. @image/\*@), or a comma-separated list of the two types.
398 _encodingContentType :: Maybe MediaType
398 _encodingContentType :: Maybe MediaType
399
399
400 -- | A map allowing additional information to be provided as headers,
400 -- | A map allowing additional information to be provided as headers,
@@ -734,7 +734,7 @@ data Response = Response
734 -- | A map containing descriptions of potential response payloads.
734 -- | A map containing descriptions of potential response payloads.
735 -- The key is a media type or media type range and the value describes it.
735 -- The key is a media type or media type range and the value describes it.
736 -- For responses that match multiple keys, only the most specific key is applicable.
736 -- For responses that match multiple keys, only the most specific key is applicable.
737− -- e.g. @text/plain@ overrides @text/*@.
737+ -- e.g. @text/plain@ overrides @text/\*@.
738 , _responseContent :: InsOrdHashMap MediaType MediaTypeObject
738 , _responseContent :: InsOrdHashMap MediaType MediaTypeObject
739
739
740 -- | Maps a header name to its definition.
740 -- | Maps a header name to its definition.
@@ -1125,6 +1125,7 @@ instance SwaggerMonoid Response
1125instance SwaggerMonoid ExternalDocs
1125instance SwaggerMonoid ExternalDocs
1126instance SwaggerMonoid Operation
1126instance SwaggerMonoid Operation
1127instance (Eq a, Hashable a) => SwaggerMonoid (InsOrdHashSet a)
1127instance (Eq a, Hashable a) => SwaggerMonoid (InsOrdHashSet a)
1128+instance SwaggerMonoid SecurityDefinitions
1128
1129
1129instance SwaggerMonoid MimeList
1130instance SwaggerMonoid MimeList
1130deriving instance SwaggerMonoid URL
1131deriving instance SwaggerMonoid URL
@@ -1491,7 +1492,7 @@ instance FromJSON Param where
1491instance FromJSON Responses where
1492instance FromJSON Responses where
1492 parseJSON (Object o) = Responses
1493 parseJSON (Object o) = Responses
1493 <$> o .:? "default"
1494 <$> o .:? "default"
1494− <*> parseJSON (Object (HashMap.delete "default" o))
1495+ <*> parseJSON (Object (deleteKey "default" o))
1495 parseJSON _ = empty
1496 parseJSON _ = empty
1496
1497
1497instance FromJSON Example where
1498instance FromJSON Example where
@@ -1614,3 +1615,5 @@ instance AesonDefaultValue MimeList where defaultValue = Just mempty
1614instance AesonDefaultValue Info
1615instance AesonDefaultValue Info
1615instance AesonDefaultValue ParamLocation
1616instance AesonDefaultValue ParamLocation
1616instance AesonDefaultValue Link
1617instance AesonDefaultValue Link
1618+instance AesonDefaultValue SecurityDefinitions where
1619+ defaultValue = Just mempty
src/Data/OpenApi/Internal/AesonUtils.hs+11−10
@@ -36,18 +36,19 @@ import Data.Text (Text)
36import Generics.SOP
36import Generics.SOP
37
37
38import qualified Data.Text as T
38import qualified Data.Text as T
39−import qualified Data.HashMap.Strict as HM
40import qualified Data.Set as Set
39import qualified Data.Set as Set
41import qualified Data.HashMap.Strict.InsOrd as InsOrd
40import qualified Data.HashMap.Strict.InsOrd as InsOrd
42import qualified Data.HashSet.InsOrd as InsOrdHS
41import qualified Data.HashSet.InsOrd as InsOrdHS
43
42
43+import Data.OpenApi.Aeson.Compat (keyToString, objectToList, stringToKey)
44+
44-------------------------------------------------------------------------------
45-------------------------------------------------------------------------------
45-- SwaggerAesonOptions
46-- SwaggerAesonOptions
46-------------------------------------------------------------------------------
47-------------------------------------------------------------------------------
47
48
48data SwaggerAesonOptions = SwaggerAesonOptions
49data SwaggerAesonOptions = SwaggerAesonOptions
49 { _saoPrefix :: String
50 { _saoPrefix :: String
50− , _saoAdditionalPairs :: [(Text, Value)]
51+ , _saoAdditionalPairs :: [Pair]
51 , _saoSubObject :: Maybe String
52 , _saoSubObject :: Maybe String
52 }
53 }
53
54
@@ -154,14 +155,14 @@ sopSwaggerGenericToJSON'' (SwaggerAesonOptions prefix _ sub) = go
154 go Nil Nil Nil = []
155 go Nil Nil Nil = []
155 go (I x :* xs) (FieldInfo name :* names) (def :* defs)
156 go (I x :* xs) (FieldInfo name :* names) (def :* defs)
156 | Just name' == sub = case json of
157 | Just name' == sub = case json of
157− Object m -> HM.toList m ++ rest
158+ Object m -> objectToList m ++ rest
158 Null -> rest
159 Null -> rest
159 _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show json
160 _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show json
160 -- If default value: omit it.
161 -- If default value: omit it.
161 | Just x == def =
162 | Just x == def =
162 rest
163 rest
163 | otherwise =
164 | otherwise =
164− (T.pack name', json) : rest
165+ (stringToKey name', json) : rest
165 where
166 where
166 json = toJSON x
167 json = toJSON x
167 name' = fieldNameModifier name
168 name' = fieldNameModifier name
@@ -195,11 +196,11 @@ sopSwaggerGenericParseJSON = withObject "Swagger Record Object" $ \obj ->
195 proxy = Proxy :: Proxy a
196 proxy = Proxy :: Proxy a
196 opts = swaggerAesonOptions proxy
197 opts = swaggerAesonOptions proxy
197
198
198− parseAdditionalField :: Object -> (Text, Value) -> Parser ()
199+ parseAdditionalField :: Object -> Pair -> Parser ()
199 parseAdditionalField obj (k, v) = do
200 parseAdditionalField obj (k, v) = do
200 v' <- obj .: k
201 v' <- obj .: k
201 unless (v == v') $ fail $
202 unless (v == v') $ fail $
202− "Additonal field don't match for key " ++ T.unpack k
203+ "Additonal field don't match for key " ++ keyToString k
203 ++ ": " ++ show v
204 ++ ": " ++ show v
204 ++ " /= " ++ show v'
205 ++ " /= " ++ show v'
205
206
@@ -230,8 +231,8 @@ sopSwaggerGenericParseJSON'' (SwaggerAesonOptions prefix _ sub) obj = go
230 -- Note: we might strip fields of outer structure.
231 -- Note: we might strip fields of outer structure.
231 cons <$> (withDef $ parseJSON $ Object obj) <*> rest
232 cons <$> (withDef $ parseJSON $ Object obj) <*> rest
232 | otherwise = case def of
233 | otherwise = case def of
233− Just def' -> cons <$> obj .:? T.pack name' .!= def' <*> rest
234+ Just def' -> cons <$> obj .:? stringToKey name' .!= def' <*> rest
234− Nothing -> cons <$> obj .: T.pack name' <*> rest
235+ Nothing -> cons <$> obj .: stringToKey name' <*> rest
235 where
236 where
236 cons h t = I h :* t
237 cons h t = I h :* t
237 name' = fieldNameModifier name
238 name' = fieldNameModifier name
@@ -294,14 +295,14 @@ sopSwaggerGenericToEncoding'' (SwaggerAesonOptions prefix _ sub) = go
294 go Nil Nil Nil = mempty
295 go Nil Nil Nil = mempty
295 go (I x :* xs) (FieldInfo name :* names) (def :* defs)
296 go (I x :* xs) (FieldInfo name :* names) (def :* defs)
296 | Just name' == sub = case toJSON x of
297 | Just name' == sub = case toJSON x of
297− Object m -> pairsToSeries (HM.toList m) <> rest
298+ Object m -> pairsToSeries (objectToList m) <> rest
298 Null -> rest
299 Null -> rest
299 _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show (toJSON x)
300 _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show (toJSON x)
300 -- If default value: omit it.
301 -- If default value: omit it.
301 | Just x == def =
302 | Just x == def =
302 rest
303 rest
303 | otherwise =
304 | otherwise =
304− (T.pack name' .= x) <> rest
305+ (stringToKey name' .= x) <> rest
305 where
306 where
306 name' = fieldNameModifier name
307 name' = fieldNameModifier name
307 rest = go xs names defs
308 rest = go xs names defs
src/Data/OpenApi/Internal/ParamSchema.hs+2−2
@@ -216,9 +216,9 @@ instance ToParamSchema LocalTime where
216
216
217-- |
217-- |
218-- >>> toParamSchema (Proxy :: Proxy ZonedTime) ^. format
218-- >>> toParamSchema (Proxy :: Proxy ZonedTime) ^. format
219−-- Just "yyyy-mm-ddThh:MM:ss+hhMM"
219+-- Just "date-time"
220instance ToParamSchema ZonedTime where
220instance ToParamSchema ZonedTime where
221− toParamSchema _ = timeParamSchema "yyyy-mm-ddThh:MM:ss+hhMM"
221+ toParamSchema _ = timeParamSchema "date-time"
222
222
223-- |
223-- |
224-- >>> toParamSchema (Proxy :: Proxy UTCTime) ^. format
224-- >>> toParamSchema (Proxy :: Proxy UTCTime) ^. format
src/Data/OpenApi/Internal/Schema.hs+57−81
@@ -64,13 +64,14 @@ import GHC.Generics
64import qualified Data.UUID.Types as UUID
64import qualified Data.UUID.Types as UUID
65import Type.Reflection (Typeable, typeRep)
65import Type.Reflection (Typeable, typeRep)
66
66
67+import Data.OpenApi.Aeson.Compat (keyToText, objectKeys, toInsOrdHashMap)
67−import Data.OpenApi.Declare
68+import Data.OpenApi.Declare
68−import Data.OpenApi.Internal
69+import Data.OpenApi.Internal
69−import Data.OpenApi.Internal.ParamSchema (ToParamSchema(..))
70+import Data.OpenApi.Internal.ParamSchema (ToParamSchema (..))
71+import Data.OpenApi.Internal.TypeShape
70−import Data.OpenApi.Lens hiding (name, schema)
72+import Data.OpenApi.Lens hiding (name, schema)
71−import qualified Data.OpenApi.Lens as Swagger
73+import qualified Data.OpenApi.Lens as Swagger
72−import Data.OpenApi.SchemaOptions
74+import Data.OpenApi.SchemaOptions
73−import Data.OpenApi.Internal.TypeShape
74
75
75import qualified Data.ByteString as BS
76import qualified Data.ByteString as BS
76import qualified Data.ByteString.Lazy.Char8 as BSL
77import qualified Data.ByteString.Lazy.Char8 as BSL
@@ -135,13 +136,13 @@ rename name (NamedSchema _ schema) = NamedSchema name schema
135--
136--
136-- instance ToSchema Coord
137-- instance ToSchema Coord
137-- @
138-- @
138−class ToSchema a where
139+class Typeable a => ToSchema a where
139 -- | Convert a type into an optionally named schema
140 -- | Convert a type into an optionally named schema
140 -- together with all used definitions.
141 -- together with all used definitions.
141 -- Note that the schema itself is included in definitions
142 -- Note that the schema itself is included in definitions
142 -- only if it is recursive (and thus needs its definition in scope).
143 -- only if it is recursive (and thus needs its definition in scope).
143 declareNamedSchema :: Proxy a -> Declare (Definitions Schema) NamedSchema
144 declareNamedSchema :: Proxy a -> Declare (Definitions Schema) NamedSchema
144− default declareNamedSchema :: (Generic a, GToSchema (Rep a), Typeable a) =>
145+ default declareNamedSchema :: (Generic a, GToSchema (Rep a)) =>
145 Proxy a -> Declare (Definitions Schema) NamedSchema
146 Proxy a -> Declare (Definitions Schema) NamedSchema
146 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
147 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
147
148
@@ -405,8 +406,8 @@ sketchSchema = sketch . toJSON
405 _ -> Nothing
406 _ -> Nothing
406 go (Object o) = mempty
407 go (Object o) = mempty
407 & type_ ?~ OpenApiObject
408 & type_ ?~ OpenApiObject
408− & required .~ sort (HashMap.keys o)
409+ & required .~ sort (objectKeys o)
409− & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
410+ & properties .~ fmap (Inline . go) (toInsOrdHashMap o)
410
411
411-- | Make a restrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
412-- | Make a restrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
412-- Produced schema uses as much constraints as possible.
413-- Produced schema uses as much constraints as possible.
@@ -570,12 +571,12 @@ sketchStrictSchema = go . toJSON
570 go js@(Object o) = mempty
571 go js@(Object o) = mempty
571 & type_ ?~ OpenApiObject
572 & type_ ?~ OpenApiObject
572 & required .~ sort names
573 & required .~ sort names
573− & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
574+ & properties .~ fmap (Inline . go) (toInsOrdHashMap o)
574 & maxProperties ?~ fromIntegral (length names)
575 & maxProperties ?~ fromIntegral (length names)
575 & minProperties ?~ fromIntegral (length names)
576 & minProperties ?~ fromIntegral (length names)
576 & enum_ ?~ [js]
577 & enum_ ?~ [js]
577 where
578 where
578− names = HashMap.keys o
579+ names = objectKeys o
579
580
580class GToSchema (f :: * -> *) where
581class GToSchema (f :: * -> *) where
581 gdeclareNamedSchema :: SchemaOptions -> Proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
582 gdeclareNamedSchema :: SchemaOptions -> Proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
@@ -610,12 +611,12 @@ instance ToSchema Scientific where declareNamedSchema = plain . paramSchemaToSc
610instance ToSchema Double where declareNamedSchema = plain . paramSchemaToSchema
611instance ToSchema Double where declareNamedSchema = plain . paramSchemaToSchema
611instance ToSchema Float where declareNamedSchema = plain . paramSchemaToSchema
612instance ToSchema Float where declareNamedSchema = plain . paramSchemaToSchema
612
613
613−instance HasResolution a => ToSchema (Fixed a) where declareNamedSchema = plain . paramSchemaToSchema
614+instance (Typeable (Fixed a), HasResolution a) => ToSchema (Fixed a) where declareNamedSchema = plain . paramSchemaToSchema
614
615
615instance ToSchema a => ToSchema (Maybe a) where
616instance ToSchema a => ToSchema (Maybe a) where
616 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a)
617 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a)
617
618
618−instance (ToSchema a, ToSchema b, Typeable a, Typeable b) => ToSchema (Either a b) where
619+instance (ToSchema a, ToSchema b) => ToSchema (Either a b) where
619 -- To match Aeson instance
620 -- To match Aeson instance
620 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions { sumEncoding = ObjectWithSingleField }
621 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions { sumEncoding = ObjectWithSingleField }
621
622
@@ -627,12 +628,18 @@ instance ToSchema UUID.UUID where
627 declareNamedSchema p = pure $ named "UUID" $ paramSchemaToSchema p
628 declareNamedSchema p = pure $ named "UUID" $ paramSchemaToSchema p
628 & example ?~ toJSON (UUID.toText UUID.nil)
629 & example ?~ toJSON (UUID.toText UUID.nil)
629
630
630−instance (ToSchema a, Typeable a, ToSchema b, Typeable b) => ToSchema (a, b)
631+instance (ToSchema a, ToSchema b) => ToSchema (a, b) where
632+ declareNamedSchema = fmap unname . genericDeclareNamedSchema defaultSchemaOptions
631−instance (ToSchema a, Typeable a, ToSchema b, Typeable b, ToSchema c, Typeable c) => ToSchema (a, b, c)
633+instance (ToSchema a, ToSchema b, ToSchema c) => ToSchema (a, b, c) where
632−instance (ToSchema a, Typeable a, ToSchema b, Typeable b, ToSchema c, Typeable c, ToSchema d, Typeable d) => ToSchema (a, b, c, d)
634+ declareNamedSchema = fmap unname . genericDeclareNamedSchema defaultSchemaOptions
635+instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d) => ToSchema (a, b, c, d) where
636+ declareNamedSchema = fmap unname . genericDeclareNamedSchema defaultSchemaOptions
633−instance (ToSchema a, Typeable a, ToSchema b, Typeable b, ToSchema c, Typeable c, ToSchema d, Typeable d, ToSchema e, Typeable e) => ToSchema (a, b, c, d, e)
637+instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d, ToSchema e) => ToSchema (a, b, c, d, e) where
638+ declareNamedSchema = fmap unname . genericDeclareNamedSchema defaultSchemaOptions
634−instance (ToSchema a, Typeable a, ToSchema b, Typeable b, ToSchema c, Typeable c, ToSchema d, Typeable d, ToSchema e, Typeable e, ToSchema f, Typeable f) => ToSchema (a, b, c, d, e, f)
639+instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d, ToSchema e, ToSchema f) => ToSchema (a, b, c, d, e, f) where
640+ declareNamedSchema = fmap unname . genericDeclareNamedSchema defaultSchemaOptions
635−instance (ToSchema a, Typeable a, ToSchema b, Typeable b, ToSchema c, Typeable c, ToSchema d, Typeable d, ToSchema e, Typeable e, ToSchema f, Typeable f, ToSchema g, Typeable g) => ToSchema (a, b, c, d, e, f, g)
641+instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d, ToSchema e, ToSchema f, ToSchema g) => ToSchema (a, b, c, d, e, f, g) where
642+ declareNamedSchema = fmap unname . genericDeclareNamedSchema defaultSchemaOptions
636
643
637timeSchema :: T.Text -> Schema
644timeSchema :: T.Text -> Schema
638timeSchema fmt = mempty
645timeSchema fmt = mempty
@@ -651,7 +658,7 @@ instance ToSchema LocalTime where
651 declareNamedSchema _ = pure $ named "LocalTime" $ timeSchema "yyyy-mm-ddThh:MM:ss"
658 declareNamedSchema _ = pure $ named "LocalTime" $ timeSchema "yyyy-mm-ddThh:MM:ss"
652 & example ?~ toJSON (LocalTime (fromGregorian 2016 7 22) (TimeOfDay 7 40 0))
659 & example ?~ toJSON (LocalTime (fromGregorian 2016 7 22) (TimeOfDay 7 40 0))
653
660
654−-- | Format @"date"@ corresponds to @yyyy-mm-ddThh:MM:ss(Z|+hh:MM)@ format.
661+-- | Format @"date-time"@ corresponds to @yyyy-mm-ddThh:MM:ss(Z|+hh:MM)@ format.
655instance ToSchema ZonedTime where
662instance ToSchema ZonedTime where
656 declareNamedSchema _ = pure $ named "ZonedTime" $ timeSchema "date-time"
663 declareNamedSchema _ = pure $ named "ZonedTime" $ timeSchema "date-time"
657 & example ?~ toJSON (ZonedTime (LocalTime (fromGregorian 2016 7 22) (TimeOfDay 7 40 0)) (hoursToTimeZone 3))
664 & example ?~ toJSON (ZonedTime (LocalTime (fromGregorian 2016 7 22) (TimeOfDay 7 40 0)) (hoursToTimeZone 3))
@@ -683,10 +690,10 @@ instance ToSchemaByteStringError BSL.ByteString => ToSchema BSL.ByteString where
683instance ToSchema IntSet where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Set Int))
690instance ToSchema IntSet where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Set Int))
684
691
685-- | NOTE: This schema does not account for the uniqueness of keys.
692-- | NOTE: This schema does not account for the uniqueness of keys.
686−instance (ToSchema a, Typeable a) => ToSchema (IntMap a) where
693+instance (ToSchema a) => ToSchema (IntMap a) where
687 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy [(Int, a)])
694 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy [(Int, a)])
688
695
689−instance (ToJSONKey k, ToSchema k, ToSchema v, Typeable k, Typeable v) => ToSchema (Map k v) where
696+instance (ToJSONKey k, ToSchema k, ToSchema v) => ToSchema (Map k v) where
690 declareNamedSchema _ = case toJSONKey :: ToJSONKeyFunction k of
697 declareNamedSchema _ = case toJSONKey :: ToJSONKeyFunction k of
691 ToJSONKeyText _ _ -> declareObjectMapSchema
698 ToJSONKeyText _ _ -> declareObjectMapSchema
692 ToJSONKeyValue _ _ -> declareNamedSchema (Proxy :: Proxy [(k, v)])
699 ToJSONKeyValue _ _ -> declareNamedSchema (Proxy :: Proxy [(k, v)])
@@ -697,7 +704,7 @@ instance (ToJSONKey k, ToSchema k, ToSchema v, Typeable k, Typeable v) => ToSche
697 & type_ ?~ OpenApiObject
704 & type_ ?~ OpenApiObject
698 & additionalProperties ?~ AdditionalPropertiesSchema schema
705 & additionalProperties ?~ AdditionalPropertiesSchema schema
699
706
700−instance (ToJSONKey k, ToSchema k, ToSchema v, Typeable k, Typeable v) => ToSchema (HashMap k v) where
707+instance (ToJSONKey k, ToSchema k, ToSchema v) => ToSchema (HashMap k v) where
701 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Map k v))
708 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Map k v))
702
709
703instance {-# OVERLAPPING #-} ToSchema Object where
710instance {-# OVERLAPPING #-} ToSchema Object where
@@ -801,16 +808,16 @@ genericDeclareNamedSchemaNewtype opts f proxy = genericNameSchema opts proxy <$>
801-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
808-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
802-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
809-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
803declareSchemaBoundedEnumKeyMapping :: forall map key value.
810declareSchemaBoundedEnumKeyMapping :: forall map key value.
804− (Bounded key, Enum key, ToJSONKey key, ToSchema key, ToSchema value, Typeable key, Typeable value)
811+ (Bounded key, Enum key, ToJSONKey key, ToSchema key, ToSchema value)
805 => Proxy (map key value) -> Declare (Definitions Schema) Schema
812 => Proxy (map key value) -> Declare (Definitions Schema) Schema
806declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key of
813declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key of
807− ToJSONKeyText keyToText _ -> objectSchema keyToText
814+ ToJSONKeyText getKey _ -> objectSchema getKey
808 ToJSONKeyValue _ _ -> declareSchema (Proxy :: Proxy [(key, value)])
815 ToJSONKeyValue _ _ -> declareSchema (Proxy :: Proxy [(key, value)])
809 where
816 where
810− objectSchema keyToText = do
817+ objectSchema getKey = do
811 valueRef <- declareSchemaRef (Proxy :: Proxy value)
818 valueRef <- declareSchemaRef (Proxy :: Proxy value)
812 let allKeys = [minBound..maxBound :: key]
819 let allKeys = [minBound..maxBound :: key]
813− mkPair k = (keyToText k, valueRef)
820+ mkPair k = (keyToText $ getKey k, valueRef)
814 return $ mempty
821 return $ mempty
815 & type_ ?~ OpenApiObject
822 & type_ ?~ OpenApiObject
816 & properties .~ InsOrdHashMap.fromList (map mkPair allKeys)
823 & properties .~ InsOrdHashMap.fromList (map mkPair allKeys)
@@ -848,7 +855,7 @@ declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key o
848-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
855-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
849-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
856-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
850toSchemaBoundedEnumKeyMapping :: forall map key value.
857toSchemaBoundedEnumKeyMapping :: forall map key value.
851− (Bounded key, Enum key, ToJSONKey key, ToSchema key, ToSchema value, Typeable key, Typeable value)
858+ (Bounded key, Enum key, ToJSONKey key, ToSchema key, ToSchema value)
852 => Proxy (map key value) -> Schema
859 => Proxy (map key value) -> Schema
853toSchemaBoundedEnumKeyMapping = flip evalDeclare mempty . declareSchemaBoundedEnumKeyMapping
860toSchemaBoundedEnumKeyMapping = flip evalDeclare mempty . declareSchemaBoundedEnumKeyMapping
854
861
@@ -861,11 +868,23 @@ genericDeclareSchema opts proxy = _namedSchemaSchema <$> genericDeclareNamedSche
861-- This function applied to @'defaultSchemaOptions'@
868-- This function applied to @'defaultSchemaOptions'@
862-- is used as the default for @'declareNamedSchema'@
869-- is used as the default for @'declareNamedSchema'@
863-- when the type is an instance of @'Generic'@.
870-- when the type is an instance of @'Generic'@.
871+--
872+-- Default implementation will use the name from 'Typeable' instance, including concrete
873+-- instantioations of type variables.
874+--
875+-- For example:
876+--
877+-- >>> _namedSchemaName $ undeclare $ genericDeclareNamedSchema defaultSchemaOptions (Proxy :: Proxy (Either Int Bool))
878+-- Just "Either_Int_Bool"
864genericDeclareNamedSchema :: forall a. (Generic a, GToSchema (Rep a), Typeable a) =>
879genericDeclareNamedSchema :: forall a. (Generic a, GToSchema (Rep a), Typeable a) =>
865 SchemaOptions -> Proxy a -> Declare (Definitions Schema) NamedSchema
880 SchemaOptions -> Proxy a -> Declare (Definitions Schema) NamedSchema
866genericDeclareNamedSchema opts _ =
881genericDeclareNamedSchema opts _ =
867− rename (Just tName) <$> gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
882+ rename (Just $ T.pack name) <$> gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
868− where tName = T.replace " " "_" $ T.pack $ show $ typeRep @a
883+ where
884+ unspace ' ' = '_'
885+ unspace x = x
886+ orig = fmap unspace $ show $ typeRep @a
887+ name = datatypeNameModifier opts orig
869
888
870
889
871-- | Derive a 'Generic'-based name for a datatype and assign it to a given 'Schema'.
890-- | Derive a 'Generic'-based name for a datatype and assign it to a given 'Schema'.
@@ -926,10 +945,13 @@ instance (Selector s, GToSchema f, GToSchema (S1 s f)) => GToSchema (C1 c (S1 s
926 case schema ^. items of
945 case schema ^. items of
927 Just (OpenApiItemsArray [_]) -> fieldSchema
946 Just (OpenApiItemsArray [_]) -> fieldSchema
928 _ -> do
947 _ -> do
929− declare defs
948+ -- We have to run recordSchema instead of just using its defs,
949+ -- since those can be recursive and will lead to infinite loop,
950+ -- see https://github.com/biocad/openapi3/pull/37
951+ NamedSchema _ schema' <- recordSchema
930− return (unnamed schema)
952+ return (unnamed schema')
931 where
953 where
932− (defs, NamedSchema _ schema) = runDeclare recordSchema mempty
954+ (_, NamedSchema _ schema) = runDeclare recordSchema mempty
933 recordSchema = gdeclareNamedSchema opts (Proxy :: Proxy (S1 s f)) s
955 recordSchema = gdeclareNamedSchema opts (Proxy :: Proxy (S1 s f)) s
934 fieldSchema = gdeclareNamedSchema opts (Proxy :: Proxy f) s
956 fieldSchema = gdeclareNamedSchema opts (Proxy :: Proxy f) s
935
957
@@ -1092,49 +1114,6 @@ data Proxy2 a b = Proxy2
1092
1114
1093data Proxy3 a b c = Proxy3
1115data Proxy3 a b c = Proxy3
1094
1116
1095−-- | This class allows to generate schemas for polymorphic types (of kind @Type -> Type@).
1096−--
1097−-- Intended usage:
1098−--
1099−-- >>> data Foo a = Foo { foo :: a, bar :: Int } deriving (Eq, Show, Generic, ToSchema1)
1100−-- >>> :{
1101−-- instance (ToSchema a, Typeable a) => ToSchema (Foo a) where
1102−-- declareNamedSchema _ = declareNamedSchema @(BySchema1 Foo a) Proxy
1103−-- :}
1104−--
1105−-- >>> toNamedSchema @(Foo Int) Proxy ^. name
1106−-- Just "Foo_Int"
1107−-- >>> toNamedSchema @(Foo Bool) Proxy ^. name
1108−-- Just "Foo_Bool"
1109−-- >>> toNamedSchema @(Foo (Foo T.Text)) Proxy ^. name
1110−-- Just "Foo_(Foo_Text)"
1111−class ToSchema1 (f :: * -> *) where
1112− declareNamedSchema1 :: (Generic (f a), GToSchema (Rep (f a)), ToSchema a) => Proxy f -> Proxy a -> Declare (Definitions Schema) NamedSchema
1113−
1114− -- It would be cleaner to have GToSchema constraint only on default signature and not in the class method
1115− -- above, however sadly GHC does not like it.
1116− default declareNamedSchema1 :: forall a. (ToSchema a, Generic (f a), GToSchema (Rep (f a)), Typeable (f a)) => Proxy f -> Proxy a -> Declare (Definitions Schema) NamedSchema
1117− declareNamedSchema1 _ _ = genericDeclareNamedSchema @(f a) defaultSchemaOptions Proxy
1118−
1119−{- | For GHC 8.6+ it's more convenient to use @DerivingVia@ to derive instances of 'ToSchema'
1120−using 'ToSchema1' instance, like this:
1121−
1122−#if __GLASGOW_HASKELL__ >= 806
1123−>>> data Foo a = Foo { foo :: a, bar :: Int } deriving (Eq, Show, Generic, ToSchema1)
1124−>>> deriving via BySchema1 Foo a instance (ToSchema a, Typeable a) => ToSchema (Foo a)
1125−#else
1126−> data Foo a = Foo { foo :: a, bar :: Int } deriving (Eq, Show, Generic, ToSchema1)
1127−> deriving via BySchema1 Foo a instance (ToSchema a, Typeable a) => ToSchema (Foo a)
1128−#endif
1129−-}
1130−newtype BySchema1 f a = BySchema1 (f a)
1131−
1132−instance (ToSchema1 f, Generic (f a), GToSchema (Rep (f a)), Typeable (f a), ToSchema a) => ToSchema (BySchema1 f a) where
1133− declareNamedSchema _ = do
1134− sch <- declareNamedSchema1 @f @a Proxy Proxy
1135− let tName = T.replace " " "_" $ T.pack $ show $ typeRep @(f a)
1136− return $ rename (Just tName) sch
1137−
1138{- $setup
1117{- $setup
1139>>> import Data.OpenApi
1118>>> import Data.OpenApi
1140>>> import Data.Aeson (encode)
1119>>> import Data.Aeson (encode)
@@ -1144,7 +1123,4 @@ instance (ToSchema1 f, Generic (f a), GToSchema (Rep (f a)), Typeable (f a), ToS
1144>>> :set -XDeriveAnyClass
1123>>> :set -XDeriveAnyClass
1145>>> :set -XStandaloneDeriving
1124>>> :set -XStandaloneDeriving
1146>>> :set -XTypeApplications
1125>>> :set -XTypeApplications
1147−#if __GLASGOW_HASKELL__ >= 806
1148−>>> :set -XDerivingVia
1149−#endif
1150-}
1126-}
src/Data/OpenApi/Internal/Schema/Validation.hs+23−11
@@ -1,4 +1,5 @@
1{-# OPTIONS_GHC -Wall #-}
1{-# OPTIONS_GHC -Wall #-}
2+{-# LANGUAGE CPP #-}
2{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE DeriveFunctor #-}
4{-# LANGUAGE DeriveFunctor #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
@@ -31,10 +32,14 @@ import Control.Lens hiding (allOf)
31import Control.Monad (forM, forM_, when)
32import Control.Monad (forM, forM_, when)
32
33
33import Data.Aeson hiding (Result)
34import Data.Aeson hiding (Result)
35+#if MIN_VERSION_aeson(2,0,0)
36+import qualified Data.Aeson.KeyMap as KeyMap
37+#endif
34import Data.Foldable (for_, sequenceA_,
38import Data.Foldable (for_, sequenceA_,
35 traverse_)
39 traverse_)
40+#if !MIN_VERSION_aeson(2,0,0)
36import Data.HashMap.Strict (HashMap)
41import Data.HashMap.Strict (HashMap)
37−import qualified Data.HashMap.Strict as HashMap
42+#endif
38import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
43import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
39import qualified "unordered-containers" Data.HashSet as HashSet
44import qualified "unordered-containers" Data.HashSet as HashSet
40import Data.Maybe (fromMaybe)
45import Data.Maybe (fromMaybe)
@@ -47,11 +52,12 @@ import qualified Data.Text.Lazy.Encoding as TL
47import Data.Vector (Vector)
52import Data.Vector (Vector)
48import qualified Data.Vector as Vector
53import qualified Data.Vector as Vector
49
54
55+import Data.OpenApi.Aeson.Compat (hasKey, keyToText, lookupKey, objectToList)
50−import Data.OpenApi.Declare
56+import Data.OpenApi.Declare
57+import Data.OpenApi.Internal
51−import Data.OpenApi.Internal
58+import Data.OpenApi.Internal.Schema
52−import Data.OpenApi.Internal.Utils
59+import Data.OpenApi.Internal.Utils
53−import Data.OpenApi.Internal.Schema
54−import Data.OpenApi.Lens
60+import Data.OpenApi.Lens
55
61
56-- | Validate @'ToJSON'@ instance matches @'ToSchema'@ for a given value.
62-- | Validate @'ToJSON'@ instance matches @'ToSchema'@ for a given value.
57-- This can be used with QuickCheck to ensure those instances are coherent:
63-- This can be used with QuickCheck to ensure those instances are coherent:
@@ -366,10 +372,16 @@ validateArray xs = do
366 len = Vector.length xs
372 len = Vector.length xs
367 allUnique = len == HashSet.size (HashSet.fromList (Vector.toList xs))
373 allUnique = len == HashSet.size (HashSet.fromList (Vector.toList xs))
368
374
375+validateObject ::
376+#if MIN_VERSION_aeson(2,0,0)
377+ KeyMap.KeyMap Value
378+#else
369−validateObject :: HashMap Text Value -> Validation Schema ()
379+ HashMap Text Value
380+#endif
381+ -> Validation Schema ()
370validateObject o = withSchema $ \sch ->
382validateObject o = withSchema $ \sch ->
371 case sch ^. discriminator of
383 case sch ^. discriminator of
372− Just (Discriminator pname types) -> case fromJSON <$> HashMap.lookup pname o of
384+ Just (Discriminator pname types) -> case fromJSON <$> lookupKey pname o of
373 Just (Success pvalue) ->
385 Just (Success pvalue) ->
374 let ref = fromMaybe pvalue $ InsOrdHashMap.lookup pvalue types
386 let ref = fromMaybe pvalue $ InsOrdHashMap.lookup pvalue types
375 -- TODO ref may be name or reference
387 -- TODO ref may be name or reference
@@ -388,15 +400,15 @@ validateObject o = withSchema $ \sch ->
388 validateRequired
400 validateRequired
389 validateProps
401 validateProps
390 where
402 where
391− size = fromIntegral (HashMap.size o)
403+ size = fromIntegral (length o)
392
404
393 validateRequired = withSchema $ \sch -> traverse_ validateReq (sch ^. required)
405 validateRequired = withSchema $ \sch -> traverse_ validateReq (sch ^. required)
394 validateReq n =
406 validateReq n =
395− when (not (HashMap.member n o)) $
407+ when (not (hasKey n o)) $
396 invalid ("property " ++ show n ++ " is required, but not found in " ++ show (encode o))
408 invalid ("property " ++ show n ++ " is required, but not found in " ++ show (encode o))
397
409
398 validateProps = withSchema $ \sch -> do
410 validateProps = withSchema $ \sch -> do
399− for_ (HashMap.toList o) $ \(k, v) ->
411+ for_ (objectToList o) $ \(keyToText -> k, v) ->
400 case v of
412 case v of
401 Null | not (k `elem` (sch ^. required)) -> valid -- null is fine for non-required property
413 Null | not (k `elem` (sch ^. required)) -> valid -- null is fine for non-required property
402 _ ->
414 _ ->
src/Data/OpenApi/Schema.hs−2
@@ -13,8 +13,6 @@ module Data.OpenApi.Schema (
13 toSchemaRef,
13 toSchemaRef,
14 schemaName,
14 schemaName,
15 toInlinedSchema,
15 toInlinedSchema,
16− ToSchema1(..),
17− BySchema1(..),
18
16
19 -- * Generic schema encoding
17 -- * Generic schema encoding
20 genericDeclareNamedSchema,
18 genericDeclareNamedSchema,
src/Data/OpenApi/Schema/Generator.hs+3−1
@@ -25,6 +25,8 @@ import Test.QuickCheck (arbitrary)
25import Test.QuickCheck.Gen
25import Test.QuickCheck.Gen
26import Test.QuickCheck.Property
26import Test.QuickCheck.Property
27
27
28+import Data.OpenApi.Aeson.Compat (fromInsOrdHashMap)
29+
28-- | Note: 'schemaGen' may 'error', if schema type is not specified,
30-- | Note: 'schemaGen' may 'error', if schema type is not specified,
29-- and cannot be inferred.
31-- and cannot be inferred.
30schemaGen :: Definitions Schema -> Schema -> Gen Value
32schemaGen :: Definitions Schema -> Schema -> Gen Value
@@ -95,7 +97,7 @@ schemaGen defns schema =
95 return . M.fromList $ zip additionalKeys (repeat . schemaGen defns $ dereference defns addlSchema)
97 return . M.fromList $ zip additionalKeys (repeat . schemaGen defns $ dereference defns addlSchema)
96 _ -> return []
98 _ -> return []
97 x <- sequence $ gens <> additionalGens
99 x <- sequence $ gens <> additionalGens
98− return . Object $ M.toHashMap x
100+ return . Object $ fromInsOrdHashMap x
99
101
100dereference :: Definitions a -> Referenced a -> a
102dereference :: Definitions a -> Referenced a -> a
101dereference _ (Inline a) = a
103dereference _ (Inline a) = a
stack.yaml+1−1
@@ -1,4 +1,4 @@
1−resolver: lts-16.8
1+resolver: lts-16.31
2packages:
2packages:
3- '.'
3- '.'
4extra-deps:
4extra-deps:
test/Data/OpenApi/CommonTestTypes.hs+123
@@ -863,6 +863,129 @@ singleMaybeFieldSchemaJSON = [aesonQQ|
863}
863}
864|]
864|]
865
865
866+-- ========================================================================
867+-- Natural Language (single field data with recursive fields)
868+-- ========================================================================
869+
870+data Predicate
871+ = PredicateNoun Noun
872+ | PredicateOmitted Omitted
873+ deriving (Eq, Ord, Read, Show, Generic)
874+instance ToJSON Predicate
875+instance ToSchema Predicate
876+
877+data Noun
878+ = Noun
879+ { nounSurf :: LangWord
880+ , nounModify :: [Modifier]
881+ }
882+ deriving (Eq, Ord, Read, Show, Generic)
883+instance ToJSON Noun
884+instance ToSchema Noun
885+
886+data LangWord
887+ = LangWord
888+ { langWordSurf :: String
889+ , langWordBase :: String
890+ }
891+ deriving (Eq, Ord, Read, Show, Generic)
892+instance ToJSON LangWord
893+instance ToSchema LangWord
894+
895+data Modifier
896+ = ModifierNoun Noun
897+ | ModifierOmitted Omitted
898+ deriving (Eq, Ord, Read, Show, Generic)
899+instance ToJSON Modifier
900+instance ToSchema Modifier
901+
902+newtype Omitted
903+ = Omitted
904+ { omittedModify :: [Modifier]
905+ }
906+ deriving (Eq, Ord, Read, Show, Generic)
907+instance ToJSON Omitted
908+instance ToSchema Omitted
909+
910+predicateSchemaDeclareJSON :: Value
911+predicateSchemaDeclareJSON = [aesonQQ|
912+[
913+ {
914+ "Predicate": {
915+ "oneOf": [
916+ {
917+ "properties": {
918+ "contents": { "$ref": "#/components/schemas/Noun" },
919+ "tag": { "enum": ["PredicateNoun"], "type": "string" }
920+ },
921+ "required": ["tag", "contents"],
922+ "type": "object"
923+ },
924+ {
925+ "properties": {
926+ "contents": { "$ref": "#/components/schemas/Omitted" },
927+ "tag": { "enum": ["PredicateOmitted"], "type": "string" }
928+ },
929+ "required": ["tag", "contents"],
930+ "type": "object"
931+ }
932+ ],
933+ "type": "object"
934+ },
935+ "Noun": {
936+ "properties": {
937+ "nounModify": {
938+ "items": { "$ref": "#/components/schemas/Modifier" },
939+ "type": "array"
940+ },
941+ "nounSurf": { "$ref": "#/components/schemas/LangWord" }
942+ },
943+ "required": ["nounSurf", "nounModify"],
944+ "type": "object"
945+ },
946+ "LangWord": {
947+ "properties": {
948+ "langWordBase": { "type": "string" },
949+ "langWordSurf": { "type": "string" }
950+ },
951+ "required": ["langWordSurf", "langWordBase"],
952+ "type": "object"
953+ },
954+ "Modifier": {
955+ "oneOf": [
956+ {
957+ "properties": {
958+ "contents": { "$ref": "#/components/schemas/Noun" },
959+ "tag": { "enum": ["ModifierNoun"], "type": "string" }
960+ },
961+ "required": ["tag", "contents"],
962+ "type": "object"
963+ },
964+ {
965+ "properties": {
966+ "contents": { "$ref": "#/components/schemas/Omitted" },
967+ "tag": { "enum": ["ModifierOmitted"], "type": "string" }
968+ },
969+ "required": ["tag", "contents"],
970+ "type": "object"
971+ }
972+ ],
973+ "type": "object"
974+ },
975+ "Omitted": {
976+ "properties": {
977+ "omittedModify": {
978+ "items": { "$ref": "#/components/schemas/Modifier" },
979+ "type": "array"
980+ }
981+ },
982+ "required": ["omittedModify"],
983+ "type": "object"
984+ }
985+ },
986+ { "$ref": "#/components/schemas/Predicate" }
987+]
988+|]
866
989
867-- ========================================================================
990-- ========================================================================
868-- TimeOfDay
991-- TimeOfDay
test/Data/OpenApi/Schema/ValidationSpec.hs+20−3
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE PackageImports #-}
3{-# LANGUAGE PackageImports #-}
3{-# LANGUAGE RecordWildCards #-}
4{-# LANGUAGE RecordWildCards #-}
@@ -8,6 +9,10 @@ module Data.OpenApi.Schema.ValidationSpec where
8import Control.Applicative
9import Control.Applicative
9import Control.Lens ((&), (.~), (?~))
10import Control.Lens ((&), (.~), (?~))
10import Data.Aeson
11import Data.Aeson
12+#if MIN_VERSION_aeson(2,0,0)
13+import qualified Data.Aeson.Key as Key
14+import qualified Data.Aeson.KeyMap as KeyMap
15+#endif
11import Data.Aeson.Types
16import Data.Aeson.Types
12import Data.Hashable (Hashable)
17import Data.Hashable (Hashable)
13import Data.HashMap.Strict (HashMap)
18import Data.HashMap.Strict (HashMap)
@@ -30,6 +35,7 @@ import GHC.Generics
30
35
31import Data.OpenApi
36import Data.OpenApi
32import Data.OpenApi.Declare
37import Data.OpenApi.Declare
38+import Data.OpenApi.Aeson.Compat (stringToKey)
33
39
34import Test.Hspec
40import Test.Hspec
35import Test.Hspec.QuickCheck
41import Test.Hspec.QuickCheck
@@ -124,9 +130,9 @@ instance Arbitrary Person where
124
130
125invalidPersonToJSON :: Person -> Value
131invalidPersonToJSON :: Person -> Value
126invalidPersonToJSON Person{..} = object
132invalidPersonToJSON Person{..} = object
127− [ T.pack "personName" .= toJSON name
133+ [ stringToKey "personName" .= toJSON name
128− , T.pack "personPhone" .= toJSON phone
134+ , stringToKey "personPhone" .= toJSON phone
129− , T.pack "personEmail" .= toJSON email
135+ , stringToKey "personEmail" .= toJSON email
130 ]
136 ]
131
137
132-- ========================================================================
138-- ========================================================================
@@ -295,6 +301,8 @@ instance Eq ZonedTime where
295-- Arbitrary instance for Data.Aeson.Value
301-- Arbitrary instance for Data.Aeson.Value
296-- ========================================================================
302-- ========================================================================
297
303
304+-- These instances were introduces in aeson upstream in 2.0.3
305+#if !MIN_VERSION_aeson(2,0,3)
298instance Arbitrary Value where
306instance Arbitrary Value where
299 -- Weights are almost random
307 -- Weights are almost random
300 -- Uniform oneof tends not to build complex objects cause of recursive call.
308 -- Uniform oneof tends not to build complex objects cause of recursive call.
@@ -305,3 +313,12 @@ instance Arbitrary Value where
305 , (3, Number <$> arbitrary)
313 , (3, Number <$> arbitrary)
306 , (3, Bool <$> arbitrary)
314 , (3, Bool <$> arbitrary)
307 , (1, return Null) ]
315 , (1, return Null) ]
316+
317+#if MIN_VERSION_aeson(2,0,0)
318+instance Arbitrary v => Arbitrary (KeyMap.KeyMap v) where
319+ arbitrary = KeyMap.fromList <$> arbitrary
320+
321+instance Arbitrary Key.Key where
322+ arbitrary = Key.fromText <$> arbitrary
323+#endif
324+#endif
test/Data/OpenApi/SchemaSpec.hs+4
@@ -59,6 +59,9 @@ checkInlinedRecSchema proxy js = inlineNonRecursiveSchemas defs s <=> js
59 where
59 where
60 (defs, s) = runDeclare (declareSchema proxy) mempty
60 (defs, s) = runDeclare (declareSchema proxy) mempty
61
61
62+checkToSchemaDeclare :: (HasCallStack, ToSchema a) => Proxy a -> Value -> Spec
63+checkToSchemaDeclare proxy js = runDeclare (declareSchemaRef proxy) mempty <=> js
64+
62spec :: Spec
65spec :: Spec
63spec = do
66spec = do
64 describe "Generic ToSchema" $ do
67 describe "Generic ToSchema" $ do
@@ -78,6 +81,7 @@ spec = do
78 context "UserId (non-record newtype)" $ checkToSchema (Proxy :: Proxy UserId) userIdSchemaJSON
81 context "UserId (non-record newtype)" $ checkToSchema (Proxy :: Proxy UserId) userIdSchemaJSON
79 context "Player (unary record)" $ checkToSchema (Proxy :: Proxy Player) playerSchemaJSON
82 context "Player (unary record)" $ checkToSchema (Proxy :: Proxy Player) playerSchemaJSON
80 context "SingleMaybeField (unary record with Maybe)" $ checkToSchema (Proxy :: Proxy SingleMaybeField) singleMaybeFieldSchemaJSON
83 context "SingleMaybeField (unary record with Maybe)" $ checkToSchema (Proxy :: Proxy SingleMaybeField) singleMaybeFieldSchemaJSON
84+ context "Natural Language (single field data with recursive fields)" $ checkToSchemaDeclare (Proxy :: Proxy Predicate) predicateSchemaDeclareJSON
81 context "Players (inlining schema)" $ checkToSchema (Proxy :: Proxy Players) playersSchemaJSON
85 context "Players (inlining schema)" $ checkToSchema (Proxy :: Proxy Players) playersSchemaJSON
82 context "MyRoseTree (datatypeNameModifier)" $ checkToSchema (Proxy :: Proxy MyRoseTree) myRoseTreeSchemaJSON
86 context "MyRoseTree (datatypeNameModifier)" $ checkToSchema (Proxy :: Proxy MyRoseTree) myRoseTreeSchemaJSON
83 context "MyRoseTree' (datatypeNameModifier)" $ checkToSchema (Proxy :: Proxy MyRoseTree') myRoseTreeSchemaJSON'
87 context "MyRoseTree' (datatypeNameModifier)" $ checkToSchema (Proxy :: Proxy MyRoseTree') myRoseTreeSchemaJSON'
test/Data/OpenApiSpec.hs+17−1
@@ -45,6 +45,10 @@ spec = do
45 fromJSON petstoreExampleJSON `shouldSatisfy` (\x -> case x of Success (_ :: OpenApi) -> True; _ -> False)
45 fromJSON petstoreExampleJSON `shouldSatisfy` (\x -> case x of Success (_ :: OpenApi) -> True; _ -> False)
46 it "roundtrips: fmap toJSON . fromJSON" $ do
46 it "roundtrips: fmap toJSON . fromJSON" $ do
47 (toJSON :: OpenApi -> Value) <$> fromJSON petstoreExampleJSON `shouldBe` Success petstoreExampleJSON
47 (toJSON :: OpenApi -> Value) <$> fromJSON petstoreExampleJSON `shouldBe` Success petstoreExampleJSON
48+ context "Security schemes" $ do
49+ it "merged correctly" $ do
50+ let merged = oAuth2SecurityDefinitionsReadOpenApi <> oAuth2SecurityDefinitionsWriteOpenApi
51+ merged `shouldBe` oAuth2SecurityDefinitionsOpenApi
48
52
49main :: IO ()
53main :: IO ()
50main = hspec spec
54main = hspec spec
@@ -441,7 +445,7 @@ responsesDefinitionExampleJSON = [aesonQQ|
441|]
445|]
442
446
443-- =======================================================================
447-- =======================================================================
444−-- Responses Definition object
448+-- Security Definition object
445-- =======================================================================
449-- =======================================================================
446
450
447securityDefinitionsExample :: SecurityDefinitions
451securityDefinitionsExample :: SecurityDefinitions
@@ -527,6 +531,18 @@ oAuth2SecurityDefinitionsExampleJSON = [aesonQQ|
527}
531}
528|]
532|]
529
533
534+oAuth2SecurityDefinitionsReadOpenApi :: OpenApi
535+oAuth2SecurityDefinitionsReadOpenApi =
536+ mempty & components . securitySchemes .~ oAuth2SecurityDefinitionsReadExample
537+
538+oAuth2SecurityDefinitionsWriteOpenApi :: OpenApi
539+oAuth2SecurityDefinitionsWriteOpenApi =
540+ mempty & components . securitySchemes .~ oAuth2SecurityDefinitionsWriteExample
541+
542+oAuth2SecurityDefinitionsOpenApi :: OpenApi
543+oAuth2SecurityDefinitionsOpenApi =
544+ mempty & components . securitySchemes .~ oAuth2SecurityDefinitionsExample
545+
530-- =======================================================================
546-- =======================================================================
531-- Swagger object
547-- Swagger object
532-- =======================================================================
548-- =======================================================================
test/SpecCommon.hs−8
@@ -8,14 +8,6 @@ import qualified Data.Vector as Vector
8
8
9import Test.Hspec
9import Test.Hspec
10
10
11−isSubJSON :: Value -> Value -> Bool
12−isSubJSON Null _ = True
13−isSubJSON (Object x) (Object y) = HashMap.keys x == HashMap.keys i && F.and i
14− where
15− i = HashMap.intersectionWith isSubJSON x y
16−isSubJSON (Array xs) (Array ys) = Vector.length xs == Vector.length ys && F.and (Vector.zipWith isSubJSON xs ys)
17−isSubJSON x y = x == y
18−
19(<=>) :: (Eq a, Show a, ToJSON a, FromJSON a, HasCallStack) => a -> Value -> Spec
11(<=>) :: (Eq a, Show a, ToJSON a, FromJSON a, HasCallStack) => a -> Value -> Spec
20x <=> js = do
12x <=> js = do
21 it "encodes correctly" $ do
13 it "encodes correctly" $ do