git.iko.soy/typeable/openapi3
on master help sign in
Switch to Github Actions, allow GHC 9.0.1 and hashable 1.3.1.0 (#18)
e4b27f3Maxim Koltsov
.github/workflows/haskell-ci.ymlnew file+177
@@ -0,0 +1,177 @@
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.12
12+#
13+# REGENDATA ("0.12",["github","cabal.project"])
14+#
15+name: Haskell-CI
16+on:
17+ - push
18+ - pull_request
19+jobs:
20+ linux:
21+ name: Haskell-CI - Linux - ${{ matrix.compiler }}
22+ runs-on: ubuntu-18.04
23+ container:
24+ image: buildpack-deps:bionic
25+ continue-on-error: ${{ matrix.allow-failure }}
26+ strategy:
27+ matrix:
28+ include:
29+ - compiler: ghc-9.0.1
30+ allow-failure: true
31+ - compiler: ghc-8.10.4
32+ allow-failure: false
33+ - compiler: ghc-8.8.4
34+ allow-failure: false
35+ - compiler: ghc-8.6.5
36+ allow-failure: false
37+ - compiler: ghc-8.4.4
38+ allow-failure: false
39+ fail-fast: false
40+ steps:
41+ - name: apt
42+ run: |
43+ apt-get update
44+ apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common
45+ apt-add-repository -y 'ppa:hvr/ghc'
46+ apt-get update
47+ apt-get install -y $CC cabal-install-3.4
48+ env:
49+ CC: ${{ matrix.compiler }}
50+ - name: Set PATH and environment variables
51+ run: |
52+ echo "$HOME/.cabal/bin" >> $GITHUB_PATH
53+ echo "LANG=C.UTF-8" >> $GITHUB_ENV
54+ echo "CABAL_DIR=$HOME/.cabal" >> $GITHUB_ENV
55+ echo "CABAL_CONFIG=$HOME/.cabal/config" >> $GITHUB_ENV
56+ HCDIR=$(echo "/opt/$CC" | sed 's/-/\//')
57+ HCNAME=ghc
58+ HC=$HCDIR/bin/$HCNAME
59+ echo "HC=$HC" >> $GITHUB_ENV
60+ echo "HCPKG=$HCDIR/bin/$HCNAME-pkg" >> $GITHUB_ENV
61+ echo "HADDOCK=$HCDIR/bin/haddock" >> $GITHUB_ENV
62+ echo "CABAL=/opt/cabal/3.4/bin/cabal -vnormal+nowrap" >> $GITHUB_ENV
63+ HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
64+ echo "HCNUMVER=$HCNUMVER" >> $GITHUB_ENV
65+ echo "ARG_TESTS=--enable-tests" >> $GITHUB_ENV
66+ echo "ARG_BENCH=--enable-benchmarks" >> $GITHUB_ENV
67+ echo "HEADHACKAGE=false" >> $GITHUB_ENV
68+ echo "ARG_COMPILER=--$HCNAME --with-compiler=$HC" >> $GITHUB_ENV
69+ echo "GHCJSARITH=0" >> $GITHUB_ENV
70+ env:
71+ CC: ${{ matrix.compiler }}
72+ - name: env
73+ run: |
74+ env
75+ - name: write cabal config
76+ run: |
77+ mkdir -p $CABAL_DIR
78+ cat >> $CABAL_CONFIG <<EOF
79+ remote-build-reporting: anonymous
80+ write-ghc-environment-files: never
81+ remote-repo-cache: $CABAL_DIR/packages
82+ logs-dir: $CABAL_DIR/logs
83+ world-file: $CABAL_DIR/world
84+ extra-prog-path: $CABAL_DIR/bin
85+ symlink-bindir: $CABAL_DIR/bin
86+ installdir: $CABAL_DIR/bin
87+ build-summary: $CABAL_DIR/logs/build.log
88+ store-dir: $CABAL_DIR/store
89+ install-dirs user
90+ prefix: $CABAL_DIR
91+ repository hackage.haskell.org
92+ url: http://hackage.haskell.org/
93+ EOF
94+ cat $CABAL_CONFIG
95+ - name: versions
96+ run: |
97+ $HC --version || true
98+ $HC --print-project-git-commit-id || true
99+ $CABAL --version || true
100+ - name: update cabal index
101+ run: |
102+ $CABAL v2-update -v
103+ - name: install cabal-plan
104+ run: |
105+ mkdir -p $HOME/.cabal/bin
106+ 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
107+ echo 'de73600b1836d3f55e32d80385acc055fd97f60eaa0ab68a755302685f5d81bc cabal-plan.xz' | sha256sum -c -
108+ xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan
109+ rm -f cabal-plan.xz
110+ chmod a+x $HOME/.cabal/bin/cabal-plan
111+ cabal-plan --version
112+ - name: checkout
113+ uses: actions/checkout@v2
114+ with:
115+ path: source
116+ - name: initial cabal.project for sdist
117+ run: |
118+ touch cabal.project
119+ echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project
120+ cat cabal.project
121+ - name: sdist
122+ run: |
123+ mkdir -p sdist
124+ $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist
125+ - name: unpack
126+ run: |
127+ mkdir -p unpacked
128+ find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \;
129+ - name: generate cabal.project
130+ run: |
131+ PKGDIR_openapi3="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/openapi3-[0-9.]*')"
132+ echo "PKGDIR_openapi3=${PKGDIR_openapi3}" >> $GITHUB_ENV
133+ touch cabal.project
134+ touch cabal.project.local
135+ echo "packages: ${PKGDIR_openapi3}" >> cabal.project
136+ echo "package openapi3" >> cabal.project
137+ echo " ghc-options: -Werror=missing-methods" >> cabal.project
138+ cat >> cabal.project <<EOF
139+ allow-newer: http-media:base
140+ EOF
141+ $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(openapi3)$/; }' >> cabal.project.local
142+ cat cabal.project
143+ cat cabal.project.local
144+ - name: dump install plan
145+ run: |
146+ $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all
147+ cabal-plan
148+ - name: cache
149+ uses: actions/cache@v2
150+ with:
151+ key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
152+ path: ~/.cabal/store
153+ restore-keys: ${{ runner.os }}-${{ matrix.compiler }}-
154+ - name: install dependencies
155+ run: |
156+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all
157+ $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all
158+ - name: build w/o tests
159+ run: |
160+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
161+ - name: build
162+ run: |
163+ $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always
164+ - name: tests
165+ run: |
166+ $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
167+ - name: cabal check
168+ run: |
169+ cd ${PKGDIR_openapi3} || false
170+ ${CABAL} -vnormal check
171+ - name: haddock
172+ run: |
173+ $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
174+ - name: unconstrained build
175+ run: |
176+ rm -f cabal.project.local
177+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
.travis.ymldeleted−149
@@ -1,149 +0,0 @@
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+5
@@ -1,3 +1,8 @@
1+Unreleased
2+----------
3+
4+- Allow `hashable-1.3.1`, prettify doctests.
5+
13.0.2.0
63.0.2.0
2-------
7-------
3
8
cabal.project+2
@@ -1,2 +1,4 @@
1packages: .
1packages: .
2tests: true
2tests: true
3+
4+allow-newer: http-media:base
openapi3.cabal+6−7
@@ -25,8 +25,9 @@ extra-source-files:
25tested-with:
25tested-with:
26 GHC ==8.4.4
26 GHC ==8.4.4
27 || ==8.6.5
27 || ==8.6.5
28− || ==8.8.3
28+ || ==8.8.4
29− || ==8.10.1
29+ || ==8.10.4
30+ || ==9.0.1
30
31
31custom-setup
32custom-setup
32 setup-depends:
33 setup-depends:
@@ -57,10 +58,10 @@ library
57
58
58 -- GHC boot libraries
59 -- GHC boot libraries
59 build-depends:
60 build-depends:
60− base >=4.11.1.0 && <4.15
61+ base >=4.11.1.0 && <4.16
61 , bytestring >=0.10.8.2 && <0.11
62 , bytestring >=0.10.8.2 && <0.11
62 , containers >=0.5.11.0 && <0.7
63 , containers >=0.5.11.0 && <0.7
63− , template-haskell >=2.13.0.0 && <2.17
64+ , template-haskell >=2.13.0.0 && <2.18
64 , time >=1.8.0.2 && <1.10
65 , time >=1.8.0.2 && <1.10
65 , transformers >=0.5.5.0 && <0.6
66 , transformers >=0.5.5.0 && <0.6
66
67
@@ -76,9 +77,7 @@ library
76 -- cookie 0.4.3 is needed by GHC 7.8 due to time>=1.4 constraint
77 -- cookie 0.4.3 is needed by GHC 7.8 due to time>=1.4 constraint
77 , cookie >=0.4.3 && <0.5
78 , cookie >=0.4.3 && <0.5
78 , generics-sop >=0.5.1.0 && <0.6
79 , generics-sop >=0.5.1.0 && <0.6
79− -- <1.3.1 is a temporary measure, until we fix doctests not to depend on key order
80− -- see https://github.com/haskell/aeson/issues/837
81− , hashable >=1.2.7.0 && <1.3.1
80+ , hashable >=1.2.7.0 && <1.4
82 , http-media >=0.8.0.0 && <0.9
81 , http-media >=0.8.0.0 && <0.9
83 , insert-ordered-containers >=0.2.3 && <0.3
82 , insert-ordered-containers >=0.2.3 && <0.3
84 , lens >=4.16.1 && <5.1
83 , lens >=4.16.1 && <5.1
src/Data/OpenApi.hs+140−20
@@ -137,6 +137,7 @@ import Data.OpenApi.Internal
137-- >>> import Data.Proxy
137-- >>> import Data.Proxy
138-- >>> import GHC.Generics
138-- >>> import GHC.Generics
139-- >>> import qualified Data.ByteString.Lazy.Char8 as BSL
139-- >>> import qualified Data.ByteString.Lazy.Char8 as BSL
140+-- >>> import Data.OpenApi.Internal.Utils
140-- >>> :set -XDeriveGeneric
141-- >>> :set -XDeriveGeneric
141-- >>> :set -XOverloadedStrings
142-- >>> :set -XOverloadedStrings
142-- >>> :set -XOverloadedLists
143-- >>> :set -XOverloadedLists
@@ -153,8 +154,16 @@ import Data.OpenApi.Internal
153--
154--
154-- In this library you can use @'mempty'@ for a default/empty value. For instance:
155-- In this library you can use @'mempty'@ for a default/empty value. For instance:
155--
156--
156−-- >>> BSL.putStrLn $ encode (mempty :: OpenApi)
157+-- >>> BSL.putStrLn $ encodePretty (mempty :: OpenApi)
158+-- {
159+-- "components": {},
160+-- "info": {
161+-- "title": "",
162+-- "version": ""
163+-- },
157−-- {"openapi":"3.0.0","info":{"version":"","title":""},"components":{}}
164+-- "openapi": "3.0.0",
165+-- "paths": {}
166+-- }
158--
167--
159-- As you can see some spec properties (e.g. @"version"@) are there even when the spec is empty.
168-- As you can see some spec properties (e.g. @"version"@) are there even when the spec is empty.
160-- That is because these properties are actually required ones.
169-- That is because these properties are actually required ones.
@@ -162,13 +171,20 @@ import Data.OpenApi.Internal
162-- You /should/ always override the default (empty) value for these properties,
171-- You /should/ always override the default (empty) value for these properties,
163-- although it is not strictly necessary:
172-- although it is not strictly necessary:
164--
173--
165−-- >>> BSL.putStrLn $ encode mempty { _infoTitle = "Todo API", _infoVersion = "1.0" }
174+-- >>> BSL.putStrLn $ encodePretty mempty { _infoTitle = "Todo API", _infoVersion = "1.0" }
175+-- {
176+-- "title": "Todo API",
166−-- {"version":"1.0","title":"Todo API"}
177+-- "version": "1.0"
178+-- }
167--
179--
168-- You can merge two values using @'mappend'@ or its infix version @('<>')@:
180-- You can merge two values using @'mappend'@ or its infix version @('<>')@:
169--
181--
170−-- >>> BSL.putStrLn $ encode $ mempty { _infoTitle = "Todo API" } <> mempty { _infoVersion = "1.0" }
182+-- >>> BSL.putStrLn $ encodePretty $ mempty { _infoTitle = "Todo API" } <> mempty { _infoVersion = "1.0" }
171−-- {"version":"1.0","title":"Todo API"}
183+-- {
184+-- "title": "Todo API",
185+-- "version": "1.0"
186+-- }
187+
172--
188--
173-- This can be useful for combining specifications of endpoints into a whole API specification:
189-- This can be useful for combining specifications of endpoints into a whole API specification:
174--
190--
@@ -194,14 +210,48 @@ import Data.OpenApi.Internal
194-- make it fairly simple to construct/modify any part of the specification:
210-- make it fairly simple to construct/modify any part of the specification:
195--
211--
196-- >>> :{
212-- >>> :{
197−-- BSL.putStrLn $ encode $ (mempty :: OpenApi)
213+-- BSL.putStrLn $ encodePretty $ (mempty :: OpenApi)
198-- & components . schemas .~ [ ("User", mempty & type_ ?~ OpenApiString) ]
214-- & components . schemas .~ [ ("User", mempty & type_ ?~ OpenApiString) ]
199-- & paths .~
215-- & paths .~
200-- [ ("/user", mempty & get ?~ (mempty
216-- [ ("/user", mempty & get ?~ (mempty
201-- & at 200 ?~ ("OK" & _Inline.content.at "application/json" ?~ (mempty & schema ?~ Ref (Reference "User")))
217-- & at 200 ?~ ("OK" & _Inline.content.at "application/json" ?~ (mempty & schema ?~ Ref (Reference "User")))
202-- & at 404 ?~ "User info not found")) ]
218-- & at 404 ?~ "User info not found")) ]
203-- :}
219-- :}
204−-- {"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"}}}}
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+-- }
205--
255--
206-- In the snippet above we declare an API with a single path @/user@. This path provides method @GET@
256-- In the snippet above we declare an API with a single path @/user@. This path provides method @GET@
207-- which produces @application/json@ output. It should respond with code @200@ and body specified
257-- which produces @application/json@ output. It should respond with code @200@ and body specified
@@ -213,23 +263,34 @@ import Data.OpenApi.Internal
213-- common field is @'description'@. Many components of a Swagger specification
263-- common field is @'description'@. Many components of a Swagger specification
214-- can have descriptions, and you can use the same name for them:
264-- can have descriptions, and you can use the same name for them:
215--
265--
216−-- >>> BSL.putStrLn $ encode $ (mempty :: Response) & description .~ "No content"
266+-- >>> BSL.putStrLn $ encodePretty $ (mempty :: Response) & description .~ "No content"
267+-- {
217−-- {"description":"No content"}
268+-- "description": "No content"
269+-- }
218-- >>> :{
270-- >>> :{
219−-- BSL.putStrLn $ encode $ (mempty :: Schema)
271+-- BSL.putStrLn $ encodePretty $ (mempty :: Schema)
220-- & type_ ?~ OpenApiBoolean
272-- & type_ ?~ OpenApiBoolean
221-- & description ?~ "To be or not to be"
273-- & description ?~ "To be or not to be"
222-- :}
274-- :}
275+-- {
223−-- {"type":"boolean","description":"To be or not to be"}
276+-- "description": "To be or not to be",
277+-- "type": "boolean"
278+-- }
224--
279--
225-- Additionally, to simplify working with @'Response'@, both @'Operation'@ and @'Responses'@
280-- Additionally, to simplify working with @'Response'@, both @'Operation'@ and @'Responses'@
226-- have direct access to it via @'at' code@. Example:
281-- have direct access to it via @'at' code@. Example:
227--
282--
228-- >>> :{
283-- >>> :{
229−-- BSL.putStrLn $ encode $ (mempty :: Operation)
284+-- BSL.putStrLn $ encodePretty $ (mempty :: Operation)
230-- & at 404 ?~ "Not found"
285-- & at 404 ?~ "Not found"
231-- :}
286-- :}
287+-- {
288+-- "responses": {
289+-- "404": {
232−-- {"responses":{"404":{"description":"Not found"}}}
290+-- "description": "Not found"
291+-- }
292+-- }
293+-- }
233--
294--
234-- You might've noticed that @'type_'@ has an extra underscore in its name
295-- You might've noticed that @'type_'@ has an extra underscore in its name
235-- compared to, say, @'description'@ field accessor.
296-- compared to, say, @'description'@ field accessor.
@@ -276,10 +337,27 @@ import Data.OpenApi.Internal
276-- >>> data Person = Person { name :: String, age :: Integer } deriving Generic
337-- >>> data Person = Person { name :: String, age :: Integer } deriving Generic
277-- >>> instance ToJSON Person
338-- >>> instance ToJSON Person
278-- >>> instance ToSchema Person
339-- >>> instance ToSchema Person
279−-- >>> BSL.putStrLn $ encode (Person "David" 28)
340+-- >>> BSL.putStrLn $ encodePretty (Person "David" 28)
341+-- {
342+-- "age": 28,
280−-- {"age":28,"name":"David"}
343+-- "name": "David"
344+-- }
281−-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Person)
345+-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy Person)
282−-- {"required":["name","age"],"type":"object","properties":{"age":{"type":"integer"},"name":{"type":"string"}}}
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+-- }
283--
361--
284-- This package implements OpenAPI 3.0 spec, which supports @oneOf@ in schemas, allowing any sum types
362-- This package implements OpenAPI 3.0 spec, which supports @oneOf@ in schemas, allowing any sum types
285-- to be faithfully represented. All sum encodings supported by @aeson@ are supported here as well, with
363-- to be faithfully represented. All sum encodings supported by @aeson@ are supported here as well, with
@@ -290,8 +368,50 @@ import Data.OpenApi.Internal
290-- >>> data Error = ErrorNoUser { userId :: Int } | ErrorAccessDenied { requiredPermission :: String } deriving Generic
368-- >>> data Error = ErrorNoUser { userId :: Int } | ErrorAccessDenied { requiredPermission :: String } deriving Generic
291-- >>> instance ToJSON Error
369-- >>> instance ToJSON Error
292-- >>> instance ToSchema Error
370-- >>> instance ToSchema Error
293−-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Error)
371+-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy Error)
294−-- {"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"}
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+-- }
295
415
296-- $manipulation
416-- $manipulation
297-- Sometimes you have to work with an imported or generated @'Swagger'@.
417-- Sometimes you have to work with an imported or generated @'Swagger'@.
src/Data/OpenApi/Internal.hs+35−12
@@ -60,6 +60,8 @@ import Data.OpenApi.Internal.AesonUtils (sopSwaggerGenericToEncoding)
60-- $setup
60-- $setup
61-- >>> :seti -XDataKinds
61-- >>> :seti -XDataKinds
62-- >>> import Data.Aeson
62-- >>> import Data.Aeson
63+-- >>> import Data.ByteString.Lazy.Char8 as BSL
64+-- >>> import Data.OpenApi.Internal.Utils
63
65
64-- | A list of definitions that can be used in references.
66-- | A list of definitions that can be used in references.
65type Definitions = InsOrdHashMap Text
67type Definitions = InsOrdHashMap Text
@@ -848,20 +850,37 @@ data HttpSchemeType
848
850
849-- |
851-- |
850--
852--
851−-- >>> encode (SecuritySchemeHttp (HttpSchemeBearer Nothing))
853+-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeHttp (HttpSchemeBearer Nothing))
854+-- {
852−-- "{\"scheme\":\"bearer\",\"type\":\"http\"}"
855+-- "scheme": "bearer",
856+-- "type": "http"
857+-- }
853--
858--
854−-- >>> encode (SecuritySchemeHttp (HttpSchemeBearer (Just "jwt")))
859+-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeHttp (HttpSchemeBearer (Just "jwt")))
860+-- {
861+-- "bearerFormat": "jwt",
855−-- "{\"scheme\":\"bearer\",\"type\":\"http\",\"bearerFormat\":\"jwt\"}"
862+-- "scheme": "bearer",
863+-- "type": "http"
864+-- }
856--
865--
857−-- >>> encode (SecuritySchemeHttp HttpSchemeBasic)
866+-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeHttp HttpSchemeBasic)
867+-- {
858−-- "{\"scheme\":\"basic\",\"type\":\"http\"}"
868+-- "scheme": "basic",
869+-- "type": "http"
870+-- }
859--
871--
860−-- >>> encode (SecuritySchemeHttp (HttpSchemeCustom "CANARY"))
872+-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeHttp (HttpSchemeCustom "CANARY"))
873+-- {
861−-- "{\"scheme\":\"CANARY\",\"type\":\"http\"}"
874+-- "scheme": "CANARY",
875+-- "type": "http"
876+-- }
862--
877--
863−-- >>> encode (SecuritySchemeApiKey (ApiKeyParams "id" ApiKeyCookie))
878+-- >>> BSL.putStrLn $ encodePretty (SecuritySchemeApiKey (ApiKeyParams "id" ApiKeyCookie))
879+-- {
880+-- "in": "cookie",
881+-- "name": "id",
864−-- "{\"in\":\"cookie\",\"name\":\"id\",\"type\":\"apiKey\"}"
882+-- "type": "apiKey"
883+-- }
865--
884--
866data SecuritySchemeType
885data SecuritySchemeType
867 = SecuritySchemeHttp HttpSchemeType
886 = SecuritySchemeHttp HttpSchemeType
@@ -1305,8 +1324,12 @@ instance ToJSON Header where
1305-- | As for nullary schema for 0-arity type constructors, see
1324-- | As for nullary schema for 0-arity type constructors, see
1306-- <https://github.com/GetShopTV/swagger2/issues/167>.
1325-- <https://github.com/GetShopTV/swagger2/issues/167>.
1307--
1326--
1308−-- >>> encode (OpenApiItemsArray [])
1327+-- >>> BSL.putStrLn $ encodePretty (OpenApiItemsArray [])
1328+-- {
1329+-- "example": [],
1330+-- "items": {},
1309−-- "{\"example\":[],\"items\":{},\"maxItems\":0}"
1331+-- "maxItems": 0
1332+-- }
1310--
1333--
1311instance ToJSON OpenApiItems where
1334instance ToJSON OpenApiItems where
1312 toJSON (OpenApiItemsObject x) = object [ "items" .= x ]
1335 toJSON (OpenApiItemsObject x) = object [ "items" .= x ]
src/Data/OpenApi/Internal/ParamSchema.hs+27−9
@@ -46,7 +46,7 @@ import Data.OpenApi.Lens
46import Data.OpenApi.SchemaOptions
46import Data.OpenApi.SchemaOptions
47
47
48import qualified Data.ByteString as BS
48import qualified Data.ByteString as BS
49−import qualified Data.ByteString.Lazy as BSL
49+import qualified Data.ByteString.Lazy.Char8 as BSL
50import GHC.TypeLits (TypeError, ErrorMessage(..))
50import GHC.TypeLits (TypeError, ErrorMessage(..))
51
51
52-- | Default schema for binary data (any sequence of octets).
52-- | Default schema for binary data (any sequence of octets).
@@ -112,8 +112,10 @@ passwordSchema = mempty
112class ToParamSchema a where
112class ToParamSchema a where
113 -- | Convert a type into a plain parameter schema.
113 -- | Convert a type into a plain parameter schema.
114 --
114 --
115− -- >>> encode $ toParamSchema (Proxy :: Proxy Integer)
115+ -- >>> BSL.putStrLn $ encodePretty $ toParamSchema (Proxy :: Proxy Integer)
116+ -- {
116− -- "{\"type\":\"integer\"}"
117+ -- "type": "integer"
118+ -- }
117 toParamSchema :: Proxy a -> Schema
119 toParamSchema :: Proxy a -> Schema
118 default toParamSchema :: (Generic a, GToParamSchema (Rep a)) => Proxy a -> Schema
120 default toParamSchema :: (Generic a, GToParamSchema (Rep a)) => Proxy a -> Schema
119 toParamSchema = genericToParamSchema defaultSchemaOptions
121 toParamSchema = genericToParamSchema defaultSchemaOptions
@@ -155,8 +157,12 @@ instance ToParamSchema Word64 where
155
157
156-- | Default plain schema for @'Bounded'@, @'Integral'@ types.
158-- | Default plain schema for @'Bounded'@, @'Integral'@ types.
157--
159--
158−-- >>> encode $ toParamSchemaBoundedIntegral (Proxy :: Proxy Int8)
160+-- >>> BSL.putStrLn $ encodePretty $ toParamSchemaBoundedIntegral (Proxy :: Proxy Int8)
161+-- {
162+-- "maximum": 127,
163+-- "minimum": -128,
159−-- "{\"maximum\":127,\"minimum\":-128,\"type\":\"integer\"}"
164+-- "type": "integer"
165+-- }
160toParamSchemaBoundedIntegral :: forall a t. (Bounded a, Integral a) => Proxy a -> Schema
166toParamSchemaBoundedIntegral :: forall a t. (Bounded a, Integral a) => Proxy a -> Schema
161toParamSchemaBoundedIntegral _ = mempty
167toParamSchemaBoundedIntegral _ = mempty
162 & type_ ?~ OpenApiInteger
168 & type_ ?~ OpenApiInteger
@@ -275,8 +281,13 @@ instance ToParamSchema a => ToParamSchema (HashSet a) where
275 toParamSchema _ = toParamSchema (Proxy :: Proxy (Set a))
281 toParamSchema _ = toParamSchema (Proxy :: Proxy (Set a))
276
282
277-- |
283-- |
278−-- >>> encode $ toParamSchema (Proxy :: Proxy ())
284+-- >>> BSL.putStrLn $ encodePretty $ toParamSchema (Proxy :: Proxy ())
285+-- {
279−-- "{\"type\":\"string\",\"enum\":[\"_\"]}"
286+-- "enum": [
287+-- "_"
288+-- ],
289+-- "type": "string"
290+-- }
280instance ToParamSchema () where
291instance ToParamSchema () where
281 toParamSchema _ = mempty
292 toParamSchema _ = mempty
282 & type_ ?~ OpenApiString
293 & type_ ?~ OpenApiString
@@ -291,8 +302,14 @@ instance ToParamSchema UUID where
291--
302--
292-- >>> :set -XDeriveGeneric
303-- >>> :set -XDeriveGeneric
293-- >>> data Color = Red | Blue deriving Generic
304-- >>> data Color = Red | Blue deriving Generic
294−-- >>> encode $ genericToParamSchema defaultSchemaOptions (Proxy :: Proxy Color)
305+-- >>> BSL.putStrLn $ encodePretty $ genericToParamSchema defaultSchemaOptions (Proxy :: Proxy Color)
295−-- "{\"type\":\"string\",\"enum\":[\"Red\",\"Blue\"]}"
306+-- {
307+-- "enum": [
308+-- "Red",
309+-- "Blue"
310+-- ],
311+-- "type": "string"
312+-- }
296genericToParamSchema :: forall a t. (Generic a, GToParamSchema (Rep a)) => SchemaOptions -> Proxy a -> Schema
313genericToParamSchema :: forall a t. (Generic a, GToParamSchema (Rep a)) => SchemaOptions -> Proxy a -> Schema
297genericToParamSchema opts _ = gtoParamSchema opts (Proxy :: Proxy (Rep a)) mempty
314genericToParamSchema opts _ = gtoParamSchema opts (Proxy :: Proxy (Rep a)) mempty
298
315
@@ -334,3 +351,4 @@ data Proxy3 a b c = Proxy3
334
351
335-- $setup
352-- $setup
336-- >>> import Data.Aeson (encode)
353-- >>> import Data.Aeson (encode)
354+-- >>> import Data.OpenApi.Internal.Utils
src/Data/OpenApi/Internal/Schema.hs+267−38
@@ -42,6 +42,7 @@ import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
42import Data.Int
42import Data.Int
43import Data.IntSet (IntSet)
43import Data.IntSet (IntSet)
44import Data.IntMap (IntMap)
44import Data.IntMap (IntMap)
45+import Data.List (sort)
45import Data.List.NonEmpty.Compat (NonEmpty)
46import Data.List.NonEmpty.Compat (NonEmpty)
46import Data.Map (Map)
47import Data.Map (Map)
47import Data.Maybe (fromMaybe)
48import Data.Maybe (fromMaybe)
@@ -156,13 +157,19 @@ declareSchema = fmap _namedSchemaSchema . declareNamedSchema
156--
157--
157-- >>> toNamedSchema (Proxy :: Proxy String) ^. name
158-- >>> toNamedSchema (Proxy :: Proxy String) ^. name
158-- Nothing
159-- Nothing
159−-- >>> BSL.putStrLn $ encode (toNamedSchema (Proxy :: Proxy String) ^. schema)
160+-- >>> BSL.putStrLn $ encodePretty (toNamedSchema (Proxy :: Proxy String) ^. schema)
161+-- {
160−-- {"type":"string"}
162+-- "type": "string"
163+-- }
161--
164--
162-- >>> toNamedSchema (Proxy :: Proxy Day) ^. name
165-- >>> toNamedSchema (Proxy :: Proxy Day) ^. name
163-- Just "Day"
166-- Just "Day"
164−-- >>> BSL.putStrLn $ encode (toNamedSchema (Proxy :: Proxy Day) ^. schema)
167+-- >>> BSL.putStrLn $ encodePretty (toNamedSchema (Proxy :: Proxy Day) ^. schema)
168+-- {
165−-- {"example":"2016-07-22","format":"date","type":"string"}
169+-- "example": "2016-07-22",
170+-- "format": "date",
171+-- "type": "string"
172+-- }
166toNamedSchema :: ToSchema a => Proxy a -> NamedSchema
173toNamedSchema :: ToSchema a => Proxy a -> NamedSchema
167toNamedSchema = undeclare . declareNamedSchema
174toNamedSchema = undeclare . declareNamedSchema
168
175
@@ -178,22 +185,35 @@ schemaName = _namedSchemaName . toNamedSchema
178
185
179-- | Convert a type into a schema.
186-- | Convert a type into a schema.
180--
187--
181−-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Int8)
188+-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy Int8)
189+-- {
190+-- "maximum": 127,
191+-- "minimum": -128,
182−-- {"maximum":127,"minimum":-128,"type":"integer"}
192+-- "type": "integer"
193+-- }
183--
194--
184−-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy [Day])
195+-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy [Day])
196+-- {
197+-- "items": {
185−-- {"items":{"$ref":"#/components/schemas/Day"},"type":"array"}
198+-- "$ref": "#/components/schemas/Day"
199+-- },
200+-- "type": "array"
201+-- }
186toSchema :: ToSchema a => Proxy a -> Schema
202toSchema :: ToSchema a => Proxy a -> Schema
187toSchema = _namedSchemaSchema . toNamedSchema
203toSchema = _namedSchemaSchema . toNamedSchema
188
204
189-- | Convert a type into a referenced schema if possible.
205-- | Convert a type into a referenced schema if possible.
190-- Only named schemas can be referenced, nameless schemas are inlined.
206-- Only named schemas can be referenced, nameless schemas are inlined.
191--
207--
192−-- >>> BSL.putStrLn $ encode $ toSchemaRef (Proxy :: Proxy Integer)
208+-- >>> BSL.putStrLn $ encodePretty $ toSchemaRef (Proxy :: Proxy Integer)
209+-- {
193−-- {"type":"integer"}
210+-- "type": "integer"
211+-- }
194--
212--
195−-- >>> BSL.putStrLn $ encode $ toSchemaRef (Proxy :: Proxy Day)
213+-- >>> BSL.putStrLn $ encodePretty $ toSchemaRef (Proxy :: Proxy Day)
214+-- {
196−-- {"$ref":"#/components/schemas/Day"}
215+-- "$ref": "#/components/schemas/Day"
216+-- }
197toSchemaRef :: ToSchema a => Proxy a -> Referenced Schema
217toSchemaRef :: ToSchema a => Proxy a -> Referenced Schema
198toSchemaRef = undeclare . declareSchemaRef
218toSchemaRef = undeclare . declareSchemaRef
199
219
@@ -261,8 +281,15 @@ inlineAllSchemas = inlineSchemasWhen (const True)
261
281
262-- | Convert a type into a schema without references.
282-- | Convert a type into a schema without references.
263--
283--
264−-- >>> BSL.putStrLn $ encode $ toInlinedSchema (Proxy :: Proxy [Day])
284+-- >>> BSL.putStrLn $ encodePretty $ toInlinedSchema (Proxy :: Proxy [Day])
285+-- {
286+-- "items": {
265−-- {"items":{"example":"2016-07-22","format":"date","type":"string"},"type":"array"}
287+-- "example": "2016-07-22",
288+-- "format": "date",
289+-- "type": "string"
290+-- },
291+-- "type": "array"
292+-- }
266--
293--
267-- __WARNING:__ @'toInlinedSchema'@ will produce infinite schema
294-- __WARNING:__ @'toInlinedSchema'@ will produce infinite schema
268-- when inlining recursive schemas.
295-- when inlining recursive schemas.
@@ -295,19 +322,64 @@ inlineNonRecursiveSchemas defs = inlineSchemasWhen nonRecursive defs
295-- | Make an unrestrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
322-- | Make an unrestrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
296-- Produced schema can be used for further refinement.
323-- Produced schema can be used for further refinement.
297--
324--
298−-- >>> BSL.putStrLn $ encode $ sketchSchema "hello"
325+-- >>> BSL.putStrLn $ encodePretty $ sketchSchema "hello"
326+-- {
299−-- {"example":"hello","type":"string"}
327+-- "example": "hello",
328+-- "type": "string"
329+-- }
300--
330--
301−-- >>> BSL.putStrLn $ encode $ sketchSchema (1, 2, 3)
331+-- >>> BSL.putStrLn $ encodePretty $ sketchSchema (1, 2, 3)
332+-- {
333+-- "example": [
334+-- 1,
335+-- 2,
336+-- 3
337+-- ],
338+-- "items": {
302−-- {"example":[1,2,3],"items":{"type":"number"},"type":"array"}
339+-- "type": "number"
340+-- },
341+-- "type": "array"
342+-- }
303--
343--
304−-- >>> BSL.putStrLn $ encode $ sketchSchema ("Jack", 25)
344+-- >>> BSL.putStrLn $ encodePretty $ sketchSchema ("Jack", 25)
305−-- {"example":["Jack",25],"items":[{"type":"string"},{"type":"number"}],"type":"array"}
345+-- {
346+-- "example": [
347+-- "Jack",
348+-- 25
349+-- ],
350+-- "items": [
351+-- {
352+-- "type": "string"
353+-- },
354+-- {
355+-- "type": "number"
356+-- }
357+-- ],
358+-- "type": "array"
359+-- }
306--
360--
307-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
361-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
308-- >>> instance ToJSON Person
362-- >>> instance ToJSON Person
309−-- >>> BSL.putStrLn $ encode $ sketchSchema (Person "Jack" 25)
363+-- >>> BSL.putStrLn $ encodePretty $ sketchSchema (Person "Jack" 25)
310−-- {"example":{"age":25,"name":"Jack"},"required":["age","name"],"type":"object","properties":{"age":{"type":"number"},"name":{"type":"string"}}}
364+-- {
365+-- "example": {
366+-- "age": 25,
367+-- "name": "Jack"
368+-- },
369+-- "properties": {
370+-- "age": {
371+-- "type": "number"
372+-- },
373+-- "name": {
374+-- "type": "string"
375+-- }
376+-- },
377+-- "required": [
378+-- "age",
379+-- "name"
380+-- ],
381+-- "type": "object"
382+-- }
311sketchSchema :: ToJSON a => a -> Schema
383sketchSchema :: ToJSON a => a -> Schema
312sketchSchema = sketch . toJSON
384sketchSchema = sketch . toJSON
313 where
385 where
@@ -333,25 +405,139 @@ sketchSchema = sketch . toJSON
333 _ -> Nothing
405 _ -> Nothing
334 go (Object o) = mempty
406 go (Object o) = mempty
335 & type_ ?~ OpenApiObject
407 & type_ ?~ OpenApiObject
336− & required .~ HashMap.keys o
408+ & required .~ sort (HashMap.keys o)
337 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
409 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
338
410
339-- | Make a restrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
411-- | Make a restrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
340-- Produced schema uses as much constraints as possible.
412-- Produced schema uses as much constraints as possible.
341--
413--
342−-- >>> BSL.putStrLn $ encode $ sketchStrictSchema "hello"
414+-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema "hello"
343−-- {"maxLength":5,"pattern":"hello","minLength":5,"type":"string","enum":["hello"]}
415+-- {
416+-- "enum": [
417+-- "hello"
418+-- ],
419+-- "maxLength": 5,
420+-- "minLength": 5,
421+-- "pattern": "hello",
422+-- "type": "string"
423+-- }
344--
424--
345−-- >>> BSL.putStrLn $ encode $ sketchStrictSchema (1, 2, 3)
425+-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema (1, 2, 3)
346−-- {"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]]}
426+-- {
427+-- "enum": [
428+-- [
429+-- 1,
430+-- 2,
431+-- 3
432+-- ]
433+-- ],
434+-- "items": [
435+-- {
436+-- "enum": [
437+-- 1
438+-- ],
439+-- "maximum": 1,
440+-- "minimum": 1,
441+-- "multipleOf": 1,
442+-- "type": "number"
443+-- },
444+-- {
445+-- "enum": [
446+-- 2
447+-- ],
448+-- "maximum": 2,
449+-- "minimum": 2,
450+-- "multipleOf": 2,
451+-- "type": "number"
452+-- },
453+-- {
454+-- "enum": [
455+-- 3
456+-- ],
457+-- "maximum": 3,
458+-- "minimum": 3,
459+-- "multipleOf": 3,
460+-- "type": "number"
461+-- }
462+-- ],
463+-- "maxItems": 3,
464+-- "minItems": 3,
465+-- "type": "array",
466+-- "uniqueItems": true
467+-- }
347--
468--
348−-- >>> BSL.putStrLn $ encode $ sketchStrictSchema ("Jack", 25)
469+-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema ("Jack", 25)
349−-- {"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]]}
470+-- {
471+-- "enum": [
472+-- [
473+-- "Jack",
474+-- 25
475+-- ]
476+-- ],
477+-- "items": [
478+-- {
479+-- "enum": [
480+-- "Jack"
481+-- ],
482+-- "maxLength": 4,
483+-- "minLength": 4,
484+-- "pattern": "Jack",
485+-- "type": "string"
486+-- },
487+-- {
488+-- "enum": [
489+-- 25
490+-- ],
491+-- "maximum": 25,
492+-- "minimum": 25,
493+-- "multipleOf": 25,
494+-- "type": "number"
495+-- }
496+-- ],
497+-- "maxItems": 2,
498+-- "minItems": 2,
499+-- "type": "array",
500+-- "uniqueItems": true
501+-- }
350--
502--
351-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
503-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
352-- >>> instance ToJSON Person
504-- >>> instance ToJSON Person
353−-- >>> BSL.putStrLn $ encode $ sketchStrictSchema (Person "Jack" 25)
505+-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema (Person "Jack" 25)
354−-- {"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"]}}}
506+-- {
507+-- "enum": [
508+-- {
509+-- "age": 25,
510+-- "name": "Jack"
511+-- }
512+-- ],
513+-- "maxProperties": 2,
514+-- "minProperties": 2,
515+-- "properties": {
516+-- "age": {
517+-- "enum": [
518+-- 25
519+-- ],
520+-- "maximum": 25,
521+-- "minimum": 25,
522+-- "multipleOf": 25,
523+-- "type": "number"
524+-- },
525+-- "name": {
526+-- "enum": [
527+-- "Jack"
528+-- ],
529+-- "maxLength": 4,
530+-- "minLength": 4,
531+-- "pattern": "Jack",
532+-- "type": "string"
533+-- }
534+-- },
535+-- "required": [
536+-- "age",
537+-- "name"
538+-- ],
539+-- "type": "object"
540+-- }
355sketchStrictSchema :: ToJSON a => a -> Schema
541sketchStrictSchema :: ToJSON a => a -> Schema
356sketchStrictSchema = go . toJSON
542sketchStrictSchema = go . toJSON
357 where
543 where
@@ -383,7 +569,7 @@ sketchStrictSchema = go . toJSON
383 allUnique = sz == HashSet.size (HashSet.fromList (V.toList xs))
569 allUnique = sz == HashSet.size (HashSet.fromList (V.toList xs))
384 go js@(Object o) = mempty
570 go js@(Object o) = mempty
385 & type_ ?~ OpenApiObject
571 & type_ ?~ OpenApiObject
386− & required .~ names
572+ & required .~ sort names
387 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
573 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
388 & maxProperties ?~ fromIntegral (length names)
574 & maxProperties ?~ fromIntegral (length names)
389 & minProperties ?~ fromIntegral (length names)
575 & minProperties ?~ fromIntegral (length names)
@@ -553,8 +739,12 @@ instance ToSchema a => ToSchema (Identity a) where declareNamedSchema _ = declar
553
739
554-- | Default schema for @'Bounded'@, @'Integral'@ types.
740-- | Default schema for @'Bounded'@, @'Integral'@ types.
555--
741--
556−-- >>> BSL.putStrLn $ encode $ toSchemaBoundedIntegral (Proxy :: Proxy Int16)
742+-- >>> BSL.putStrLn $ encodePretty $ toSchemaBoundedIntegral (Proxy :: Proxy Int16)
743+-- {
557−-- {"maximum":32767,"minimum":-32768,"type":"integer"}
744+-- "maximum": 32767,
745+-- "minimum": -32768,
746+-- "type": "integer"
747+-- }
558toSchemaBoundedIntegral :: forall a. (Bounded a, Integral a) => Proxy a -> Schema
748toSchemaBoundedIntegral :: forall a. (Bounded a, Integral a) => Proxy a -> Schema
559toSchemaBoundedIntegral _ = mempty
749toSchemaBoundedIntegral _ = mempty
560 & type_ ?~ OpenApiInteger
750 & type_ ?~ OpenApiInteger
@@ -586,8 +776,27 @@ genericDeclareNamedSchemaNewtype opts f proxy = genericNameSchema opts proxy <$>
586-- >>> instance ToSchema ButtonState
776-- >>> instance ToSchema ButtonState
587-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
777-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
588-- >>> type ImageUrl = T.Text
778-- >>> type ImageUrl = T.Text
589−-- >>> BSL.putStrLn $ encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
779+-- >>> BSL.putStrLn $ encodePretty $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
590−-- {"type":"object","properties":{"Focus":{"type":"string"},"Disabled":{"type":"string"},"Active":{"type":"string"},"Neutral":{"type":"string"},"Hover":{"type":"string"}}}
780+-- {
781+-- "properties": {
782+-- "Active": {
783+-- "type": "string"
784+-- },
785+-- "Disabled": {
786+-- "type": "string"
787+-- },
788+-- "Focus": {
789+-- "type": "string"
790+-- },
791+-- "Hover": {
792+-- "type": "string"
793+-- },
794+-- "Neutral": {
795+-- "type": "string"
796+-- }
797+-- },
798+-- "type": "object"
799+-- }
591--
800--
592-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
801-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
593-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
802-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
@@ -614,8 +823,27 @@ declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key o
614-- >>> instance ToSchema ButtonState
823-- >>> instance ToSchema ButtonState
615-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
824-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
616-- >>> type ImageUrl = T.Text
825-- >>> type ImageUrl = T.Text
617−-- >>> BSL.putStrLn $ encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
826+-- >>> BSL.putStrLn $ encodePretty $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
618−-- {"type":"object","properties":{"Focus":{"type":"string"},"Disabled":{"type":"string"},"Active":{"type":"string"},"Neutral":{"type":"string"},"Hover":{"type":"string"}}}
827+-- {
828+-- "properties": {
829+-- "Active": {
830+-- "type": "string"
831+-- },
832+-- "Disabled": {
833+-- "type": "string"
834+-- },
835+-- "Focus": {
836+-- "type": "string"
837+-- },
838+-- "Hover": {
839+-- "type": "string"
840+-- },
841+-- "Neutral": {
842+-- "type": "string"
843+-- }
844+-- },
845+-- "type": "object"
846+-- }
619--
847--
620-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
848-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
621-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
849-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
@@ -908,6 +1136,7 @@ instance (ToSchema1 f, Generic (f a), GToSchema (Rep (f a)), Typeable (f a), ToS
908>>> import Data.OpenApi
1136>>> import Data.OpenApi
909>>> import Data.Aeson (encode)
1137>>> import Data.Aeson (encode)
910>>> import Data.Aeson.Types (toJSONKeyText)
1138>>> import Data.Aeson.Types (toJSONKeyText)
1139+>>> import Data.OpenApi.Internal.Utils
911>>> :set -XScopedTypeVariables
1140>>> :set -XScopedTypeVariables
912>>> :set -XDeriveAnyClass
1141>>> :set -XDeriveAnyClass
913>>> :set -XStandaloneDeriving
1142>>> :set -XStandaloneDeriving
src/Data/OpenApi/Internal/Schema/Validation.hs+15−15
@@ -31,7 +31,6 @@ import Control.Lens hiding (allOf)
31import Control.Monad (forM, forM_, when)
31import Control.Monad (forM, forM_, when)
32
32
33import Data.Aeson hiding (Result)
33import Data.Aeson hiding (Result)
34−import Data.Aeson.Encode.Pretty (encodePretty)
35import Data.Foldable (for_, sequenceA_,
34import Data.Foldable (for_, sequenceA_,
36 traverse_)
35 traverse_)
37import Data.HashMap.Strict (HashMap)
36import Data.HashMap.Strict (HashMap)
@@ -50,6 +49,7 @@ import qualified Data.Vector as Vector
50
49
51import Data.OpenApi.Declare
50import Data.OpenApi.Declare
52import Data.OpenApi.Internal
51import Data.OpenApi.Internal
52+import Data.OpenApi.Internal.Utils
53import Data.OpenApi.Internal.Schema
53import Data.OpenApi.Internal.Schema
54import Data.OpenApi.Lens
54import Data.OpenApi.Lens
55
55
@@ -103,33 +103,33 @@ validateToJSONWithPatternChecker checker = validateJSONWithPatternChecker checke
103-- <BLANKLINE>
103-- <BLANKLINE>
104-- Swagger Schema:
104-- Swagger Schema:
105-- {
105-- {
106−-- "required": [
107−-- "name",
108−-- "phone"
109−-- ],
110−-- "type": "object",
111-- "properties": {
106-- "properties": {
112−-- "phone": {
113−-- "$ref": "#/components/schemas/Phone"
114−-- },
115-- "name": {
107-- "name": {
116-- "type": "string"
108-- "type": "string"
109+-- },
110+-- "phone": {
111+-- "$ref": "#/components/schemas/Phone"
117-- }
112-- }
118−-- }
113+-- },
114+-- "required": [
115+-- "name",
116+-- "phone"
117+-- ],
118+-- "type": "object"
119-- }
119-- }
120-- <BLANKLINE>
120-- <BLANKLINE>
121-- Swagger Description Context:
121-- Swagger Description Context:
122-- {
122-- {
123-- "Phone": {
123-- "Phone": {
124−-- "required": [
125−-- "value"
126−-- ],
127−-- "type": "object",
128-- "properties": {
124-- "properties": {
129-- "value": {
125-- "value": {
130-- "type": "string"
126-- "type": "string"
131-- }
127-- }
132−-- }
128+-- },
129+-- "required": [
130+-- "value"
131+-- ],
132+-- "type": "object"
133-- }
133-- }
134-- }
134-- }
135-- <BLANKLINE>
135-- <BLANKLINE>
src/Data/OpenApi/Internal/Utils.hs+5
@@ -13,6 +13,8 @@ import Control.Lens ((&), (%~))
13import Control.Lens.TH
13import Control.Lens.TH
14import Data.Aeson
14import Data.Aeson
15import Data.Aeson.Types
15import Data.Aeson.Types
16+import qualified Data.Aeson.Encode.Pretty as P
17+import qualified Data.ByteString.Lazy as BSL
16import Data.Char
18import Data.Char
17import Data.Data
19import Data.Data
18import Data.Hashable (Hashable)
20import Data.Hashable (Hashable)
@@ -132,3 +134,6 @@ instance SwaggerMonoid (Maybe a) where
132 swaggerMempty = Nothing
134 swaggerMempty = Nothing
133 swaggerMappend x Nothing = x
135 swaggerMappend x Nothing = x
134 swaggerMappend _ y = y
136 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+117−10
@@ -55,13 +55,16 @@ import qualified Data.HashSet.InsOrd as InsOrdHS
55-- >>> import Data.Proxy
55-- >>> import Data.Proxy
56-- >>> import Data.Time
56-- >>> import Data.Time
57-- >>> import qualified Data.ByteString.Lazy.Char8 as BSL
57-- >>> import qualified Data.ByteString.Lazy.Char8 as BSL
58+-- >>> import Data.OpenApi.Internal.Utils
58
59
59-- | Prepend path piece to all operations of the spec.
60-- | Prepend path piece to all operations of the spec.
60-- Leading and trailing slashes are trimmed/added automatically.
61-- Leading and trailing slashes are trimmed/added automatically.
61--
62--
62-- >>> let api = (mempty :: OpenApi) & paths .~ [("/info", mempty)]
63-- >>> let api = (mempty :: OpenApi) & paths .~ [("/info", mempty)]
63−-- >>> BSL.putStrLn $ encode $ prependPath "user/{user_id}" api ^. paths
64+-- >>> BSL.putStrLn $ encodePretty $ prependPath "user/{user_id}" api ^. paths
65+-- {
64−-- {"/user/{user_id}/info":{}}
66+-- "/user/{user_id}/info": {}
67+-- }
65prependPath :: FilePath -> OpenApi -> OpenApi
68prependPath :: FilePath -> OpenApi -> OpenApi
66prependPath path = paths %~ InsOrdHashMap.mapKeys (path </>)
69prependPath path = paths %~ InsOrdHashMap.mapKeys (path </>)
67 where
70 where
@@ -82,10 +85,63 @@ allOperations = paths.traverse.template
82-- >>> let ok = (mempty :: Operation) & at 200 ?~ "OK"
85-- >>> let ok = (mempty :: Operation) & at 200 ?~ "OK"
83-- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ ok & post ?~ ok)]
86-- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ ok & post ?~ ok)]
84-- >>> let sub = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)]
87-- >>> let sub = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)]
85−-- >>> BSL.putStrLn $ encode api
88+-- >>> BSL.putStrLn $ encodePretty api
86−-- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/user":{"get":{"responses":{"200":{"description":"OK"}}},"post":{"responses":{"200":{"description":"OK"}}}}},"components":{}}
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+-- }
87−-- >>> BSL.putStrLn $ encode $ api & operationsOf sub . at 404 ?~ "Not found"
115+-- >>> BSL.putStrLn $ encodePretty $ 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":{}}
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+-- }
89operationsOf :: OpenApi -> Traversal' OpenApi Operation
145operationsOf :: OpenApi -> Traversal' OpenApi Operation
90operationsOf sub = paths.itraversed.withIndex.subops
146operationsOf sub = paths.itraversed.withIndex.subops
91 where
147 where
@@ -123,8 +179,26 @@ applyTagsFor ops ts swag = swag
123--
179--
124-- FIXME doc
180-- FIXME doc
125--
181--
126−-- >>> BSL.putStrLn $ encode $ runDeclare (declareResponse "application/json" (Proxy :: Proxy Day)) mempty
182+-- >>> BSL.putStrLn $ encodePretty $ 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"}}}}]
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+-- ]
128declareResponse :: ToSchema a => MediaType -> Proxy a -> Declare (Definitions Schema) Response
202declareResponse :: ToSchema a => MediaType -> Proxy a -> Declare (Definitions Schema) Response
129declareResponse cType proxy = do
203declareResponse cType proxy = do
130 s <- declareSchemaRef proxy
204 s <- declareSchemaRef proxy
@@ -143,8 +217,41 @@ declareResponse cType proxy = do
143--
217--
144-- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)]
218-- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)]
145-- >>> let res = declareResponse "application/json" (Proxy :: Proxy Day)
219-- >>> let res = declareResponse "application/json" (Proxy :: Proxy Day)
146−-- >>> BSL.putStrLn $ encode $ api & setResponse 200 res
220+-- >>> BSL.putStrLn $ encodePretty $ 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"}}}}
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+-- }
148--
255--
149-- See also @'setResponseWith'@.
256-- See also @'setResponseWith'@.
150setResponse :: HttpStatusCode -> Declare (Definitions Schema) Response -> OpenApi -> OpenApi
257setResponse :: HttpStatusCode -> Declare (Definitions Schema) Response -> OpenApi -> OpenApi
src/Data/OpenApi/Optics.hs+54−9
@@ -22,43 +22,88 @@
22-- Example from the "Data.OpenApi" module using @optics@:
22-- Example from the "Data.OpenApi" module using @optics@:
23--
23--
24-- >>> :{
24-- >>> :{
25−-- BSL.putStrLn $ encode $ (mempty :: OpenApi)
25+-- BSL.putStrLn $ encodePretty $ (mempty :: OpenApi)
26-- & #components % #schemas .~ [ ("User", mempty & #type ?~ OpenApiString) ]
26-- & #components % #schemas .~ [ ("User", mempty & #type ?~ OpenApiString) ]
27-- & #paths .~
27-- & #paths .~
28-- [ ("/user", mempty & #get ?~ (mempty
28-- [ ("/user", mempty & #get ?~ (mempty
29-- & at 200 ?~ ("OK" & #_Inline % #content % at "application/json" ?~ (mempty & #schema ?~ Ref (Reference "User")))
29-- & at 200 ?~ ("OK" & #_Inline % #content % at "application/json" ?~ (mempty & #schema ?~ Ref (Reference "User")))
30-- & at 404 ?~ "User info not found")) ]
30-- & at 404 ?~ "User info not found")) ]
31-- :}
31-- :}
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"}}}}
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+-- }
33--
67--
34-- For convenience optics are defined as /labels/. It means that field accessor
68-- For convenience optics are defined as /labels/. It means that field accessor
35-- names can be overloaded for different types. One such common field is
69-- names can be overloaded for different types. One such common field is
36-- @#description@. Many components of a Swagger specification can have
70-- @#description@. Many components of a Swagger specification can have
37-- descriptions, and you can use the same name for them:
71-- descriptions, and you can use the same name for them:
38--
72--
39−-- >>> BSL.putStrLn $ encode $ (mempty :: Response) & #description .~ "No content"
73+-- >>> BSL.putStrLn $ encodePretty $ (mempty :: Response) & #description .~ "No content"
74+-- {
40−-- {"description":"No content"}
75+-- "description": "No content"
76+-- }
41-- >>> :{
77-- >>> :{
42−-- BSL.putStrLn $ encode $ (mempty :: Schema)
78+-- BSL.putStrLn $ encodePretty $ (mempty :: Schema)
43-- & #type ?~ OpenApiBoolean
79-- & #type ?~ OpenApiBoolean
44-- & #description ?~ "To be or not to be"
80-- & #description ?~ "To be or not to be"
45-- :}
81-- :}
82+-- {
46−-- {"type":"boolean","description":"To be or not to be"}
83+-- "description": "To be or not to be",
84+-- "type": "boolean"
85+-- }
47--
86--
48-- Additionally, to simplify working with @'Response'@, both @'Operation'@ and
87-- Additionally, to simplify working with @'Response'@, both @'Operation'@ and
49-- @'Responses'@ have direct access to it via @'Optics.Core.At.at'@. Example:
88-- @'Responses'@ have direct access to it via @'Optics.Core.At.at'@. Example:
50--
89--
51-- >>> :{
90-- >>> :{
52−-- BSL.putStrLn $ encode $ (mempty :: Operation)
91+-- BSL.putStrLn $ encodePretty $ (mempty :: Operation)
53-- & at 404 ?~ "Not found"
92-- & at 404 ?~ "Not found"
54-- :}
93-- :}
94+-- {
95+-- "responses": {
96+-- "404": {
55−-- {"responses":{"404":{"description":"Not found"}}}
97+-- "description": "Not found"
98+-- }
99+-- }
56−--
100+-- }
57module Data.OpenApi.Optics () where
101module Data.OpenApi.Optics () where
58
102
59import Data.Aeson (Value)
103import Data.Aeson (Value)
60import Data.Scientific (Scientific)
104import Data.Scientific (Scientific)
61import Data.OpenApi.Internal
105import Data.OpenApi.Internal
106+import Data.OpenApi.Internal.Utils
62import Data.Text (Text)
107import Data.Text (Text)
63import Optics.Core
108import Optics.Core
64import Optics.TH
109import Optics.TH