git.iko.soy/typeable/openapi3
on 98e9537 help sign in
23 files changed+359−1385
since splitsnapshots
unifiedsplit
.github/workflows/haskell-ci.yml−243 .travis.yml+149 CHANGELOG.md−47 cabal.haskell-ci−11 cabal.project−2 openapi3.cabal+16−20 src/Data/OpenApi.hs+20−141 src/Data/OpenApi/Aeson/Compat.hs−76 src/Data/OpenApi/Internal.hs+21−88 src/Data/OpenApi/Internal/AesonUtils.hs+10−11 src/Data/OpenApi/Internal/ParamSchema.hs+11−29 src/Data/OpenApi/Internal/Schema.hs+72−334 src/Data/OpenApi/Internal/Schema/Validation.hs+27−39 src/Data/OpenApi/Internal/Utils.hs−5 src/Data/OpenApi/Operation.hs+10−117 src/Data/OpenApi/Optics.hs+9−54 src/Data/OpenApi/Schema/Generator.hs+1−3 stack.yaml+1−1 test/Data/OpenApi/CommonTestTypes.hs−123 test/Data/OpenApi/Schema/ValidationSpec.hs+3−20 test/Data/OpenApi/SchemaSpec.hs−4 test/Data/OpenApiSpec.hs+1−17 test/SpecCommon.hs+8
.github/workflows/haskell-ci.ymldeleted−243
@@ -1,243 +0,0 @@
1−# This GitHub workflow config has been generated by a script via
2−#
3−# haskell-ci 'github' 'cabal.project'
4−#
5−# To regenerate the script (for example after adjusting tested-with) run
6−#
7−# haskell-ci regenerate
8−#
9−# For more information, see https://github.com/haskell-CI/haskell-ci
10−#
11−# version: 0.14.3
12−#
13−# REGENDATA ("0.14.3",["github","cabal.project"])
14−#
15−name: Haskell-CI
16−on:
17− push:
18− branches:
19− - master
20− pull_request:
21− branches:
22− - master
23−jobs:
24− linux:
25− name: Haskell-CI - Linux - ${{ matrix.compiler }}
26− runs-on: ubuntu-18.04
27− timeout-minutes:
28− 60
29− container:
30− image: buildpack-deps:focal
31− continue-on-error: ${{ matrix.allow-failure }}
32− strategy:
33− matrix:
34− include:
35− - compiler: ghc-9.2.2
36− compilerKind: ghc
37− compilerVersion: 9.2.2
38− setup-method: ghcup
39− allow-failure: true
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
49− allow-failure: false
50− - compiler: ghc-8.8.4
51− compilerKind: ghc
52− compilerVersion: 8.8.4
53− setup-method: hvr-ppa
54− allow-failure: false
55− - compiler: ghc-8.6.5
56− compilerKind: ghc
57− compilerVersion: 8.6.5
58− setup-method: hvr-ppa
59− allow-failure: false
60− - compiler: ghc-8.4.4
61− compilerKind: ghc
62− compilerVersion: 8.4.4
63− setup-method: hvr-ppa
64− allow-failure: false
65− fail-fast: false
66− steps:
67− - name: apt
68− run: |
69− apt-get update
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
78− apt-add-repository -y 'ppa:hvr/ghc'
79− apt-get update
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
86− env:
87− HCKIND: ${{ matrix.compilerKind }}
88− HCNAME: ${{ matrix.compiler }}
89− HCVER: ${{ matrix.compilerVersion }}
90− - name: Set PATH and environment variables
91− run: |
92− echo "$HOME/.cabal/bin" >> $GITHUB_PATH
93− echo "LANG=C.UTF-8" >> "$GITHUB_ENV"
94− echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
95− echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
96− HCDIR=/opt/$HCKIND/$HCVER
97− if [ "${{ matrix.setup-method }}" = ghcup ]; then
98− HC=$HOME/.ghcup/bin/$HCKIND-$HCVER
99− echo "HC=$HC" >> "$GITHUB_ENV"
100− echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$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"
108− echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
109− fi
110−
111− HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
112− echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
113− echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
114− echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
115− echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
116− echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
117− echo "GHCJSARITH=0" >> "$GITHUB_ENV"
118− env:
119− HCKIND: ${{ matrix.compilerKind }}
120− HCNAME: ${{ matrix.compiler }}
121− HCVER: ${{ matrix.compilerVersion }}
122− - name: env
123− run: |
124− env
125− - name: write cabal config
126− run: |
127− mkdir -p $CABAL_DIR
128− cat >> $CABAL_CONFIG <<EOF
129− remote-build-reporting: anonymous
130− write-ghc-environment-files: never
131− remote-repo-cache: $CABAL_DIR/packages
132− logs-dir: $CABAL_DIR/logs
133− world-file: $CABAL_DIR/world
134− extra-prog-path: $CABAL_DIR/bin
135− symlink-bindir: $CABAL_DIR/bin
136− installdir: $CABAL_DIR/bin
137− build-summary: $CABAL_DIR/logs/build.log
138− store-dir: $CABAL_DIR/store
139− install-dirs user
140− prefix: $CABAL_DIR
141− repository hackage.haskell.org
142− url: http://hackage.haskell.org/
143− EOF
144− cat >> $CABAL_CONFIG <<EOF
145− program-default-options
146− ghc-options: $GHCJOBS +RTS -M3G -RTS
147− EOF
148− cat $CABAL_CONFIG
149− - name: versions
150− run: |
151− $HC --version || true
152− $HC --print-project-git-commit-id || true
153− $CABAL --version || true
154− - name: update cabal index
155− run: |
156− $CABAL v2-update -v
157− - name: install cabal-plan
158− run: |
159− mkdir -p $HOME/.cabal/bin
160− curl -sL https://github.com/haskell-hvr/cabal-plan/releases/download/v0.6.2.0/cabal-plan-0.6.2.0-x86_64-linux.xz > cabal-plan.xz
161− echo 'de73600b1836d3f55e32d80385acc055fd97f60eaa0ab68a755302685f5d81bc cabal-plan.xz' | sha256sum -c -
162− xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan
163− rm -f cabal-plan.xz
164− chmod a+x $HOME/.cabal/bin/cabal-plan
165− cabal-plan --version
166− - name: checkout
167− uses: actions/checkout@v2
168− with:
169− path: source
170− - name: initial cabal.project for sdist
171− run: |
172− touch cabal.project
173− echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project
174− cat cabal.project
175− - name: sdist
176− run: |
177− mkdir -p sdist
178− $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist
179− - name: unpack
180− run: |
181− mkdir -p unpacked
182− find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \;
183− - name: generate cabal.project
184− run: |
185− PKGDIR_openapi3="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/openapi3-[0-9.]*')"
186− echo "PKGDIR_openapi3=${PKGDIR_openapi3}" >> "$GITHUB_ENV"
187− rm -f cabal.project cabal.project.local
188− touch cabal.project
189− touch cabal.project.local
190− echo "packages: ${PKGDIR_openapi3}" >> cabal.project
191− echo "package openapi3" >> cabal.project
192− echo " ghc-options: -Werror=missing-methods" >> cabal.project
193− cat >> cabal.project <<EOF
194− allow-newer: http-media:base
195− EOF
196− $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(openapi3)$/; }' >> cabal.project.local
197− cat cabal.project
198− cat cabal.project.local
199− - name: dump install plan
200− run: |
201− $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all
202− cabal-plan
203− - name: cache
204− uses: actions/cache@v2
205− with:
206− key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
207− path: ~/.cabal/store
208− restore-keys: ${{ runner.os }}-${{ matrix.compiler }}-
209− - name: install dependencies
210− run: |
211− $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all
212− $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all
213− - name: build w/o tests
214− run: |
215− $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
216− - name: build
217− run: |
218− $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always
219− - name: tests
220− run: |
221− $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
222− - name: cabal check
223− run: |
224− cd ${PKGDIR_openapi3} || false
225− ${CABAL} -vnormal check
226− - name: haddock
227− run: |
228− $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
229− - name: unconstrained build
230− run: |
231− rm -f cabal.project.local
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
.travis.ymlnew file+149
@@ -0,0 +1,149 @@
1+# This Travis job script has been generated by a script via
2+#
3+# haskell-ci '--branches' 'master' '--haddock-jobs=>=8.4' '--output' '.travis.yml' 'openapi3.cabal'
4+#
5+# To regenerate the script (for example after adjusting tested-with) run
6+#
7+# haskell-ci regenerate
8+#
9+# For more information, see https://github.com/haskell-CI/haskell-ci
10+#
11+# version: 0.10.1
12+#
13+version: ~> 1.0
14+language: c
15+os: linux
16+dist: xenial
17+git:
18+ # whether to recursively clone submodules
19+ submodules: false
20+branches:
21+ only:
22+ - master
23+cache:
24+ directories:
25+ - $HOME/.cabal/packages
26+ - $HOME/.cabal/store
27+ - $HOME/.hlint
28+before_cache:
29+ - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
30+ # remove files that are regenerated by 'cabal update'
31+ - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
32+ - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
33+ - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
34+ - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
35+ - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
36+ - rm -rfv $CABALHOME/packages/head.hackage
37+jobs:
38+ include:
39+ - compiler: ghc-8.10.1
40+ addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.10.1","cabal-install-3.2"]}}
41+ os: linux
42+ - compiler: ghc-8.8.3
43+ addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.8.3","cabal-install-3.2"]}}
44+ os: linux
45+ - compiler: ghc-8.6.5
46+ addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.6.5","cabal-install-3.2"]}}
47+ os: linux
48+ - compiler: ghc-8.4.4
49+ addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.4.4","cabal-install-3.2"]}}
50+ os: linux
51+before_install:
52+ - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
53+ - WITHCOMPILER="-w $HC"
54+ - HADDOCK=$(echo "/opt/$CC/bin/haddock" | sed 's/-/\//')
55+ - HCPKG="$HC-pkg"
56+ - unset CC
57+ - CABAL=/opt/ghc/bin/cabal
58+ - CABALHOME=$HOME/.cabal
59+ - export PATH="$CABALHOME/bin:$PATH"
60+ - TOP=$(pwd)
61+ - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
62+ - echo $HCNUMVER
63+ - CABAL="$CABAL -vnormal+nowrap"
64+ - set -o pipefail
65+ - TEST=--enable-tests
66+ - BENCH=--enable-benchmarks
67+ - HEADHACKAGE=false
68+ - rm -f $CABALHOME/config
69+ - |
70+ echo "verbose: normal +nowrap +markoutput" >> $CABALHOME/config
71+ echo "remote-build-reporting: anonymous" >> $CABALHOME/config
72+ echo "write-ghc-environment-files: always" >> $CABALHOME/config
73+ echo "remote-repo-cache: $CABALHOME/packages" >> $CABALHOME/config
74+ echo "logs-dir: $CABALHOME/logs" >> $CABALHOME/config
75+ echo "world-file: $CABALHOME/world" >> $CABALHOME/config
76+ echo "extra-prog-path: $CABALHOME/bin" >> $CABALHOME/config
77+ echo "symlink-bindir: $CABALHOME/bin" >> $CABALHOME/config
78+ echo "installdir: $CABALHOME/bin" >> $CABALHOME/config
79+ echo "build-summary: $CABALHOME/logs/build.log" >> $CABALHOME/config
80+ echo "store-dir: $CABALHOME/store" >> $CABALHOME/config
81+ echo "install-dirs user" >> $CABALHOME/config
82+ echo " prefix: $CABALHOME" >> $CABALHOME/config
83+ echo "repository hackage.haskell.org" >> $CABALHOME/config
84+ echo " url: http://hackage.haskell.org/" >> $CABALHOME/config
85+install:
86+ - ${CABAL} --version
87+ - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
88+ - |
89+ echo "program-default-options" >> $CABALHOME/config
90+ echo " ghc-options: $GHCJOBS +RTS -M6G -RTS" >> $CABALHOME/config
91+ - cat $CABALHOME/config
92+ - rm -fv cabal.project cabal.project.local cabal.project.freeze
93+ - travis_retry ${CABAL} v2-update -v
94+ # Generate cabal.project
95+ - rm -rf cabal.project cabal.project.local cabal.project.freeze
96+ - touch cabal.project
97+ - |
98+ echo "packages: ." >> cabal.project
99+ - echo 'package openapi3' >> cabal.project
100+ - "echo ' ghc-options: -Werror=missing-methods' >> cabal.project"
101+ - |
102+ - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(openapi3)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
103+ - cat cabal.project || true
104+ - cat cabal.project.local || true
105+ - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi
106+ - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH}
107+ - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
108+ - rm cabal.project.freeze
109+ - travis_wait 40 ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all
110+ - travis_wait 40 ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all
111+script:
112+ - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
113+ # Packaging...
114+ - ${CABAL} v2-sdist all
115+ # Unpacking...
116+ - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
117+ - cd ${DISTDIR} || false
118+ - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
119+ - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm '{}' \;
120+ - PKGDIR_openapi3="$(find . -maxdepth 1 -type d -regex '.*/openapi3-[0-9.]*')"
121+ # Generate cabal.project
122+ - rm -rf cabal.project cabal.project.local cabal.project.freeze
123+ - touch cabal.project
124+ - |
125+ echo "packages: ${PKGDIR_openapi3}" >> cabal.project
126+ - echo 'package openapi3' >> cabal.project
127+ - "echo ' ghc-options: -Werror=missing-methods' >> cabal.project"
128+ - |
129+ - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(openapi3)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
130+ - cat cabal.project || true
131+ - cat cabal.project.local || true
132+ # Building...
133+ # this builds all libraries and executables (without tests/benchmarks)
134+ - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all
135+ # Building with tests and benchmarks...
136+ # build & run tests, build benchmarks
137+ - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all
138+ # Testing...
139+ - ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all
140+ # cabal check...
141+ - (cd ${PKGDIR_openapi3} && ${CABAL} -vnormal check)
142+ # haddock...
143+ - ${CABAL} v2-haddock $WITHCOMPILER --with-haddock $HADDOCK ${TEST} ${BENCH} all
144+ # Building without installed constraints for packages in global-db...
145+ - rm -f cabal.project.local
146+ - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all
147+
148+# REGENDATA ("0.10.1",["--branches","master","--haddock-jobs=>=8.4","--output",".travis.yml","openapi3.cabal"])
149+# EOF
CHANGELOG.md−47
@@ -1,50 +1,3 @@
1−Unreleased
2−----------
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)).
29−- Allow `hashable-1.3.1`, prettify doctests (see [#18](https://github.com/biocad/openapi3/pull/18)).
30−
31−3.0.2.0
32−-------
33−
34−- Fix definition for HTTP security scheme (see [#10](https://github.com/biocad/openapi3/pull/10));
35−- Fix error message in `exclusiveMaximum` / `exclusiveMinimum` validation (see
36− [#15](https://github.com/biocad/openapi3/pull/15));
37−
38−3.0.1.0
39−-------
40−
41−- Support polymorphic types in schema generator.
42−
43−3.0.0
44−-----
45−
46−- Switch to OpenAPI 3.0 specification.
47−
4812.6.1
492-----
503
cabal.haskell-cideleted−11
@@ -1,11 +0,0 @@
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
cabal.project−2
@@ -1,4 +1,2 @@
11packages: .
22tests: true
3−
4−allow-newer: http-media:base
openapi3.cabal+16−20
@@ -1,6 +1,6 @@
11cabal-version: >=1.10
22name: openapi3
3−version: 3.2.2
3+version: 3.0.0
44
55synopsis: OpenAPI 3.0 data model
66category: Web, Swagger, OpenApi
@@ -25,10 +25,8 @@ extra-source-files:
2525tested-with:
2626 GHC ==8.4.4
2727 || ==8.6.5
28− || ==8.8.4
29− || ==8.10.7
30− || ==9.0.2
31− || ==9.2.2
28+ || ==8.8.3
29+ || ==8.10.1
3230
3331custom-setup
3432 setup-depends:
@@ -57,36 +55,34 @@ library
5755 Data.OpenApi.Internal.AesonUtils
5856 Data.OpenApi.Internal.TypeShape
5957
60− Data.OpenApi.Aeson.Compat
61−
6258 -- GHC boot libraries
6359 build-depends:
64− base >=4.11.1.0 && <4.17
65− , bytestring >=0.10.8.2 && <0.12
60+ base >=4.11.1.0 && <4.15
61+ , bytestring >=0.10.8.2 && <0.11
6662 , containers >=0.5.11.0 && <0.7
67− , template-haskell >=2.13.0.0 && <2.19
68− , time >=1.8.0.2 && <1.14
63+ , template-haskell >=2.13.0.0 && <2.17
64+ , time >=1.8.0.2 && <1.10
6965 , transformers >=0.5.5.0 && <0.6
7066
7167 build-depends:
7268 mtl >=2.2.2 && <2.3
73− , text >=1.2.3.1 && <2.1
69+ , text >=1.2.3.1 && <1.3
7470
7571 -- other dependencies
7672 build-depends:
77− base-compat-batteries >=0.11.1 && <0.13
78− , aeson >=1.4.2.0 && <1.6 || >=2.0.1.0 && < 2.1
73+ base-compat-batteries >=0.11.1 && <0.12
74+ , aeson >=1.4.2.0 && <1.6
7975 , aeson-pretty >=0.8.7 && <0.9
8076 -- cookie 0.4.3 is needed by GHC 7.8 due to time>=1.4 constraint
8177 , cookie >=0.4.3 && <0.5
8278 , generics-sop >=0.5.1.0 && <0.6
83− , hashable >=1.2.7.0 && <1.5
79+ , hashable >=1.2.7.0 && <1.4
8480 , http-media >=0.8.0.0 && <0.9
8581 , insert-ordered-containers >=0.2.3 && <0.3
86− , lens >=4.16.1 && <5.2
82+ , lens >=4.16.1 && <4.20
8783 , network >=2.6.3.5 && <3.2
88− , optics-core >=0.2 && <0.5
89− , optics-th >=0.2 && <0.5
84+ , optics-core >=0.2 && <0.4
85+ , optics-th >=0.2 && <0.4
9086 , scientific >=0.3.6.2 && <0.4
9187 , unordered-containers >=0.2.9.0 && <0.3
9288 , uuid-types >=1.0.3 && <1.1
@@ -122,14 +118,14 @@ test-suite spec
122118
123119 -- test-suite only dependencies
124120 build-depends:
125− hspec >=2.5.5 && <2.10
121+ hspec >=2.5.5 && <2.8
126122 , HUnit >=1.6.0.0 && <1.7
127123 , quickcheck-instances >=0.3.19 && <0.14
128124 , utf8-string >=1.0.1.1 && <1.1
129125
130126 -- https://github.com/haskell/cabal/issues/3708
131127 build-tool-depends:
132− hspec-discover:hspec-discover >=2.5.5 && <2.10
128+ hspec-discover:hspec-discover >=2.5.5 && <2.8
133129
134130 other-modules:
135131 SpecCommon
src/Data/OpenApi.hs+20−141
@@ -91,7 +91,6 @@ module Data.OpenApi (
9191 -- ** Security
9292 SecurityScheme(..),
9393 SecuritySchemeType(..),
94− HttpSchemeType(..),
9594 SecurityDefinitions(..),
9695 SecurityRequirement(..),
9796
@@ -137,7 +136,6 @@ import Data.OpenApi.Internal
137136-- >>> import Data.Proxy
138137-- >>> import GHC.Generics
139138-- >>> import qualified Data.ByteString.Lazy.Char8 as BSL
140−-- >>> import Data.OpenApi.Internal.Utils
141139-- >>> :set -XDeriveGeneric
142140-- >>> :set -XOverloadedStrings
143141-- >>> :set -XOverloadedLists
@@ -154,16 +152,8 @@ import Data.OpenApi.Internal
154152--
155153-- In this library you can use @'mempty'@ for a default/empty value. For instance:
156154--
157−-- >>> BSL.putStrLn $ encodePretty (mempty :: OpenApi)
158−-- {
159−-- "components": {},
160−-- "info": {
161−-- "title": "",
162−-- "version": ""
163−-- },
164−-- "openapi": "3.0.0",
165−-- "paths": {}
166−-- }
155+-- >>> BSL.putStrLn $ encode (mempty :: OpenApi)
156+-- {"openapi":"3.0.0","info":{"version":"","title":""},"components":{}}
167157--
168158-- As you can see some spec properties (e.g. @"version"@) are there even when the spec is empty.
169159-- That is because these properties are actually required ones.
@@ -171,20 +161,13 @@ import Data.OpenApi.Internal
171161-- You /should/ always override the default (empty) value for these properties,
172162-- although it is not strictly necessary:
173163--
174−-- >>> BSL.putStrLn $ encodePretty mempty { _infoTitle = "Todo API", _infoVersion = "1.0" }
175−-- {
176−-- "title": "Todo API",
177−-- "version": "1.0"
178−-- }
164+-- >>> BSL.putStrLn $ encode mempty { _infoTitle = "Todo API", _infoVersion = "1.0" }
165+-- {"version":"1.0","title":"Todo API"}
179166--
180167-- You can merge two values using @'mappend'@ or its infix version @('<>')@:
181168--
182−-- >>> BSL.putStrLn $ encodePretty $ mempty { _infoTitle = "Todo API" } <> mempty { _infoVersion = "1.0" }
183−-- {
184−-- "title": "Todo API",
185−-- "version": "1.0"
186−-- }
187−
169+-- >>> BSL.putStrLn $ encode $ mempty { _infoTitle = "Todo API" } <> mempty { _infoVersion = "1.0" }
170+-- {"version":"1.0","title":"Todo API"}
188171--
189172-- This can be useful for combining specifications of endpoints into a whole API specification:
190173--
@@ -210,48 +193,14 @@ import Data.OpenApi.Internal
210193-- make it fairly simple to construct/modify any part of the specification:
211194--
212195-- >>> :{
213−-- BSL.putStrLn $ encodePretty $ (mempty :: OpenApi)
196+-- BSL.putStrLn $ encode $ (mempty :: OpenApi)
214197-- & components . schemas .~ [ ("User", mempty & type_ ?~ OpenApiString) ]
215198-- & paths .~
216199-- [ ("/user", mempty & get ?~ (mempty
217200-- & at 200 ?~ ("OK" & _Inline.content.at "application/json" ?~ (mempty & schema ?~ Ref (Reference "User")))
218201-- & at 404 ?~ "User info not found")) ]
219202-- :}
220−-- {
221−-- "components": {
222−-- "schemas": {
223−-- "User": {
224−-- "type": "string"
225−-- }
226−-- }
227−-- },
228−-- "info": {
229−-- "title": "",
230−-- "version": ""
231−-- },
232−-- "openapi": "3.0.0",
233−-- "paths": {
234−-- "/user": {
235−-- "get": {
236−-- "responses": {
237−-- "200": {
238−-- "content": {
239−-- "application/json": {
240−-- "schema": {
241−-- "$ref": "#/components/schemas/User"
242−-- }
243−-- }
244−-- },
245−-- "description": "OK"
246−-- },
247−-- "404": {
248−-- "description": "User info not found"
249−-- }
250−-- }
251−-- }
252−-- }
253−-- }
254−-- }
203+-- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/user":{"get":{"responses":{"404":{"description":"User info not found"},"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"OK"}}}}},"components":{"schemas":{"User":{"type":"string"}}}}
255204--
256205-- In the snippet above we declare an API with a single path @/user@. This path provides method @GET@
257206-- which produces @application/json@ output. It should respond with code @200@ and body specified
@@ -263,34 +212,23 @@ import Data.OpenApi.Internal
263212-- common field is @'description'@. Many components of a Swagger specification
264213-- can have descriptions, and you can use the same name for them:
265214--
266−-- >>> BSL.putStrLn $ encodePretty $ (mempty :: Response) & description .~ "No content"
267−-- {
268−-- "description": "No content"
269−-- }
215+-- >>> BSL.putStrLn $ encode $ (mempty :: Response) & description .~ "No content"
216+-- {"description":"No content"}
270217-- >>> :{
271−-- BSL.putStrLn $ encodePretty $ (mempty :: Schema)
218+-- BSL.putStrLn $ encode $ (mempty :: Schema)
272219-- & type_ ?~ OpenApiBoolean
273220-- & description ?~ "To be or not to be"
274221-- :}
275−-- {
276−-- "description": "To be or not to be",
277−-- "type": "boolean"
278−-- }
222+-- {"type":"boolean","description":"To be or not to be"}
279223--
280224-- Additionally, to simplify working with @'Response'@, both @'Operation'@ and @'Responses'@
281225-- have direct access to it via @'at' code@. Example:
282226--
283227-- >>> :{
284−-- BSL.putStrLn $ encodePretty $ (mempty :: Operation)
228+-- BSL.putStrLn $ encode $ (mempty :: Operation)
285229-- & at 404 ?~ "Not found"
286230-- :}
287−-- {
288−-- "responses": {
289−-- "404": {
290−-- "description": "Not found"
291−-- }
292−-- }
293−-- }
231+-- {"responses":{"404":{"description":"Not found"}}}
294232--
295233-- You might've noticed that @'type_'@ has an extra underscore in its name
296234-- compared to, say, @'description'@ field accessor.
@@ -337,27 +275,10 @@ import Data.OpenApi.Internal
337275-- >>> data Person = Person { name :: String, age :: Integer } deriving Generic
338276-- >>> instance ToJSON Person
339277-- >>> instance ToSchema Person
340−-- >>> BSL.putStrLn $ encodePretty (Person "David" 28)
341−-- {
342−-- "age": 28,
343−-- "name": "David"
344−-- }
345−-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy Person)
346−-- {
347−-- "properties": {
348−-- "age": {
349−-- "type": "integer"
350−-- },
351−-- "name": {
352−-- "type": "string"
353−-- }
354−-- },
355−-- "required": [
356−-- "name",
357−-- "age"
358−-- ],
359−-- "type": "object"
360−-- }
278+-- >>> BSL.putStrLn $ encode (Person "David" 28)
279+-- {"age":28,"name":"David"}
280+-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Person)
281+-- {"required":["name","age"],"type":"object","properties":{"age":{"type":"integer"},"name":{"type":"string"}}}
361282--
362283-- This package implements OpenAPI 3.0 spec, which supports @oneOf@ in schemas, allowing any sum types
363284-- to be faithfully represented. All sum encodings supported by @aeson@ are supported here as well, with
@@ -368,50 +289,8 @@ import Data.OpenApi.Internal
368289-- >>> data Error = ErrorNoUser { userId :: Int } | ErrorAccessDenied { requiredPermission :: String } deriving Generic
369290-- >>> instance ToJSON Error
370291-- >>> instance ToSchema Error
371−-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy Error)
372−-- {
373−-- "oneOf": [
374−-- {
375−-- "properties": {
376−-- "tag": {
377−-- "enum": [
378−-- "ErrorNoUser"
379−-- ],
380−-- "type": "string"
381−-- },
382−-- "userId": {
383−-- "maximum": 9223372036854775807,
384−-- "minimum": -9223372036854775808,
385−-- "type": "integer"
386−-- }
387−-- },
388−-- "required": [
389−-- "userId",
390−-- "tag"
391−-- ],
392−-- "type": "object"
393−-- },
394−-- {
395−-- "properties": {
396−-- "requiredPermission": {
397−-- "type": "string"
398−-- },
399−-- "tag": {
400−-- "enum": [
401−-- "ErrorAccessDenied"
402−-- ],
403−-- "type": "string"
404−-- }
405−-- },
406−-- "required": [
407−-- "requiredPermission",
408−-- "tag"
409−-- ],
410−-- "type": "object"
411−-- }
412−-- ],
413−-- "type": "object"
414−-- }
292+-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Error)
293+-- {"oneOf":[{"required":["userId","tag"],"type":"object","properties":{"tag":{"type":"string","enum":["ErrorNoUser"]},"userId":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"}}},{"required":["requiredPermission","tag"],"type":"object","properties":{"tag":{"type":"string","enum":["ErrorAccessDenied"]},"requiredPermission":{"type":"string"}}}],"type":"object"}
415294
416295-- $manipulation
417296-- Sometimes you have to work with an imported or generated @'Swagger'@.
src/Data/OpenApi/Aeson/Compat.hsdeleted−76
@@ -1,76 +0,0 @@
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+21−88
@@ -1,4 +1,3 @@
1−{-# LANGUAGE CPP #-}
21{-# LANGUAGE DataKinds #-}
32{-# LANGUAGE DeriveDataTypeable #-}
43{-# LANGUAGE DeriveGeneric #-}
@@ -22,9 +21,6 @@ import Prelude.Compat
2221import Control.Applicative
2322import Control.Lens ((&), (.~), (?~))
2423import Data.Aeson hiding (Encoding)
25−#if MIN_VERSION_aeson(2,0,0)
26−import qualified Data.Aeson.KeyMap as KeyMap
27−#endif
2824import qualified Data.Aeson.Types as JSON
2925import Data.Data (Constr, Data (..), DataType, Fixity (..), Typeable,
3026 constrIndex, mkConstr, mkDataType)
@@ -49,19 +45,21 @@ import Text.Read (readMaybe)
4945import Data.HashMap.Strict.InsOrd (InsOrdHashMap)
5046import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
5147
52−import Data.OpenApi.Aeson.Compat (deleteKey)
53−import Data.OpenApi.Internal.AesonUtils (AesonDefaultValue (..), HasSwaggerAesonOptions (..),
54− mkSwaggerAesonOptions, saoAdditionalPairs, saoSubObject,
55− sopSwaggerGenericParseJSON, sopSwaggerGenericToEncoding,
56− sopSwaggerGenericToJSON, sopSwaggerGenericToJSONWithOpts)
57−import Data.OpenApi.Internal.Utils
5848import Generics.SOP.TH (deriveGeneric)
49+import Data.OpenApi.Internal.AesonUtils (sopSwaggerGenericToJSON
50+ ,sopSwaggerGenericToJSONWithOpts
51+ ,sopSwaggerGenericParseJSON
52+ ,HasSwaggerAesonOptions(..)
53+ ,AesonDefaultValue(..)
54+ ,mkSwaggerAesonOptions
55+ ,saoAdditionalPairs
56+ ,saoSubObject)
57+import Data.OpenApi.Internal.Utils
58+import Data.OpenApi.Internal.AesonUtils (sopSwaggerGenericToEncoding)
5959
6060-- $setup
6161-- >>> :seti -XDataKinds
6262-- >>> import Data.Aeson
63−-- >>> import Data.ByteString.Lazy.Char8 as BSL
64−-- >>> import Data.OpenApi.Internal.Utils
6563
6664-- | A list of definitions that can be used in references.
6765type Definitions = InsOrdHashMap Text
@@ -196,7 +194,7 @@ data Components = Components
196194 , _componentsExamples :: Definitions Example
197195 , _componentsRequestBodies :: Definitions RequestBody
198196 , _componentsHeaders :: Definitions Header
199− , _componentsSecuritySchemes :: SecurityDefinitions
197+ , _componentsSecuritySchemes :: Definitions SecurityScheme
200198 , _componentsLinks :: Definitions Link
201199 , _componentsCallbacks :: Definitions Callback
202200 } deriving (Eq, Show, Generic, Data, Typeable)
@@ -337,7 +335,7 @@ data RequestBody = RequestBody
337335 -- | The content of the request body.
338336 -- The key is a media type or media type range and the value describes it.
339337 -- For requests that match multiple keys, only the most specific key is applicable.
340− -- e.g. @text/plain@ overrides @text/\*@
338+ -- e.g. @text/plain@ overrides @text/*@
341339 , _requestBodyContent :: InsOrdHashMap MediaType MediaTypeObject
342340
343341 -- | Determines if the request body is required in the request.
@@ -394,7 +392,7 @@ data Encoding = Encoding
394392 -- for other primitive types – @text/plain@; for object - @application/json@;
395393 -- for array – the default is defined based on the inner type.
396394 -- 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.
395+ -- a wildcard media type (e.g. @image/*@), or a comma-separated list of the two types.
398396 _encodingContentType :: Maybe MediaType
399397
400398 -- | A map allowing additional information to be provided as headers,
@@ -734,7 +732,7 @@ data Response = Response
734732 -- | A map containing descriptions of potential response payloads.
735733 -- The key is a media type or media type range and the value describes it.
736734 -- For responses that match multiple keys, only the most specific key is applicable.
737− -- e.g. @text/plain@ overrides @text/\*@.
735+ -- e.g. @text/plain@ overrides @text/*@.
738736 , _responseContent :: InsOrdHashMap MediaType MediaTypeObject
739737
740738 -- | Maps a header name to its definition.
@@ -840,50 +838,8 @@ data OAuth2Flows = OAuth2Flows
840838 , _oAuth2FlowsAuthorizationCode :: Maybe (OAuth2Flow OAuth2AuthorizationCodeFlow)
841839 } deriving (Eq, Show, Generic, Data, Typeable)
842840
843−type BearerFormat = Text
844−
845−data HttpSchemeType
846− = HttpSchemeBearer (Maybe BearerFormat)
847− | HttpSchemeBasic
848− | HttpSchemeCustom Text
849− deriving (Eq, Show, Generic, Data, Typeable)
850−
851−-- |
852−--
853−-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeHttp (HttpSchemeBearer Nothing))
854−-- {
855−-- "scheme": "bearer",
856−-- "type": "http"
857−-- }
858−--
859−-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeHttp (HttpSchemeBearer (Just "jwt")))
860−-- {
861−-- "bearerFormat": "jwt",
862−-- "scheme": "bearer",
863−-- "type": "http"
864−-- }
865−--
866−-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeHttp HttpSchemeBasic)
867−-- {
868−-- "scheme": "basic",
869−-- "type": "http"
870−-- }
871−--
872−-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeHttp (HttpSchemeCustom "CANARY"))
873−-- {
874−-- "scheme": "CANARY",
875−-- "type": "http"
876−-- }
877−--
878−-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeApiKey (ApiKeyParams "id" ApiKeyCookie))
879−-- {
880−-- "in": "cookie",
881−-- "name": "id",
882−-- "type": "apiKey"
883−-- }
884−--
885841data SecuritySchemeType
886− = SecuritySchemeHttp HttpSchemeType
842+ = SecuritySchemeHttp
887843 | SecuritySchemeApiKey ApiKeyParams
888844 | SecuritySchemeOAuth2 OAuth2Flows
889845 | SecuritySchemeOpenIdConnect URL
@@ -1125,7 +1081,6 @@ instance SwaggerMonoid Response
11251081instance SwaggerMonoid ExternalDocs
11261082instance SwaggerMonoid Operation
11271083instance (Eq a, Hashable a) => SwaggerMonoid (InsOrdHashSet a)
1128−instance SwaggerMonoid SecurityDefinitions
11291084
11301085instance SwaggerMonoid MimeList
11311086deriving instance SwaggerMonoid URL
@@ -1274,19 +1229,8 @@ instance ToJSON OAuth2Flows where
12741229 toEncoding = sopSwaggerGenericToEncoding
12751230
12761231instance ToJSON SecuritySchemeType where
1277− toJSON (SecuritySchemeHttp ty) = case ty of
1278− HttpSchemeBearer mFmt ->
1279− object $ [ "type" .= ("http" :: Text)
1280− , "scheme" .= ("bearer" :: Text)
1281− ] <> maybe [] (\t -> ["bearerFormat" .= t]) mFmt
1282− HttpSchemeBasic ->
1283− object [ "type" .= ("http" :: Text)
1284− , "scheme" .= ("basic" :: Text)
1285− ]
1286− HttpSchemeCustom t ->
1287− object [ "type" .= ("http" :: Text)
1288− , "scheme" .= t
1289− ]
1232+ toJSON SecuritySchemeHttp
1233+ = object [ "type" .= ("http" :: Text) ]
12901234 toJSON (SecuritySchemeApiKey params)
12911235 = toJSON params
12921236 <+> object [ "type" .= ("apiKey" :: Text) ]
@@ -1325,12 +1269,8 @@ instance ToJSON Header where
13251269-- | As for nullary schema for 0-arity type constructors, see
13261270-- <https://github.com/GetShopTV/swagger2/issues/167>.
13271271--
1328−-- >>> BSL.putStrLn $ encodePretty (OpenApiItemsArray [])
1329−-- {
1330−-- "example": [],
1331−-- "items": {},
1332−-- "maxItems": 0
1333−-- }
1272+-- >>> encode (OpenApiItemsArray [])
1273+-- "{\"example\":[],\"items\":{},\"maxItems\":0}"
13341274--
13351275instance ToJSON OpenApiItems where
13361276 toJSON (OpenApiItemsObject x) = object [ "items" .= x ]
@@ -1439,12 +1379,7 @@ instance FromJSON SecuritySchemeType where
14391379 parseJSON js@(Object o) = do
14401380 (t :: Text) <- o .: "type"
14411381 case t of
1442− "http" -> do
1443− scheme <- o .: "scheme"
1444− SecuritySchemeHttp <$> case scheme of
1445− "bearer" -> HttpSchemeBearer <$> (o .:! "bearerFormat")
1446− "basic" -> pure HttpSchemeBasic
1447− t -> pure $ HttpSchemeCustom t
1382+ "http" -> pure SecuritySchemeHttp
14481383 "apiKey" -> SecuritySchemeApiKey <$> parseJSON js
14491384 "oauth2" -> SecuritySchemeOAuth2 <$> (o .: "flows")
14501385 "openIdConnect" -> SecuritySchemeOpenIdConnect <$> (o .: "openIdConnectUrl")
@@ -1492,7 +1427,7 @@ instance FromJSON Param where
14921427instance FromJSON Responses where
14931428 parseJSON (Object o) = Responses
14941429 <$> o .:? "default"
1495− <*> parseJSON (Object (deleteKey "default" o))
1430+ <*> parseJSON (Object (HashMap.delete "default" o))
14961431 parseJSON _ = empty
14971432
14981433instance FromJSON Example where
@@ -1615,5 +1550,3 @@ instance AesonDefaultValue MimeList where defaultValue = Just mempty
16151550instance AesonDefaultValue Info
16161551instance AesonDefaultValue ParamLocation
16171552instance AesonDefaultValue Link
1618−instance AesonDefaultValue SecurityDefinitions where
1619− defaultValue = Just mempty
src/Data/OpenApi/Internal/AesonUtils.hs+10−11
@@ -36,19 +36,18 @@ import Data.Text (Text)
3636import Generics.SOP
3737
3838import qualified Data.Text as T
39+import qualified Data.HashMap.Strict as HM
3940import qualified Data.Set as Set
4041import qualified Data.HashMap.Strict.InsOrd as InsOrd
4142import qualified Data.HashSet.InsOrd as InsOrdHS
4243
43−import Data.OpenApi.Aeson.Compat (keyToString, objectToList, stringToKey)
44−
4544-------------------------------------------------------------------------------
4645-- SwaggerAesonOptions
4746-------------------------------------------------------------------------------
4847
4948data SwaggerAesonOptions = SwaggerAesonOptions
5049 { _saoPrefix :: String
51− , _saoAdditionalPairs :: [Pair]
50+ , _saoAdditionalPairs :: [(Text, Value)]
5251 , _saoSubObject :: Maybe String
5352 }
5453
@@ -155,14 +154,14 @@ sopSwaggerGenericToJSON'' (SwaggerAesonOptions prefix _ sub) = go
155154 go Nil Nil Nil = []
156155 go (I x :* xs) (FieldInfo name :* names) (def :* defs)
157156 | Just name' == sub = case json of
158− Object m -> objectToList m ++ rest
157+ Object m -> HM.toList m ++ rest
159158 Null -> rest
160159 _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show json
161160 -- If default value: omit it.
162161 | Just x == def =
163162 rest
164163 | otherwise =
165− (stringToKey name', json) : rest
164+ (T.pack name', json) : rest
166165 where
167166 json = toJSON x
168167 name' = fieldNameModifier name
@@ -196,11 +195,11 @@ sopSwaggerGenericParseJSON = withObject "Swagger Record Object" $ \obj ->
196195 proxy = Proxy :: Proxy a
197196 opts = swaggerAesonOptions proxy
198197
199− parseAdditionalField :: Object -> Pair -> Parser ()
198+ parseAdditionalField :: Object -> (Text, Value) -> Parser ()
200199 parseAdditionalField obj (k, v) = do
201200 v' <- obj .: k
202201 unless (v == v') $ fail $
203− "Additonal field don't match for key " ++ keyToString k
202+ "Additonal field don't match for key " ++ T.unpack k
204203 ++ ": " ++ show v
205204 ++ " /= " ++ show v'
206205
@@ -231,8 +230,8 @@ sopSwaggerGenericParseJSON'' (SwaggerAesonOptions prefix _ sub) obj = go
231230 -- Note: we might strip fields of outer structure.
232231 cons <$> (withDef $ parseJSON $ Object obj) <*> rest
233232 | otherwise = case def of
234− Just def' -> cons <$> obj .:? stringToKey name' .!= def' <*> rest
235− Nothing -> cons <$> obj .: stringToKey name' <*> rest
233+ Just def' -> cons <$> obj .:? T.pack name' .!= def' <*> rest
234+ Nothing -> cons <$> obj .: T.pack name' <*> rest
236235 where
237236 cons h t = I h :* t
238237 name' = fieldNameModifier name
@@ -295,14 +294,14 @@ sopSwaggerGenericToEncoding'' (SwaggerAesonOptions prefix _ sub) = go
295294 go Nil Nil Nil = mempty
296295 go (I x :* xs) (FieldInfo name :* names) (def :* defs)
297296 | Just name' == sub = case toJSON x of
298− Object m -> pairsToSeries (objectToList m) <> rest
297+ Object m -> pairsToSeries (HM.toList m) <> rest
299298 Null -> rest
300299 _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show (toJSON x)
301300 -- If default value: omit it.
302301 | Just x == def =
303302 rest
304303 | otherwise =
305− (stringToKey name' .= x) <> rest
304+ (T.pack name' .= x) <> rest
306305 where
307306 name' = fieldNameModifier name
308307 rest = go xs names defs
src/Data/OpenApi/Internal/ParamSchema.hs+11−29
@@ -46,7 +46,7 @@ import Data.OpenApi.Lens
4646import Data.OpenApi.SchemaOptions
4747
4848import qualified Data.ByteString as BS
49−import qualified Data.ByteString.Lazy.Char8 as BSL
49+import qualified Data.ByteString.Lazy as BSL
5050import GHC.TypeLits (TypeError, ErrorMessage(..))
5151
5252-- | Default schema for binary data (any sequence of octets).
@@ -112,10 +112,8 @@ passwordSchema = mempty
112112class ToParamSchema a where
113113 -- | Convert a type into a plain parameter schema.
114114 --
115− -- >>> BSL.putStrLn $ encodePretty $ toParamSchema (Proxy :: Proxy Integer)
116− -- {
117− -- "type": "integer"
118− -- }
115+ -- >>> encode $ toParamSchema (Proxy :: Proxy Integer)
116+ -- "{\"type\":\"integer\"}"
119117 toParamSchema :: Proxy a -> Schema
120118 default toParamSchema :: (Generic a, GToParamSchema (Rep a)) => Proxy a -> Schema
121119 toParamSchema = genericToParamSchema defaultSchemaOptions
@@ -157,12 +155,8 @@ instance ToParamSchema Word64 where
157155
158156-- | Default plain schema for @'Bounded'@, @'Integral'@ types.
159157--
160−-- >>> BSL.putStrLn $ encodePretty $ toParamSchemaBoundedIntegral (Proxy :: Proxy Int8)
161−-- {
162−-- "maximum": 127,
163−-- "minimum": -128,
164−-- "type": "integer"
165−-- }
158+-- >>> encode $ toParamSchemaBoundedIntegral (Proxy :: Proxy Int8)
159+-- "{\"maximum\":127,\"minimum\":-128,\"type\":\"integer\"}"
166160toParamSchemaBoundedIntegral :: forall a t. (Bounded a, Integral a) => Proxy a -> Schema
167161toParamSchemaBoundedIntegral _ = mempty
168162 & type_ ?~ OpenApiInteger
@@ -216,9 +210,9 @@ instance ToParamSchema LocalTime where
216210
217211-- |
218212-- >>> toParamSchema (Proxy :: Proxy ZonedTime) ^. format
219−-- Just "date-time"
213+-- Just "yyyy-mm-ddThh:MM:ss+hhMM"
220214instance ToParamSchema ZonedTime where
221− toParamSchema _ = timeParamSchema "date-time"
215+ toParamSchema _ = timeParamSchema "yyyy-mm-ddThh:MM:ss+hhMM"
222216
223217-- |
224218-- >>> toParamSchema (Proxy :: Proxy UTCTime) ^. format
@@ -281,13 +275,8 @@ instance ToParamSchema a => ToParamSchema (HashSet a) where
281275 toParamSchema _ = toParamSchema (Proxy :: Proxy (Set a))
282276
283277-- |
284−-- >>> BSL.putStrLn $ encodePretty $ toParamSchema (Proxy :: Proxy ())
285−-- {
286−-- "enum": [
287−-- "_"
288−-- ],
289−-- "type": "string"
290−-- }
278+-- >>> encode $ toParamSchema (Proxy :: Proxy ())
279+-- "{\"type\":\"string\",\"enum\":[\"_\"]}"
291280instance ToParamSchema () where
292281 toParamSchema _ = mempty
293282 & type_ ?~ OpenApiString
@@ -302,14 +291,8 @@ instance ToParamSchema UUID where
302291--
303292-- >>> :set -XDeriveGeneric
304293-- >>> data Color = Red | Blue deriving Generic
305−-- >>> BSL.putStrLn $ encodePretty $ genericToParamSchema defaultSchemaOptions (Proxy :: Proxy Color)
306−-- {
307−-- "enum": [
308−-- "Red",
309−-- "Blue"
310−-- ],
311−-- "type": "string"
312−-- }
294+-- >>> encode $ genericToParamSchema defaultSchemaOptions (Proxy :: Proxy Color)
295+-- "{\"type\":\"string\",\"enum\":[\"Red\",\"Blue\"]}"
313296genericToParamSchema :: forall a t. (Generic a, GToParamSchema (Rep a)) => SchemaOptions -> Proxy a -> Schema
314297genericToParamSchema opts _ = gtoParamSchema opts (Proxy :: Proxy (Rep a)) mempty
315298
@@ -351,4 +334,3 @@ data Proxy3 a b c = Proxy3
351334
352335-- $setup
353336-- >>> import Data.Aeson (encode)
354−-- >>> import Data.OpenApi.Internal.Utils
src/Data/OpenApi/Internal/Schema.hs+72−334
@@ -1,17 +1,15 @@
1−{-# LANGUAGE CPP #-}
21{-# LANGUAGE ConstraintKinds #-}
3−{-# LANGUAGE DataKinds #-}
42{-# LANGUAGE DefaultSignatures #-}
53{-# LANGUAGE FlexibleContexts #-}
64{-# LANGUAGE FlexibleInstances #-}
75{-# LANGUAGE KindSignatures #-}
6+{-# LANGUAGE DataKinds #-}
87{-# LANGUAGE OverloadedLists #-}
98{-# LANGUAGE OverloadedStrings #-}
109{-# LANGUAGE PackageImports #-}
1110{-# LANGUAGE PolyKinds #-}
1211{-# LANGUAGE ScopedTypeVariables #-}
1312{-# LANGUAGE TupleSections #-}
14−{-# LANGUAGE TypeApplications #-}
1513{-# LANGUAGE TypeFamilies #-}
1614{-# LANGUAGE TypeOperators #-}
1715{-# LANGUAGE TypeSynonymInstances #-}
@@ -42,7 +40,6 @@ import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
4240import Data.Int
4341import Data.IntSet (IntSet)
4442import Data.IntMap (IntMap)
45−import Data.List (sort)
4643import Data.List.NonEmpty.Compat (NonEmpty)
4744import Data.Map (Map)
4845import Data.Maybe (fromMaybe)
@@ -62,16 +59,14 @@ import Numeric.Natural.Compat (Natural)
6259import Data.Word
6360import GHC.Generics
6461import qualified Data.UUID.Types as UUID
65−import Type.Reflection (Typeable, typeRep)
6662
67−import Data.OpenApi.Aeson.Compat (keyToText, objectKeys, toInsOrdHashMap)
68−import Data.OpenApi.Declare
69−import Data.OpenApi.Internal
70−import Data.OpenApi.Internal.ParamSchema (ToParamSchema (..))
71−import Data.OpenApi.Internal.TypeShape
72−import Data.OpenApi.Lens hiding (name, schema)
73−import qualified Data.OpenApi.Lens as Swagger
74−import Data.OpenApi.SchemaOptions
63+import Data.OpenApi.Declare
64+import Data.OpenApi.Internal
65+import Data.OpenApi.Internal.ParamSchema (ToParamSchema(..))
66+import Data.OpenApi.Lens hiding (name, schema)
67+import qualified Data.OpenApi.Lens as Swagger
68+import Data.OpenApi.SchemaOptions
69+import Data.OpenApi.Internal.TypeShape
7570
7671import qualified Data.ByteString as BS
7772import qualified Data.ByteString.Lazy.Char8 as BSL
@@ -136,7 +131,7 @@ rename name (NamedSchema _ schema) = NamedSchema name schema
136131--
137132-- instance ToSchema Coord
138133-- @
139−class Typeable a => ToSchema a where
134+class ToSchema a where
140135 -- | Convert a type into an optionally named schema
141136 -- together with all used definitions.
142137 -- Note that the schema itself is included in definitions
@@ -158,19 +153,13 @@ declareSchema = fmap _namedSchemaSchema . declareNamedSchema
158153--
159154-- >>> toNamedSchema (Proxy :: Proxy String) ^. name
160155-- Nothing
161−-- >>> BSL.putStrLn $ encodePretty (toNamedSchema (Proxy :: Proxy String) ^. schema)
162−-- {
163−-- "type": "string"
164−-- }
156+-- >>> BSL.putStrLn $ encode (toNamedSchema (Proxy :: Proxy String) ^. schema)
157+-- {"type":"string"}
165158--
166159-- >>> toNamedSchema (Proxy :: Proxy Day) ^. name
167160-- Just "Day"
168−-- >>> BSL.putStrLn $ encodePretty (toNamedSchema (Proxy :: Proxy Day) ^. schema)
169−-- {
170−-- "example": "2016-07-22",
171−-- "format": "date",
172−-- "type": "string"
173−-- }
161+-- >>> BSL.putStrLn $ encode (toNamedSchema (Proxy :: Proxy Day) ^. schema)
162+-- {"example":"2016-07-22","format":"date","type":"string"}
174163toNamedSchema :: ToSchema a => Proxy a -> NamedSchema
175164toNamedSchema = undeclare . declareNamedSchema
176165
@@ -186,35 +175,22 @@ schemaName = _namedSchemaName . toNamedSchema
186175
187176-- | Convert a type into a schema.
188177--
189−-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy Int8)
190−-- {
191−-- "maximum": 127,
192−-- "minimum": -128,
193−-- "type": "integer"
194−-- }
178+-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Int8)
179+-- {"maximum":127,"minimum":-128,"type":"integer"}
195180--
196−-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy [Day])
197−-- {
198−-- "items": {
199−-- "$ref": "#/components/schemas/Day"
200−-- },
201−-- "type": "array"
202−-- }
181+-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy [Day])
182+-- {"items":{"$ref":"#/components/schemas/Day"},"type":"array"}
203183toSchema :: ToSchema a => Proxy a -> Schema
204184toSchema = _namedSchemaSchema . toNamedSchema
205185
206186-- | Convert a type into a referenced schema if possible.
207187-- Only named schemas can be referenced, nameless schemas are inlined.
208188--
209−-- >>> BSL.putStrLn $ encodePretty $ toSchemaRef (Proxy :: Proxy Integer)
210−-- {
211−-- "type": "integer"
212−-- }
189+-- >>> BSL.putStrLn $ encode $ toSchemaRef (Proxy :: Proxy Integer)
190+-- {"type":"integer"}
213191--
214−-- >>> BSL.putStrLn $ encodePretty $ toSchemaRef (Proxy :: Proxy Day)
215−-- {
216−-- "$ref": "#/components/schemas/Day"
217−-- }
192+-- >>> BSL.putStrLn $ encode $ toSchemaRef (Proxy :: Proxy Day)
193+-- {"$ref":"#/components/schemas/Day"}
218194toSchemaRef :: ToSchema a => Proxy a -> Referenced Schema
219195toSchemaRef = undeclare . declareSchemaRef
220196
@@ -282,15 +258,8 @@ inlineAllSchemas = inlineSchemasWhen (const True)
282258
283259-- | Convert a type into a schema without references.
284260--
285−-- >>> BSL.putStrLn $ encodePretty $ toInlinedSchema (Proxy :: Proxy [Day])
286−-- {
287−-- "items": {
288−-- "example": "2016-07-22",
289−-- "format": "date",
290−-- "type": "string"
291−-- },
292−-- "type": "array"
293−-- }
261+-- >>> BSL.putStrLn $ encode $ toInlinedSchema (Proxy :: Proxy [Day])
262+-- {"items":{"example":"2016-07-22","format":"date","type":"string"},"type":"array"}
294263--
295264-- __WARNING:__ @'toInlinedSchema'@ will produce infinite schema
296265-- when inlining recursive schemas.
@@ -323,64 +292,19 @@ inlineNonRecursiveSchemas defs = inlineSchemasWhen nonRecursive defs
323292-- | Make an unrestrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
324293-- Produced schema can be used for further refinement.
325294--
326−-- >>> BSL.putStrLn $ encodePretty $ sketchSchema "hello"
327−-- {
328−-- "example": "hello",
329−-- "type": "string"
330−-- }
295+-- >>> BSL.putStrLn $ encode $ sketchSchema "hello"
296+-- {"example":"hello","type":"string"}
331297--
332−-- >>> BSL.putStrLn $ encodePretty $ sketchSchema (1, 2, 3)
333−-- {
334−-- "example": [
335−-- 1,
336−-- 2,
337−-- 3
338−-- ],
339−-- "items": {
340−-- "type": "number"
341−-- },
342−-- "type": "array"
343−-- }
298+-- >>> BSL.putStrLn $ encode $ sketchSchema (1, 2, 3)
299+-- {"example":[1,2,3],"items":{"type":"number"},"type":"array"}
344300--
345−-- >>> BSL.putStrLn $ encodePretty $ sketchSchema ("Jack", 25)
346−-- {
347−-- "example": [
348−-- "Jack",
349−-- 25
350−-- ],
351−-- "items": [
352−-- {
353−-- "type": "string"
354−-- },
355−-- {
356−-- "type": "number"
357−-- }
358−-- ],
359−-- "type": "array"
360−-- }
301+-- >>> BSL.putStrLn $ encode $ sketchSchema ("Jack", 25)
302+-- {"example":["Jack",25],"items":[{"type":"string"},{"type":"number"}],"type":"array"}
361303--
362304-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
363305-- >>> instance ToJSON Person
364−-- >>> BSL.putStrLn $ encodePretty $ sketchSchema (Person "Jack" 25)
365−-- {
366−-- "example": {
367−-- "age": 25,
368−-- "name": "Jack"
369−-- },
370−-- "properties": {
371−-- "age": {
372−-- "type": "number"
373−-- },
374−-- "name": {
375−-- "type": "string"
376−-- }
377−-- },
378−-- "required": [
379−-- "age",
380−-- "name"
381−-- ],
382−-- "type": "object"
383−-- }
306+-- >>> BSL.putStrLn $ encode $ sketchSchema (Person "Jack" 25)
307+-- {"example":{"age":25,"name":"Jack"},"required":["age","name"],"type":"object","properties":{"age":{"type":"number"},"name":{"type":"string"}}}
384308sketchSchema :: ToJSON a => a -> Schema
385309sketchSchema = sketch . toJSON
386310 where
@@ -406,139 +330,25 @@ sketchSchema = sketch . toJSON
406330 _ -> Nothing
407331 go (Object o) = mempty
408332 & type_ ?~ OpenApiObject
409− & required .~ sort (objectKeys o)
410− & properties .~ fmap (Inline . go) (toInsOrdHashMap o)
333+ & required .~ HashMap.keys o
334+ & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
411335
412336-- | Make a restrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
413337-- Produced schema uses as much constraints as possible.
414338--
415−-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema "hello"
416−-- {
417−-- "enum": [
418−-- "hello"
419−-- ],
420−-- "maxLength": 5,
421−-- "minLength": 5,
422−-- "pattern": "hello",
423−-- "type": "string"
424−-- }
339+-- >>> BSL.putStrLn $ encode $ sketchStrictSchema "hello"
340+-- {"maxLength":5,"pattern":"hello","minLength":5,"type":"string","enum":["hello"]}
425341--
426−-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema (1, 2, 3)
427−-- {
428−-- "enum": [
429−-- [
430−-- 1,
431−-- 2,
432−-- 3
433−-- ]
434−-- ],
435−-- "items": [
436−-- {
437−-- "enum": [
438−-- 1
439−-- ],
440−-- "maximum": 1,
441−-- "minimum": 1,
442−-- "multipleOf": 1,
443−-- "type": "number"
444−-- },
445−-- {
446−-- "enum": [
447−-- 2
448−-- ],
449−-- "maximum": 2,
450−-- "minimum": 2,
451−-- "multipleOf": 2,
452−-- "type": "number"
453−-- },
454−-- {
455−-- "enum": [
456−-- 3
457−-- ],
458−-- "maximum": 3,
459−-- "minimum": 3,
460−-- "multipleOf": 3,
461−-- "type": "number"
462−-- }
463−-- ],
464−-- "maxItems": 3,
465−-- "minItems": 3,
466−-- "type": "array",
467−-- "uniqueItems": true
468−-- }
342+-- >>> BSL.putStrLn $ encode $ sketchStrictSchema (1, 2, 3)
343+-- {"minItems":3,"uniqueItems":true,"items":[{"maximum":1,"minimum":1,"multipleOf":1,"type":"number","enum":[1]},{"maximum":2,"minimum":2,"multipleOf":2,"type":"number","enum":[2]},{"maximum":3,"minimum":3,"multipleOf":3,"type":"number","enum":[3]}],"maxItems":3,"type":"array","enum":[[1,2,3]]}
469344--
470−-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema ("Jack", 25)
471−-- {
472−-- "enum": [
473−-- [
474−-- "Jack",
475−-- 25
476−-- ]
477−-- ],
478−-- "items": [
479−-- {
480−-- "enum": [
481−-- "Jack"
482−-- ],
483−-- "maxLength": 4,
484−-- "minLength": 4,
485−-- "pattern": "Jack",
486−-- "type": "string"
487−-- },
488−-- {
489−-- "enum": [
490−-- 25
491−-- ],
492−-- "maximum": 25,
493−-- "minimum": 25,
494−-- "multipleOf": 25,
495−-- "type": "number"
496−-- }
497−-- ],
498−-- "maxItems": 2,
499−-- "minItems": 2,
500−-- "type": "array",
501−-- "uniqueItems": true
502−-- }
345+-- >>> BSL.putStrLn $ encode $ sketchStrictSchema ("Jack", 25)
346+-- {"minItems":2,"uniqueItems":true,"items":[{"maxLength":4,"pattern":"Jack","minLength":4,"type":"string","enum":["Jack"]},{"maximum":25,"minimum":25,"multipleOf":25,"type":"number","enum":[25]}],"maxItems":2,"type":"array","enum":[["Jack",25]]}
503347--
504348-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
505349-- >>> instance ToJSON Person
506−-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema (Person "Jack" 25)
507−-- {
508−-- "enum": [
509−-- {
510−-- "age": 25,
511−-- "name": "Jack"
512−-- }
513−-- ],
514−-- "maxProperties": 2,
515−-- "minProperties": 2,
516−-- "properties": {
517−-- "age": {
518−-- "enum": [
519−-- 25
520−-- ],
521−-- "maximum": 25,
522−-- "minimum": 25,
523−-- "multipleOf": 25,
524−-- "type": "number"
525−-- },
526−-- "name": {
527−-- "enum": [
528−-- "Jack"
529−-- ],
530−-- "maxLength": 4,
531−-- "minLength": 4,
532−-- "pattern": "Jack",
533−-- "type": "string"
534−-- }
535−-- },
536−-- "required": [
537−-- "age",
538−-- "name"
539−-- ],
540−-- "type": "object"
541−-- }
350+-- >>> BSL.putStrLn $ encode $ sketchStrictSchema (Person "Jack" 25)
351+-- {"minProperties":2,"required":["age","name"],"maxProperties":2,"type":"object","enum":[{"age":25,"name":"Jack"}],"properties":{"age":{"maximum":25,"minimum":25,"multipleOf":25,"type":"number","enum":[25]},"name":{"maxLength":4,"pattern":"Jack","minLength":4,"type":"string","enum":["Jack"]}}}
542352sketchStrictSchema :: ToJSON a => a -> Schema
543353sketchStrictSchema = go . toJSON
544354 where
@@ -570,13 +380,13 @@ sketchStrictSchema = go . toJSON
570380 allUnique = sz == HashSet.size (HashSet.fromList (V.toList xs))
571381 go js@(Object o) = mempty
572382 & type_ ?~ OpenApiObject
573− & required .~ sort names
574− & properties .~ fmap (Inline . go) (toInsOrdHashMap o)
383+ & required .~ names
384+ & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
575385 & maxProperties ?~ fromIntegral (length names)
576386 & minProperties ?~ fromIntegral (length names)
577387 & enum_ ?~ [js]
578388 where
579− names = objectKeys o
389+ names = HashMap.keys o
580390
581391class GToSchema (f :: * -> *) where
582392 gdeclareNamedSchema :: SchemaOptions -> Proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
@@ -611,7 +421,7 @@ instance ToSchema Scientific where declareNamedSchema = plain . paramSchemaToSc
611421instance ToSchema Double where declareNamedSchema = plain . paramSchemaToSchema
612422instance ToSchema Float where declareNamedSchema = plain . paramSchemaToSchema
613423
614−instance (Typeable (Fixed a), HasResolution a) => ToSchema (Fixed a) where declareNamedSchema = plain . paramSchemaToSchema
424+instance HasResolution a => ToSchema (Fixed a) where declareNamedSchema = plain . paramSchemaToSchema
615425
616426instance ToSchema a => ToSchema (Maybe a) where
617427 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a)
@@ -628,18 +438,12 @@ instance ToSchema UUID.UUID where
628438 declareNamedSchema p = pure $ named "UUID" $ paramSchemaToSchema p
629439 & example ?~ toJSON (UUID.toText UUID.nil)
630440
631−instance (ToSchema a, ToSchema b) => ToSchema (a, b) where
632− declareNamedSchema = fmap unname . genericDeclareNamedSchema defaultSchemaOptions
633−instance (ToSchema a, ToSchema b, ToSchema c) => ToSchema (a, b, c) where
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
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
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
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
441+instance (ToSchema a, ToSchema b) => ToSchema (a, b)
442+instance (ToSchema a, ToSchema b, ToSchema c) => ToSchema (a, b, c)
443+instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d) => ToSchema (a, b, c, d)
444+instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d, ToSchema e) => ToSchema (a, b, c, d, e)
445+instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d, ToSchema e, ToSchema f) => ToSchema (a, b, c, d, e, f)
446+instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d, ToSchema e, ToSchema f, ToSchema g) => ToSchema (a, b, c, d, e, f, g)
643447
644448timeSchema :: T.Text -> Schema
645449timeSchema fmt = mempty
@@ -658,7 +462,7 @@ instance ToSchema LocalTime where
658462 declareNamedSchema _ = pure $ named "LocalTime" $ timeSchema "yyyy-mm-ddThh:MM:ss"
659463 & example ?~ toJSON (LocalTime (fromGregorian 2016 7 22) (TimeOfDay 7 40 0))
660464
661−-- | Format @"date-time"@ corresponds to @yyyy-mm-ddThh:MM:ss(Z|+hh:MM)@ format.
465+-- | Format @"date"@ corresponds to @yyyy-mm-ddThh:MM:ss(Z|+hh:MM)@ format.
662466instance ToSchema ZonedTime where
663467 declareNamedSchema _ = pure $ named "ZonedTime" $ timeSchema "date-time"
664468 & example ?~ toJSON (ZonedTime (LocalTime (fromGregorian 2016 7 22) (TimeOfDay 7 40 0)) (hoursToTimeZone 3))
@@ -690,7 +494,7 @@ instance ToSchemaByteStringError BSL.ByteString => ToSchema BSL.ByteString where
690494instance ToSchema IntSet where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Set Int))
691495
692496-- | NOTE: This schema does not account for the uniqueness of keys.
693−instance (ToSchema a) => ToSchema (IntMap a) where
497+instance ToSchema a => ToSchema (IntMap a) where
694498 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy [(Int, a)])
695499
696500instance (ToJSONKey k, ToSchema k, ToSchema v) => ToSchema (Map k v) where
@@ -746,12 +550,8 @@ instance ToSchema a => ToSchema (Identity a) where declareNamedSchema _ = declar
746550
747551-- | Default schema for @'Bounded'@, @'Integral'@ types.
748552--
749−-- >>> BSL.putStrLn $ encodePretty $ toSchemaBoundedIntegral (Proxy :: Proxy Int16)
750−-- {
751−-- "maximum": 32767,
752−-- "minimum": -32768,
753−-- "type": "integer"
754−-- }
553+-- >>> BSL.putStrLn $ encode $ toSchemaBoundedIntegral (Proxy :: Proxy Int16)
554+-- {"maximum":32767,"minimum":-32768,"type":"integer"}
755555toSchemaBoundedIntegral :: forall a. (Bounded a, Integral a) => Proxy a -> Schema
756556toSchemaBoundedIntegral _ = mempty
757557 & type_ ?~ OpenApiInteger
@@ -783,27 +583,8 @@ genericDeclareNamedSchemaNewtype opts f proxy = genericNameSchema opts proxy <$>
783583-- >>> instance ToSchema ButtonState
784584-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
785585-- >>> type ImageUrl = T.Text
786−-- >>> BSL.putStrLn $ encodePretty $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
787−-- {
788−-- "properties": {
789−-- "Active": {
790−-- "type": "string"
791−-- },
792−-- "Disabled": {
793−-- "type": "string"
794−-- },
795−-- "Focus": {
796−-- "type": "string"
797−-- },
798−-- "Hover": {
799−-- "type": "string"
800−-- },
801−-- "Neutral": {
802−-- "type": "string"
803−-- }
804−-- },
805−-- "type": "object"
806−-- }
586+-- >>> BSL.putStrLn $ encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
587+-- {"type":"object","properties":{"Focus":{"type":"string"},"Disabled":{"type":"string"},"Active":{"type":"string"},"Neutral":{"type":"string"},"Hover":{"type":"string"}}}
807588--
808589-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
809590-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
@@ -811,13 +592,13 @@ declareSchemaBoundedEnumKeyMapping :: forall map key value.
811592 (Bounded key, Enum key, ToJSONKey key, ToSchema key, ToSchema value)
812593 => Proxy (map key value) -> Declare (Definitions Schema) Schema
813594declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key of
814− ToJSONKeyText getKey _ -> objectSchema getKey
595+ ToJSONKeyText keyToText _ -> objectSchema keyToText
815596 ToJSONKeyValue _ _ -> declareSchema (Proxy :: Proxy [(key, value)])
816597 where
817− objectSchema getKey = do
598+ objectSchema keyToText = do
818599 valueRef <- declareSchemaRef (Proxy :: Proxy value)
819600 let allKeys = [minBound..maxBound :: key]
820− mkPair k = (keyToText $ getKey k, valueRef)
601+ mkPair k = (keyToText k, valueRef)
821602 return $ mempty
822603 & type_ ?~ OpenApiObject
823604 & properties .~ InsOrdHashMap.fromList (map mkPair allKeys)
@@ -830,27 +611,8 @@ declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key o
830611-- >>> instance ToSchema ButtonState
831612-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
832613-- >>> type ImageUrl = T.Text
833−-- >>> BSL.putStrLn $ encodePretty $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
834−-- {
835−-- "properties": {
836−-- "Active": {
837−-- "type": "string"
838−-- },
839−-- "Disabled": {
840−-- "type": "string"
841−-- },
842−-- "Focus": {
843−-- "type": "string"
844−-- },
845−-- "Hover": {
846−-- "type": "string"
847−-- },
848−-- "Neutral": {
849−-- "type": "string"
850−-- }
851−-- },
852−-- "type": "object"
853−-- }
614+-- >>> BSL.putStrLn $ encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
615+-- {"type":"object","properties":{"Focus":{"type":"string"},"Disabled":{"type":"string"},"Active":{"type":"string"},"Neutral":{"type":"string"},"Hover":{"type":"string"}}}
854616--
855617-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
856618-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
@@ -860,7 +622,7 @@ toSchemaBoundedEnumKeyMapping :: forall map key value.
860622toSchemaBoundedEnumKeyMapping = flip evalDeclare mempty . declareSchemaBoundedEnumKeyMapping
861623
862624-- | A configurable generic @'Schema'@ creator.
863−genericDeclareSchema :: (Generic a, GToSchema (Rep a), Typeable a) =>
625+genericDeclareSchema :: (Generic a, GToSchema (Rep a)) =>
864626 SchemaOptions -> Proxy a -> Declare (Definitions Schema) Schema
865627genericDeclareSchema opts proxy = _namedSchemaSchema <$> genericDeclareNamedSchema opts proxy
866628
@@ -868,24 +630,9 @@ genericDeclareSchema opts proxy = _namedSchemaSchema <$> genericDeclareNamedSche
868630-- This function applied to @'defaultSchemaOptions'@
869631-- is used as the default for @'declareNamedSchema'@
870632-- 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"
879−genericDeclareNamedSchema :: forall a. (Generic a, GToSchema (Rep a), Typeable a) =>
633+genericDeclareNamedSchema :: forall a. (Generic a, GToSchema (Rep a)) =>
880634 SchemaOptions -> Proxy a -> Declare (Definitions Schema) NamedSchema
881−genericDeclareNamedSchema opts _ =
882− rename (Just $ T.pack name) <$> gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
883− where
884− unspace ' ' = '_'
885− unspace x = x
886− orig = fmap unspace $ show $ typeRep @a
887− name = datatypeNameModifier opts orig
888−
635+genericDeclareNamedSchema opts _ = gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
889636
890637-- | Derive a 'Generic'-based name for a datatype and assign it to a given 'Schema'.
891638genericNameSchema :: forall a d f.
@@ -945,13 +692,10 @@ instance (Selector s, GToSchema f, GToSchema (S1 s f)) => GToSchema (C1 c (S1 s
945692 case schema ^. items of
946693 Just (OpenApiItemsArray [_]) -> fieldSchema
947694 _ -> do
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
952− return (unnamed schema')
695+ declare defs
696+ return (unnamed schema)
953697 where
954− (_, NamedSchema _ schema) = runDeclare recordSchema mempty
698+ (defs, NamedSchema _ schema) = runDeclare recordSchema mempty
955699 recordSchema = gdeclareNamedSchema opts (Proxy :: Proxy (S1 s f)) s
956700 fieldSchema = gdeclareNamedSchema opts (Proxy :: Proxy f) s
957701
@@ -1114,13 +858,7 @@ data Proxy2 a b = Proxy2
1114858
1115859data Proxy3 a b c = Proxy3
1116860
1117−{- $setup
1118−>>> import Data.OpenApi
1119−>>> import Data.Aeson (encode)
1120−>>> import Data.Aeson.Types (toJSONKeyText)
1121−>>> import Data.OpenApi.Internal.Utils
1122−>>> :set -XScopedTypeVariables
1123−>>> :set -XDeriveAnyClass
1124−>>> :set -XStandaloneDeriving
1125−>>> :set -XTypeApplications
1126−-}
861+-- $setup
862+-- >>> import Data.OpenApi
863+-- >>> import Data.Aeson (encode)
864+-- >>> import Data.Aeson.Types (toJSONKeyText)
src/Data/OpenApi/Internal/Schema/Validation.hs+27−39
@@ -1,5 +1,4 @@
11{-# OPTIONS_GHC -Wall #-}
2−{-# LANGUAGE CPP #-}
32{-# LANGUAGE DataKinds #-}
43{-# LANGUAGE DeriveFunctor #-}
54{-# LANGUAGE FlexibleInstances #-}
@@ -32,14 +31,11 @@ import Control.Lens hiding (allOf)
3231import Control.Monad (forM, forM_, when)
3332
3433import Data.Aeson hiding (Result)
35−#if MIN_VERSION_aeson(2,0,0)
36−import qualified Data.Aeson.KeyMap as KeyMap
37−#endif
34+import Data.Aeson.Encode.Pretty (encodePretty)
3835import Data.Foldable (for_, sequenceA_,
3936 traverse_)
40−#if !MIN_VERSION_aeson(2,0,0)
4137import Data.HashMap.Strict (HashMap)
42−#endif
38+import qualified Data.HashMap.Strict as HashMap
4339import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
4440import qualified "unordered-containers" Data.HashSet as HashSet
4541import Data.Maybe (fromMaybe)
@@ -52,12 +48,10 @@ import qualified Data.Text.Lazy.Encoding as TL
5248import Data.Vector (Vector)
5349import qualified Data.Vector as Vector
5450
55−import Data.OpenApi.Aeson.Compat (hasKey, keyToText, lookupKey, objectToList)
56−import Data.OpenApi.Declare
57−import Data.OpenApi.Internal
58−import Data.OpenApi.Internal.Schema
59−import Data.OpenApi.Internal.Utils
60−import Data.OpenApi.Lens
51+import Data.OpenApi.Declare
52+import Data.OpenApi.Internal
53+import Data.OpenApi.Internal.Schema
54+import Data.OpenApi.Lens
6155
6256-- | Validate @'ToJSON'@ instance matches @'ToSchema'@ for a given value.
6357-- This can be used with QuickCheck to ensure those instances are coherent:
@@ -109,33 +103,33 @@ validateToJSONWithPatternChecker checker = validateJSONWithPatternChecker checke
109103-- <BLANKLINE>
110104-- Swagger Schema:
111105-- {
112−-- "properties": {
113−-- "name": {
114−-- "type": "string"
115−-- },
116−-- "phone": {
117−-- "$ref": "#/components/schemas/Phone"
118−-- }
119−-- },
120106-- "required": [
121107-- "name",
122108-- "phone"
123109-- ],
124−-- "type": "object"
110+-- "type": "object",
111+-- "properties": {
112+-- "phone": {
113+-- "$ref": "#/components/schemas/Phone"
114+-- },
115+-- "name": {
116+-- "type": "string"
117+-- }
118+-- }
125119-- }
126120-- <BLANKLINE>
127121-- Swagger Description Context:
128122-- {
129123-- "Phone": {
124+-- "required": [
125+-- "value"
126+-- ],
127+-- "type": "object",
130128-- "properties": {
131129-- "value": {
132130-- "type": "string"
133131-- }
134−-- },
135−-- "required": [
136−-- "value"
137−-- ],
138−-- "type": "object"
132+-- }
139133-- }
140134-- }
141135-- <BLANKLINE>
@@ -321,11 +315,11 @@ validateNumber n = withConfig $ \_cfg -> withSchema $ \sch -> do
321315
322316 check maximum_ $ \m ->
323317 when (if exMax then (n >= m) else (n > m)) $
324− invalid ("value " ++ show n ++ " exceeds maximum (should be " ++ (if exMax then "<" else "<=") ++ show m ++ ")")
318+ invalid ("value " ++ show n ++ " exceeds maximum (should be " ++ if exMax then "<" else "<=" ++ show m ++ ")")
325319
326320 check minimum_ $ \m ->
327321 when (if exMin then (n <= m) else (n < m)) $
328− invalid ("value " ++ show n ++ " falls below minimum (should be " ++ (if exMin then ">" else ">=") ++ show m ++ ")")
322+ invalid ("value " ++ show n ++ " falls below minimum (should be " ++ if exMin then ">" else ">=" ++ show m ++ ")")
329323
330324 check multipleOf $ \k ->
331325 when (not (isInteger (n / k))) $
@@ -372,16 +366,10 @@ validateArray xs = do
372366 len = Vector.length xs
373367 allUnique = len == HashSet.size (HashSet.fromList (Vector.toList xs))
374368
375−validateObject ::
376−#if MIN_VERSION_aeson(2,0,0)
377− KeyMap.KeyMap Value
378−#else
379− HashMap Text Value
380−#endif
381− -> Validation Schema ()
369+validateObject :: HashMap Text Value -> Validation Schema ()
382370validateObject o = withSchema $ \sch ->
383371 case sch ^. discriminator of
384− Just (Discriminator pname types) -> case fromJSON <$> lookupKey pname o of
372+ Just (Discriminator pname types) -> case fromJSON <$> HashMap.lookup pname o of
385373 Just (Success pvalue) ->
386374 let ref = fromMaybe pvalue $ InsOrdHashMap.lookup pvalue types
387375 -- TODO ref may be name or reference
@@ -400,15 +388,15 @@ validateObject o = withSchema $ \sch ->
400388 validateRequired
401389 validateProps
402390 where
403− size = fromIntegral (length o)
391+ size = fromIntegral (HashMap.size o)
404392
405393 validateRequired = withSchema $ \sch -> traverse_ validateReq (sch ^. required)
406394 validateReq n =
407− when (not (hasKey n o)) $
395+ when (not (HashMap.member n o)) $
408396 invalid ("property " ++ show n ++ " is required, but not found in " ++ show (encode o))
409397
410398 validateProps = withSchema $ \sch -> do
411− for_ (objectToList o) $ \(keyToText -> k, v) ->
399+ for_ (HashMap.toList o) $ \(k, v) ->
412400 case v of
413401 Null | not (k `elem` (sch ^. required)) -> valid -- null is fine for non-required property
414402 _ ->
src/Data/OpenApi/Internal/Utils.hs−5
@@ -13,8 +13,6 @@ import Control.Lens ((&), (%~))
1313import Control.Lens.TH
1414import Data.Aeson
1515import Data.Aeson.Types
16−import qualified Data.Aeson.Encode.Pretty as P
17−import qualified Data.ByteString.Lazy as BSL
1816import Data.Char
1917import Data.Data
2018import Data.Hashable (Hashable)
@@ -134,6 +132,3 @@ instance SwaggerMonoid (Maybe a) where
134132 swaggerMempty = Nothing
135133 swaggerMappend x Nothing = x
136134 swaggerMappend _ y = y
137−
138−encodePretty :: ToJSON a => a -> BSL.ByteString
139−encodePretty = P.encodePretty' $ P.defConfig { P.confCompare = P.compare }
src/Data/OpenApi/Operation.hs+10−117
@@ -55,16 +55,13 @@ import qualified Data.HashSet.InsOrd as InsOrdHS
5555-- >>> import Data.Proxy
5656-- >>> import Data.Time
5757-- >>> import qualified Data.ByteString.Lazy.Char8 as BSL
58−-- >>> import Data.OpenApi.Internal.Utils
5958
6059-- | Prepend path piece to all operations of the spec.
6160-- Leading and trailing slashes are trimmed/added automatically.
6261--
6362-- >>> let api = (mempty :: OpenApi) & paths .~ [("/info", mempty)]
64−-- >>> BSL.putStrLn $ encodePretty $ prependPath "user/{user_id}" api ^. paths
65−-- {
66−-- "/user/{user_id}/info": {}
67−-- }
63+-- >>> BSL.putStrLn $ encode $ prependPath "user/{user_id}" api ^. paths
64+-- {"/user/{user_id}/info":{}}
6865prependPath :: FilePath -> OpenApi -> OpenApi
6966prependPath path = paths %~ InsOrdHashMap.mapKeys (path </>)
7067 where
@@ -85,63 +82,10 @@ allOperations = paths.traverse.template
8582-- >>> let ok = (mempty :: Operation) & at 200 ?~ "OK"
8683-- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ ok & post ?~ ok)]
8784-- >>> let sub = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)]
88−-- >>> BSL.putStrLn $ encodePretty api
89−-- {
90−-- "components": {},
91−-- "info": {
92−-- "title": "",
93−-- "version": ""
94−-- },
95−-- "openapi": "3.0.0",
96−-- "paths": {
97−-- "/user": {
98−-- "get": {
99−-- "responses": {
100−-- "200": {
101−-- "description": "OK"
102−-- }
103−-- }
104−-- },
105−-- "post": {
106−-- "responses": {
107−-- "200": {
108−-- "description": "OK"
109−-- }
110−-- }
111−-- }
112−-- }
113−-- }
114−-- }
115−-- >>> BSL.putStrLn $ encodePretty $ api & operationsOf sub . at 404 ?~ "Not found"
116−-- {
117−-- "components": {},
118−-- "info": {
119−-- "title": "",
120−-- "version": ""
121−-- },
122−-- "openapi": "3.0.0",
123−-- "paths": {
124−-- "/user": {
125−-- "get": {
126−-- "responses": {
127−-- "200": {
128−-- "description": "OK"
129−-- },
130−-- "404": {
131−-- "description": "Not found"
132−-- }
133−-- }
134−-- },
135−-- "post": {
136−-- "responses": {
137−-- "200": {
138−-- "description": "OK"
139−-- }
140−-- }
141−-- }
142−-- }
143−-- }
144−-- }
85+-- >>> BSL.putStrLn $ encode api
86+-- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/user":{"get":{"responses":{"200":{"description":"OK"}}},"post":{"responses":{"200":{"description":"OK"}}}}},"components":{}}
87+-- >>> BSL.putStrLn $ encode $ api & operationsOf sub . at 404 ?~ "Not found"
88+-- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/user":{"get":{"responses":{"404":{"description":"Not found"},"200":{"description":"OK"}}},"post":{"responses":{"200":{"description":"OK"}}}}},"components":{}}
14589operationsOf :: OpenApi -> Traversal' OpenApi Operation
14690operationsOf sub = paths.itraversed.withIndex.subops
14791 where
@@ -179,26 +123,8 @@ applyTagsFor ops ts swag = swag
179123--
180124-- FIXME doc
181125--
182−-- >>> BSL.putStrLn $ encodePretty $ runDeclare (declareResponse "application/json" (Proxy :: Proxy Day)) mempty
183−-- [
184−-- {
185−-- "Day": {
186−-- "example": "2016-07-22",
187−-- "format": "date",
188−-- "type": "string"
189−-- }
190−-- },
191−-- {
192−-- "content": {
193−-- "application/json": {
194−-- "schema": {
195−-- "$ref": "#/components/schemas/Day"
196−-- }
197−-- }
198−-- },
199−-- "description": ""
200−-- }
201−-- ]
126+-- >>> BSL.putStrLn $ encode $ runDeclare (declareResponse "application/json" (Proxy :: Proxy Day)) mempty
127+-- [{"Day":{"example":"2016-07-22","format":"date","type":"string"}},{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Day"}}}}]
202128declareResponse :: ToSchema a => MediaType -> Proxy a -> Declare (Definitions Schema) Response
203129declareResponse cType proxy = do
204130 s <- declareSchemaRef proxy
@@ -217,41 +143,8 @@ declareResponse cType proxy = do
217143--
218144-- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)]
219145-- >>> let res = declareResponse "application/json" (Proxy :: Proxy Day)
220−-- >>> BSL.putStrLn $ encodePretty $ api & setResponse 200 res
221−-- {
222−-- "components": {
223−-- "schemas": {
224−-- "Day": {
225−-- "example": "2016-07-22",
226−-- "format": "date",
227−-- "type": "string"
228−-- }
229−-- }
230−-- },
231−-- "info": {
232−-- "title": "",
233−-- "version": ""
234−-- },
235−-- "openapi": "3.0.0",
236−-- "paths": {
237−-- "/user": {
238−-- "get": {
239−-- "responses": {
240−-- "200": {
241−-- "content": {
242−-- "application/json": {
243−-- "schema": {
244−-- "$ref": "#/components/schemas/Day"
245−-- }
246−-- }
247−-- },
248−-- "description": ""
249−-- }
250−-- }
251−-- }
252−-- }
253−-- }
254−-- }
146+-- >>> BSL.putStrLn $ encode $ api & setResponse 200 res
147+-- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/user":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Day"}}},"description":""}}}}},"components":{"schemas":{"Day":{"example":"2016-07-22","format":"date","type":"string"}}}}
255148--
256149-- See also @'setResponseWith'@.
257150setResponse :: HttpStatusCode -> Declare (Definitions Schema) Response -> OpenApi -> OpenApi
src/Data/OpenApi/Optics.hs+9−54
@@ -22,88 +22,43 @@
2222-- Example from the "Data.OpenApi" module using @optics@:
2323--
2424-- >>> :{
25−-- BSL.putStrLn $ encodePretty $ (mempty :: OpenApi)
25+-- BSL.putStrLn $ encode $ (mempty :: OpenApi)
2626-- & #components % #schemas .~ [ ("User", mempty & #type ?~ OpenApiString) ]
2727-- & #paths .~
2828-- [ ("/user", mempty & #get ?~ (mempty
2929-- & at 200 ?~ ("OK" & #_Inline % #content % at "application/json" ?~ (mempty & #schema ?~ Ref (Reference "User")))
3030-- & at 404 ?~ "User info not found")) ]
3131-- :}
32−-- {
33−-- "components": {
34−-- "schemas": {
35−-- "User": {
36−-- "type": "string"
37−-- }
38−-- }
39−-- },
40−-- "info": {
41−-- "title": "",
42−-- "version": ""
43−-- },
44−-- "openapi": "3.0.0",
45−-- "paths": {
46−-- "/user": {
47−-- "get": {
48−-- "responses": {
49−-- "200": {
50−-- "content": {
51−-- "application/json": {
52−-- "schema": {
53−-- "$ref": "#/components/schemas/User"
54−-- }
55−-- }
56−-- },
57−-- "description": "OK"
58−-- },
59−-- "404": {
60−-- "description": "User info not found"
61−-- }
62−-- }
63−-- }
64−-- }
65−-- }
66−-- }
32+-- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/user":{"get":{"responses":{"404":{"description":"User info not found"},"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"OK"}}}}},"components":{"schemas":{"User":{"type":"string"}}}}
6733--
6834-- For convenience optics are defined as /labels/. It means that field accessor
6935-- names can be overloaded for different types. One such common field is
7036-- @#description@. Many components of a Swagger specification can have
7137-- descriptions, and you can use the same name for them:
7238--
73−-- >>> BSL.putStrLn $ encodePretty $ (mempty :: Response) & #description .~ "No content"
74−-- {
75−-- "description": "No content"
76−-- }
39+-- >>> BSL.putStrLn $ encode $ (mempty :: Response) & #description .~ "No content"
40+-- {"description":"No content"}
7741-- >>> :{
78−-- BSL.putStrLn $ encodePretty $ (mempty :: Schema)
42+-- BSL.putStrLn $ encode $ (mempty :: Schema)
7943-- & #type ?~ OpenApiBoolean
8044-- & #description ?~ "To be or not to be"
8145-- :}
82−-- {
83−-- "description": "To be or not to be",
84−-- "type": "boolean"
85−-- }
46+-- {"type":"boolean","description":"To be or not to be"}
8647--
8748-- Additionally, to simplify working with @'Response'@, both @'Operation'@ and
8849-- @'Responses'@ have direct access to it via @'Optics.Core.At.at'@. Example:
8950--
9051-- >>> :{
91−-- BSL.putStrLn $ encodePretty $ (mempty :: Operation)
52+-- BSL.putStrLn $ encode $ (mempty :: Operation)
9253-- & at 404 ?~ "Not found"
9354-- :}
94−-- {
95−-- "responses": {
96−-- "404": {
97−-- "description": "Not found"
98−-- }
99−-- }
100−-- }
55+-- {"responses":{"404":{"description":"Not found"}}}
56+--
10157module Data.OpenApi.Optics () where
10258
10359import Data.Aeson (Value)
10460import Data.Scientific (Scientific)
10561import Data.OpenApi.Internal
106−import Data.OpenApi.Internal.Utils
10762import Data.Text (Text)
10863import Optics.Core
10964import Optics.TH
src/Data/OpenApi/Schema/Generator.hs+1−3
@@ -25,8 +25,6 @@ import Test.QuickCheck (arbitrary)
2525import Test.QuickCheck.Gen
2626import Test.QuickCheck.Property
2727
28−import Data.OpenApi.Aeson.Compat (fromInsOrdHashMap)
29−
3028-- | Note: 'schemaGen' may 'error', if schema type is not specified,
3129-- and cannot be inferred.
3230schemaGen :: Definitions Schema -> Schema -> Gen Value
@@ -97,7 +95,7 @@ schemaGen defns schema =
9795 return . M.fromList $ zip additionalKeys (repeat . schemaGen defns $ dereference defns addlSchema)
9896 _ -> return []
9997 x <- sequence $ gens <> additionalGens
100− return . Object $ fromInsOrdHashMap x
98+ return . Object $ M.toHashMap x
10199
102100dereference :: Definitions a -> Referenced a -> a
103101dereference _ (Inline a) = a
stack.yaml+1−1
@@ -1,4 +1,4 @@
1−resolver: lts-16.31
1+resolver: lts-16.8
22packages:
33- '.'
44extra-deps:
test/Data/OpenApi/CommonTestTypes.hs−123
@@ -863,129 +863,6 @@ singleMaybeFieldSchemaJSON = [aesonQQ|
863863}
864864|]
865865
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−|]
989866
990867-- ========================================================================
991868-- TimeOfDay
test/Data/OpenApi/Schema/ValidationSpec.hs+3−20
@@ -1,4 +1,3 @@
1−{-# LANGUAGE CPP #-}
21{-# LANGUAGE DeriveGeneric #-}
32{-# LANGUAGE PackageImports #-}
43{-# LANGUAGE RecordWildCards #-}
@@ -9,10 +8,6 @@ module Data.OpenApi.Schema.ValidationSpec where
98import Control.Applicative
109import Control.Lens ((&), (.~), (?~))
1110import 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
1611import Data.Aeson.Types
1712import Data.Hashable (Hashable)
1813import Data.HashMap.Strict (HashMap)
@@ -35,7 +30,6 @@ import GHC.Generics
3530
3631import Data.OpenApi
3732import Data.OpenApi.Declare
38−import Data.OpenApi.Aeson.Compat (stringToKey)
3933
4034import Test.Hspec
4135import Test.Hspec.QuickCheck
@@ -130,9 +124,9 @@ instance Arbitrary Person where
130124
131125invalidPersonToJSON :: Person -> Value
132126invalidPersonToJSON Person{..} = object
133− [ stringToKey "personName" .= toJSON name
134− , stringToKey "personPhone" .= toJSON phone
135− , stringToKey "personEmail" .= toJSON email
127+ [ T.pack "personName" .= toJSON name
128+ , T.pack "personPhone" .= toJSON phone
129+ , T.pack "personEmail" .= toJSON email
136130 ]
137131
138132-- ========================================================================
@@ -301,8 +295,6 @@ instance Eq ZonedTime where
301295-- Arbitrary instance for Data.Aeson.Value
302296-- ========================================================================
303297
304−-- These instances were introduces in aeson upstream in 2.0.3
305−#if !MIN_VERSION_aeson(2,0,3)
306298instance Arbitrary Value where
307299 -- Weights are almost random
308300 -- Uniform oneof tends not to build complex objects cause of recursive call.
@@ -313,12 +305,3 @@ instance Arbitrary Value where
313305 , (3, Number <$> arbitrary)
314306 , (3, Bool <$> arbitrary)
315307 , (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,9 +59,6 @@ checkInlinedRecSchema proxy js = inlineNonRecursiveSchemas defs s <=> js
5959 where
6060 (defs, s) = runDeclare (declareSchema proxy) mempty
6161
62−checkToSchemaDeclare :: (HasCallStack, ToSchema a) => Proxy a -> Value -> Spec
63−checkToSchemaDeclare proxy js = runDeclare (declareSchemaRef proxy) mempty <=> js
64−
6562spec :: Spec
6663spec = do
6764 describe "Generic ToSchema" $ do
@@ -81,7 +78,6 @@ spec = do
8178 context "UserId (non-record newtype)" $ checkToSchema (Proxy :: Proxy UserId) userIdSchemaJSON
8279 context "Player (unary record)" $ checkToSchema (Proxy :: Proxy Player) playerSchemaJSON
8380 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
8581 context "Players (inlining schema)" $ checkToSchema (Proxy :: Proxy Players) playersSchemaJSON
8682 context "MyRoseTree (datatypeNameModifier)" $ checkToSchema (Proxy :: Proxy MyRoseTree) myRoseTreeSchemaJSON
8783 context "MyRoseTree' (datatypeNameModifier)" $ checkToSchema (Proxy :: Proxy MyRoseTree') myRoseTreeSchemaJSON'
test/Data/OpenApiSpec.hs+1−17
@@ -45,10 +45,6 @@ spec = do
4545 fromJSON petstoreExampleJSON `shouldSatisfy` (\x -> case x of Success (_ :: OpenApi) -> True; _ -> False)
4646 it "roundtrips: fmap toJSON . fromJSON" $ do
4747 (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
5248
5349main :: IO ()
5450main = hspec spec
@@ -445,7 +441,7 @@ responsesDefinitionExampleJSON = [aesonQQ|
445441|]
446442
447443-- =======================================================================
448−-- Security Definition object
444+-- Responses Definition object
449445-- =======================================================================
450446
451447securityDefinitionsExample :: SecurityDefinitions
@@ -531,18 +527,6 @@ oAuth2SecurityDefinitionsExampleJSON = [aesonQQ|
531527}
532528|]
533529
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−
546530-- =======================================================================
547531-- Swagger object
548532-- =======================================================================
test/SpecCommon.hs+8
@@ -8,6 +8,14 @@ import qualified Data.Vector as Vector
88
99import Test.Hspec
1010
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+
1119(<=>) :: (Eq a, Show a, ToJSON a, FromJSON a, HasCallStack) => a -> Value -> Spec
1220x <=> js = do
1321 it "encodes correctly" $ do