git.iko.soy/deriving-openapi3
on master help sign in
.github/workflows/build.yaml+17−4
since splitsnapshots
unifiedsplit
@@ -1,4 +1,4 @@
1−name: Github Action Docker Image
1+name: Build
2
2
3on:
3on:
4 push:
4 push:
@@ -17,7 +17,7 @@ jobs:
17 - name: Check library version
17 - name: Check library version
18 if: github.event_name == 'release'
18 if: github.event_name == 'release'
19 run: |
19 run: |
20− if [ "$(yq eval ".version" package.yaml)" != "${{ github.event.release.tag_name }}" ]; then
20+ if [ "$(awk -F":" '{if ($1 == "version") {gsub(/ /, "", $2); print $2}}' *.cabal)" != "${{ github.event.release.tag_name }}" ]; then
21 echo "::error file=action.yaml::Library version does not match release version."
21 echo "::error file=action.yaml::Library version does not match release version."
22 exit 1
22 exit 1
23 fi
23 fi
@@ -33,7 +33,7 @@ jobs:
33 path: |
33 path: |
34 ~/.stack
34 ~/.stack
35 .stack-work
35 .stack-work
36− key: ${{ runner.os }}-stack-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}
36+ key: ${{ runner.os }}-stack-${{ hashFiles('stack.yaml') }}-${{ hashFiles('**.cabal') }}
37 restore-keys: |
37 restore-keys: |
38 ${{ runner.os }}-stack-${{ hashFiles('stack.yaml') }}
38 ${{ runner.os }}-stack-${{ hashFiles('stack.yaml') }}
39 ${{ runner.os }}-stack
39 ${{ runner.os }}-stack
@@ -43,8 +43,21 @@ jobs:
43 run: |
43 run: |
44 stack build
44 stack build
45
45
46+ - name: doctests
47+ shell: bash
48+ run: |
49+ ./runDoctests.sh
50+
46 - name: Check Formatting
51 - name: Check Formatting
47 shell: bash
52 shell: bash
48 run: |
53 run: |
49 stack install ormolu
54 stack install ormolu
50− ormolu -o -XTypeApplications -o -XCPP -m check $(find . -type f -name "*.hs")
55+ ormolu -o -XTypeApplications -o -XCPP -m check $(find src -type f -name "*.hs")
56+
57+ - name: Publish candidate
58+ shell: bash
59+ if: github.event_name == 'release'
60+ env:
61+ HACKAGE_KEY: ${{ secrets.HACKAGE_TOKEN }}
62+ run: |
63+ stack upload --pvp-bounds both --test-tarball --candidate .
.gitignore−1
@@ -1 +0,0 @@
1−deriving-openapi3.cabal
deriving-openapi3.cabalnew file+55
@@ -0,0 +1,55 @@
1+cabal-version: 1.12
2+
3+-- This file has been generated from package.yaml by hpack version 0.34.5.
4+--
5+-- see: https://github.com/sol/hpack
6+
7+name: deriving-openapi3
8+version: 0.1.0.0
9+synopsis: DerivingVia for OpenAPI 3
10+description: DerivingVia for OpenAPI 3
11+category: JSON, Generics, OpenAPI
12+homepage: https://github.com/ilyakooo0/deriving-openapi3#readme
13+bug-reports: https://github.com/ilyakooo0/deriving-openapi3/issues
14+maintainer: Ilya Kostyuchenko <ilyakooo0@gmail.com>
15+license: MIT
16+license-file: LICENSE
17+build-type: Simple
18+extra-source-files:
19+ Changelog.md
20+
21+source-repository head
22+ type: git
23+ location: https://github.com/ilyakooo0/deriving-openapi3
24+
25+library
26+ exposed-modules:
27+ Deriving.OpenApi
28+ other-modules:
29+ Paths_deriving_openapi3
30+ hs-source-dirs:
31+ src
32+ default-extensions:
33+ AllowAmbiguousTypes
34+ ScopedTypeVariables
35+ KindSignatures
36+ DataKinds
37+ TypeOperators
38+ FlexibleInstances
39+ TypeApplications
40+ UndecidableInstances
41+ TypeFamilies
42+ PolyKinds
43+ OverloadedStrings
44+ DerivingStrategies
45+ DerivingVia
46+ FlexibleContexts
47+ ghc-options: -Weverything -Wno-star-is-type -Wno-unsafe -Wno-prepositive-qualified-module -Wno-safe -Wno-missing-safe-haskell-mode -Wno-missing-import-lists -Wno-implicit-prelude
48+ build-depends:
49+ aeson
50+ , base >=4.12
51+ , deriving-aeson >=0.2.2
52+ , lens
53+ , openapi3
54+ , text
55+ default-language: Haskell2010
package.yamldeleted−49
@@ -1,49 +0,0 @@
1−name: deriving-openapi3
2−version: 0.1.0.0
3−synopsis: DerivingVia for OpenAPI 3
4−description: See <https://github.com/ilyakooo0/deriving-openapi3 GitHub> for more details.
5−maintainer: Ilya Kostyuchenko <ilyakooo0@gmail.com>
6−category: JSON, Generics, OpenAPI
7−license: MIT
8−license-file: LICENSE
9−extra-source-files: Changelog.md
10−github: ilyakooo0/deriving-openapi3
11−ghc-options: -Weverything
12− -Wno-star-is-type
13− -Wno-unsafe
14− -Wno-prepositive-qualified-module
15− -Wno-safe
16− -Wno-missing-safe-haskell-mode
17− -Wno-missing-import-lists
18− -Wno-implicit-prelude
19−dependencies:
20− - base
21− - openapi3
22− - deriving-aeson
23− - aeson
24− - lens
25− - text
26−when:
27− - condition: flag(servant-description)
28− dependencies:
29− - servant
30− cpp-options: -DSERVANT_DESCRIPTION
31−library:
32− source-dirs: src
33−default-extensions:
34− - AllowAmbiguousTypes
35− - ScopedTypeVariables
36− - KindSignatures
37− - DataKinds
38− - TypeOperators
39− - FlexibleInstances
40− - TypeApplications
41− - UndecidableInstances
42− - TypeFamilies
43− - PolyKinds
44− - OverloadedStrings
45−flags:
46− servant-description:
47− description: Create instances for the 'Description' type from 'servant'
48− default: true
49− manual: false
runDoctests.shnew file+9
@@ -0,0 +1,9 @@
1+#!/bin/sh
2+
3+set -e
4+
5+EXTENSIONS="-XAllowAmbiguousTypes -XScopedTypeVariables -XKindSignatures -XDataKinds -XTypeOperators -XFlexibleInstances -XTypeApplications -XUndecidableInstances -XTypeFamilies -XPolyKinds"
6+
7+DEPENDENCIES="--package doctest --package yaml"
8+
9+stack exec $DEPENDENCIES -- find src -name '*.hs' -exec doctest $EXTENSIONS '{}' \;
src/Deriving/OpenApi.hs+84−53
@@ -1,108 +1,139 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2{-# OPTIONS_GHC -Wno-orphans #-}
2{-# OPTIONS_GHC -Wno-orphans #-}
3
3
4+-- | __Example:__
5+--
6+-- >>> :set -XDerivingStrategies -XDerivingVia -XDeriveGeneric
7+--
8+-- >>> :{
9+-- data User = User
10+-- { userFirstName :: String,
11+-- userAge :: Maybe Integer
12+-- }
13+-- deriving stock (Generic)
14+-- deriving (FromJSON, ToJSON, ToSchema)
15+-- via CustomJSON
16+-- '[FieldLabelModifier '[StripPrefix "user", CamelToSnake], RejectUnknownFields]
17+-- User
18+-- showYaml = Data.ByteString.Char8.putStr . Data.Yaml.encode
19+-- :}
20+--
21+-- >>> showYaml $ toSchema (Proxy :: Proxy User)
22+-- type: object
23+-- properties:
24+-- first_name:
25+-- type: string
26+-- age:
27+-- type: integer
28+-- required:
29+-- - first_name
30+-- additionalProperties: false
4module Deriving.OpenApi
31module Deriving.OpenApi
5 ( CustomOpenApi,
32 ( CustomOpenApi,
6 DatatypeNameModifier,
33 DatatypeNameModifier,
7 ToSchema,
34 ToSchema,
8 module Deriving.Aeson,
35 module Deriving.Aeson,
9− Extending,
10 )
36 )
11where
37where
12
38
13import Control.Lens
39import Control.Lens
14import qualified Data.Aeson.Types as A
40import qualified Data.Aeson.Types as A
15−import Data.Kind
16import Data.OpenApi
41import Data.OpenApi
42+import Data.OpenApi.Internal.ParamSchema
17import Data.OpenApi.Internal.Schema
43import Data.OpenApi.Internal.Schema
18import Data.Proxy
44import Data.Proxy
45+import Data.Text (Text)
46+import qualified Data.Text as T
19import Data.Typeable
47import Data.Typeable
20import Deriving.Aeson
48import Deriving.Aeson
21import GHC.Generics
49import GHC.Generics
22import GHC.TypeLits
50import GHC.TypeLits
23
51
24−#ifdef SERVANT_DESCRIPTION
52+data Description (desc :: Symbol) = Description
25−
26−import Servant.API
27−import Data.Text (Text)
28−import qualified Data.Text as T
29
53
30instance (AesonOptions xs) => AesonOptions (Description f ': xs) where
54instance (AesonOptions xs) => AesonOptions (Description f ': xs) where
31 aesonOptions = aesonOptions @xs
55 aesonOptions = aesonOptions @xs
32
56
33−instance (OpenApiOptions xs, KnownSymbol t) => OpenApiOptions (Description t ': xs) where
57+instance KnownSymbol t => OpenApiOptionModifier (Description t) where
34− openApiOptions = openApiOptions @xs
35 openApiSchemaModifier = schema . description <>~ Just (toTextLine @t)
58 openApiSchemaModifier = schema . description <>~ Just (toTextLine @t)
36
59
37toTextLine :: forall s. KnownSymbol s => Text
60toTextLine :: forall s. KnownSymbol s => Text
38toTextLine = "\n\n" <> T.pack (symbolVal (Proxy @s))
61toTextLine = "\n\n" <> T.pack (symbolVal (Proxy @s))
39
62
40−#endif
63+-- $setup
64+-- >>> import qualified Data.Yaml
65+-- >>> import qualified Data.ByteString.Char8
41
66
42type CustomOpenApi = CustomJSON
67type CustomOpenApi = CustomJSON
43
68
69+instance
44−instance (OpenApiOptions xs, GToSchema (Rep x), Generic x, Typeable x, Typeable xs) => ToSchema (CustomJSON xs x) where
70+ (OpenApiOptionModifier xs, GToSchema (Rep x), Generic x, Typeable x, Typeable xs, Typeable k) =>
71+ ToSchema (CustomJSON (xs :: k) x)
72+ where
45 declareNamedSchema Proxy =
73 declareNamedSchema Proxy =
46− openApiSchemaModifier @xs <$> genericDeclareNamedSchema (openApiOptions @xs) (Proxy @x)
74+ openApiSchemaModifier @xs
75+ <$> genericDeclareNamedSchema (openApiOptionsModifier @xs defaultSchemaOptions) (Proxy @x)
76+
77+instance
78+ (OpenApiOptionModifier xs, GToParamSchema (Rep x), Generic x) =>
79+ ToParamSchema (CustomJSON (xs :: k) x)
80+ where
81+ toParamSchema Proxy =
82+ genericToParamSchema (openApiOptionsModifier @xs defaultSchemaOptions) (Proxy @x)
83+
84+class OpenApiOptionModifier x where
85+ openApiOptionsModifier :: SchemaOptions -> SchemaOptions
86+ openApiOptionsModifier = id
87+ openApiSchemaModifier :: NamedSchema -> NamedSchema
88+ openApiSchemaModifier = id
47
89
48data DatatypeNameModifier t
90data DatatypeNameModifier t
49
91
50−instance (StringModifier f, OpenApiOptions xs) => OpenApiOptions (DatatypeNameModifier f ': xs) where
92+instance (StringModifier f) => OpenApiOptionModifier (DatatypeNameModifier f) where
51− openApiOptions = (openApiOptions @xs) {datatypeNameModifier = getStringModifier @f}
93+ openApiOptionsModifier o = o {datatypeNameModifier = getStringModifier @f}
52
94
53instance (AesonOptions xs) => AesonOptions (DatatypeNameModifier f ': xs) where
95instance (AesonOptions xs) => AesonOptions (DatatypeNameModifier f ': xs) where
54 aesonOptions = aesonOptions @xs
96 aesonOptions = aesonOptions @xs
55
97
56−type family (++) (x :: [k]) (y :: [k]) :: [k] where
57− (++) '[] ys = ys
58− (++) (x ': xs) ys = xs ++ (x ': ys)
59−
60−type family Extending (c :: * -> *) (ee :: [*]) :: * -> * where
61− Extending (CustomJSON a) ee = CustomJSON (a ++ ee)
62−
63-- deriving-aeson-based instances
98-- deriving-aeson-based instances
64
99
65−class OpenApiOptions (xs :: [Type]) where
66− openApiOptions :: SchemaOptions
67− openApiSchemaModifier :: NamedSchema -> NamedSchema
68− openApiSchemaModifier = id
69−
70−instance OpenApiOptions '[] where
71− openApiOptions = defaultSchemaOptions
72−
73−instance OpenApiOptions xs => OpenApiOptions (UnwrapUnaryRecords ': xs) where
100+instance OpenApiOptionModifier UnwrapUnaryRecords where
74− openApiOptions = (openApiOptions @xs) {unwrapUnaryRecords = True}
101+ openApiOptionsModifier o = o {unwrapUnaryRecords = True}
75
102
76−instance OpenApiOptions xs => OpenApiOptions (OmitNothingFields ': xs) where
103+instance OpenApiOptionModifier OmitNothingFields
77− openApiOptions = openApiOptions @xs
78
104
79−instance OpenApiOptions xs => OpenApiOptions (RejectUnknownFields ': xs) where
105+#if MIN_VERSION_deriving_aeson(0,2,4)
80− openApiOptions = openApiOptions @xs
106+instance OpenApiOptionModifier RejectUnknownFields where
81 openApiSchemaModifier = schema . additionalProperties .~ Just (AdditionalPropertiesAllowed False)
107 openApiSchemaModifier = schema . additionalProperties .~ Just (AdditionalPropertiesAllowed False)
108+#endif
82
109
83−instance (StringModifier f, OpenApiOptions xs) => OpenApiOptions (FieldLabelModifier f ': xs) where
110+instance StringModifier f => OpenApiOptionModifier (FieldLabelModifier f) where
84− openApiOptions = (openApiOptions @xs) {fieldLabelModifier = getStringModifier @f}
111+ openApiOptionsModifier o = o {fieldLabelModifier = getStringModifier @f}
85
112
86−instance (StringModifier f, OpenApiOptions xs) => OpenApiOptions (ConstructorTagModifier f ': xs) where
113+instance StringModifier f => OpenApiOptionModifier (ConstructorTagModifier f) where
87− openApiOptions = (openApiOptions @xs) {constructorTagModifier = getStringModifier @f}
114+ openApiOptionsModifier o = o {constructorTagModifier = getStringModifier @f}
88
115
89instance
116instance
90− (OpenApiOptions xs, TypeError ('Text "openapi3-deriving does not currently the `TagSingleConstructors` modifier.")) =>
117+ TypeError ('Text "deriving-openapi3 does not currently the `TagSingleConstructors` modifier.") =>
91− OpenApiOptions (TagSingleConstructors ': xs)
118+ OpenApiOptionModifier TagSingleConstructors
92− where
119+
93− openApiOptions = undefined
120+instance OpenApiOptionModifier NoAllNullaryToStringTag where
121+ openApiOptionsModifier o = o {allNullaryToStringTag = False}
122+
123+instance (KnownSymbol t, KnownSymbol c) => OpenApiOptionModifier (SumTaggedObject t c) where
124+ openApiOptionsModifier o = o {sumEncoding = A.TaggedObject (symbolVal (Proxy @t)) (symbolVal (Proxy @c))}
94
125
95−instance OpenApiOptions xs => OpenApiOptions (NoAllNullaryToStringTag ': xs) where
126+instance OpenApiOptionModifier SumUntaggedValue where
96− openApiOptions = (openApiOptions @xs) {allNullaryToStringTag = False}
127+ openApiOptionsModifier o = o {sumEncoding = A.UntaggedValue}
97
128
98−instance (KnownSymbol t, KnownSymbol c, OpenApiOptions xs) => OpenApiOptions (SumTaggedObject t c ': xs) where
129+instance OpenApiOptionModifier SumObjectWithSingleField where
99− openApiOptions = (openApiOptions @xs) {sumEncoding = A.TaggedObject (symbolVal (Proxy @t)) (symbolVal (Proxy @c))}
130+ openApiOptionsModifier o = o {sumEncoding = A.ObjectWithSingleField}
100
131
101−instance (OpenApiOptions xs) => OpenApiOptions (SumUntaggedValue ': xs) where
132+instance OpenApiOptionModifier SumTwoElemArray where
102− openApiOptions = (openApiOptions @xs) {sumEncoding = A.UntaggedValue}
133+ openApiOptionsModifier o = o {sumEncoding = A.TwoElemArray}
103
134
104−instance (OpenApiOptions xs) => OpenApiOptions (SumObjectWithSingleField ': xs) where
135+instance OpenApiOptionModifier '[]
105− openApiOptions = (openApiOptions @xs) {sumEncoding = A.ObjectWithSingleField}
106
136
107−instance (OpenApiOptions xs) => OpenApiOptions (SumTwoElemArray ': xs) where
137+instance (OpenApiOptionModifier x, OpenApiOptionModifier xs) => OpenApiOptionModifier (x ': xs) where
108− openApiOptions = (openApiOptions @xs) {sumEncoding = A.TwoElemArray}
138+ openApiOptionsModifier = openApiOptionsModifier @xs . openApiOptionsModifier @x
139+ openApiSchemaModifier = openApiSchemaModifier @xs . openApiSchemaModifier @x
stack.yaml+1−4
@@ -1,7 +1,4 @@
1−resolver: lts-18.21
1+resolver: lts-18.23
2
2
3packages:
3packages:
4 - .
4 - .
5−
6−extra-deps:
7− - openapi3-3.1.0