git.iko.soy/deriving-openapi3
on ae714de help sign in
.github/workflows/build.yaml+4−17
since splitsnapshots
unifiedsplit
@@ -1,4 +1,4 @@
1−name: Build
1+name: Github Action Docker Image
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 [ "$(awk -F":" '{if ($1 == "version") {gsub(/ /, "", $2); print $2}}' *.cabal)" != "${{ github.event.release.tag_name }}" ]; then
20+ if [ "$(yq eval ".version" package.yaml)" != "${{ 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('**.cabal') }}
36+ key: ${{ runner.os }}-stack-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}
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,21 +43,8 @@ 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−
51 - name: Check Formatting
46 - name: Check Formatting
52 shell: bash
47 shell: bash
53 run: |
48 run: |
54 stack install ormolu
49 stack install ormolu
55− ormolu -o -XTypeApplications -o -XCPP -m check $(find src -type f -name "*.hs")
50+ ormolu -o -XTypeApplications -o -XCPP -m check $(find . -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
@@ -0,0 +1 @@
1+deriving-openapi3.cabal
deriving-openapi3.cabaldeleted−55
@@ -1,55 +0,0 @@
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.yamlnew file+49
@@ -0,0 +1,49 @@
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.shdeleted−9
@@ -1,9 +0,0 @@
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+53−84
@@ -1,139 +1,108 @@
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
31module Deriving.OpenApi
4module Deriving.OpenApi
32 ( CustomOpenApi,
5 ( CustomOpenApi,
33 DatatypeNameModifier,
6 DatatypeNameModifier,
34 ToSchema,
7 ToSchema,
35 module Deriving.Aeson,
8 module Deriving.Aeson,
9+ Extending,
36 )
10 )
37where
11where
38
12
39import Control.Lens
13import Control.Lens
40import qualified Data.Aeson.Types as A
14import qualified Data.Aeson.Types as A
15+import Data.Kind
41import Data.OpenApi
16import Data.OpenApi
42−import Data.OpenApi.Internal.ParamSchema
43import Data.OpenApi.Internal.Schema
17import Data.OpenApi.Internal.Schema
44import Data.Proxy
18import Data.Proxy
45−import Data.Text (Text)
46−import qualified Data.Text as T
47import Data.Typeable
19import Data.Typeable
48import Deriving.Aeson
20import Deriving.Aeson
49import GHC.Generics
21import GHC.Generics
50import GHC.TypeLits
22import GHC.TypeLits
51
23
52−data Description (desc :: Symbol) = Description
24+#ifdef SERVANT_DESCRIPTION
25+
26+import Servant.API
27+import Data.Text (Text)
28+import qualified Data.Text as T
53
29
54instance (AesonOptions xs) => AesonOptions (Description f ': xs) where
30instance (AesonOptions xs) => AesonOptions (Description f ': xs) where
55 aesonOptions = aesonOptions @xs
31 aesonOptions = aesonOptions @xs
56
32
57−instance KnownSymbol t => OpenApiOptionModifier (Description t) where
33+instance (OpenApiOptions xs, KnownSymbol t) => OpenApiOptions (Description t ': xs) where
34+ openApiOptions = openApiOptions @xs
58 openApiSchemaModifier = schema . description <>~ Just (toTextLine @t)
35 openApiSchemaModifier = schema . description <>~ Just (toTextLine @t)
59
36
60toTextLine :: forall s. KnownSymbol s => Text
37toTextLine :: forall s. KnownSymbol s => Text
61toTextLine = "\n\n" <> T.pack (symbolVal (Proxy @s))
38toTextLine = "\n\n" <> T.pack (symbolVal (Proxy @s))
62
39
63−-- $setup
40+#endif
64−-- >>> import qualified Data.Yaml
65−-- >>> import qualified Data.ByteString.Char8
66
41
67type CustomOpenApi = CustomJSON
42type CustomOpenApi = CustomJSON
68
43
69−instance
70− (OpenApiOptionModifier xs, GToSchema (Rep x), Generic x, Typeable x, Typeable xs, Typeable k) =>
44+instance (OpenApiOptions xs, GToSchema (Rep x), Generic x, Typeable x, Typeable xs) => ToSchema (CustomJSON xs x) where
71− ToSchema (CustomJSON (xs :: k) x)
72− where
73 declareNamedSchema Proxy =
45 declareNamedSchema Proxy =
74− openApiSchemaModifier @xs
46+ openApiSchemaModifier @xs <$> genericDeclareNamedSchema (openApiOptions @xs) (Proxy @x)
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
89
47
90data DatatypeNameModifier t
48data DatatypeNameModifier t
91
49
92−instance (StringModifier f) => OpenApiOptionModifier (DatatypeNameModifier f) where
50+instance (StringModifier f, OpenApiOptions xs) => OpenApiOptions (DatatypeNameModifier f ': xs) where
93− openApiOptionsModifier o = o {datatypeNameModifier = getStringModifier @f}
51+ openApiOptions = (openApiOptions @xs) {datatypeNameModifier = getStringModifier @f}
94
52
95instance (AesonOptions xs) => AesonOptions (DatatypeNameModifier f ': xs) where
53instance (AesonOptions xs) => AesonOptions (DatatypeNameModifier f ': xs) where
96 aesonOptions = aesonOptions @xs
54 aesonOptions = aesonOptions @xs
97
55
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+
98-- deriving-aeson-based instances
63-- deriving-aeson-based instances
99
64
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+
100−instance OpenApiOptionModifier UnwrapUnaryRecords where
73+instance OpenApiOptions xs => OpenApiOptions (UnwrapUnaryRecords ': xs) where
101− openApiOptionsModifier o = o {unwrapUnaryRecords = True}
74+ openApiOptions = (openApiOptions @xs) {unwrapUnaryRecords = True}
102
75
103−instance OpenApiOptionModifier OmitNothingFields
76+instance OpenApiOptions xs => OpenApiOptions (OmitNothingFields ': xs) where
77+ openApiOptions = openApiOptions @xs
104
78
105−#if MIN_VERSION_deriving_aeson(0,2,4)
106−instance OpenApiOptionModifier RejectUnknownFields where
79+instance OpenApiOptions xs => OpenApiOptions (RejectUnknownFields ': xs) where
80+ openApiOptions = openApiOptions @xs
107 openApiSchemaModifier = schema . additionalProperties .~ Just (AdditionalPropertiesAllowed False)
81 openApiSchemaModifier = schema . additionalProperties .~ Just (AdditionalPropertiesAllowed False)
108−#endif
109
82
110−instance StringModifier f => OpenApiOptionModifier (FieldLabelModifier f) where
83+instance (StringModifier f, OpenApiOptions xs) => OpenApiOptions (FieldLabelModifier f ': xs) where
111− openApiOptionsModifier o = o {fieldLabelModifier = getStringModifier @f}
84+ openApiOptions = (openApiOptions @xs) {fieldLabelModifier = getStringModifier @f}
112
85
113−instance StringModifier f => OpenApiOptionModifier (ConstructorTagModifier f) where
86+instance (StringModifier f, OpenApiOptions xs) => OpenApiOptions (ConstructorTagModifier f ': xs) where
114− openApiOptionsModifier o = o {constructorTagModifier = getStringModifier @f}
87+ openApiOptions = (openApiOptions @xs) {constructorTagModifier = getStringModifier @f}
115
88
116instance
89instance
117− TypeError ('Text "deriving-openapi3 does not currently the `TagSingleConstructors` modifier.") =>
90+ (OpenApiOptions xs, TypeError ('Text "openapi3-deriving does not currently the `TagSingleConstructors` modifier.")) =>
118− OpenApiOptionModifier TagSingleConstructors
91+ OpenApiOptions (TagSingleConstructors ': xs)
119−
92+ where
120−instance OpenApiOptionModifier NoAllNullaryToStringTag where
93+ openApiOptions = undefined
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))}
125
94
126−instance OpenApiOptionModifier SumUntaggedValue where
95+instance OpenApiOptions xs => OpenApiOptions (NoAllNullaryToStringTag ': xs) where
127− openApiOptionsModifier o = o {sumEncoding = A.UntaggedValue}
96+ openApiOptions = (openApiOptions @xs) {allNullaryToStringTag = False}
128
97
129−instance OpenApiOptionModifier SumObjectWithSingleField where
98+instance (KnownSymbol t, KnownSymbol c, OpenApiOptions xs) => OpenApiOptions (SumTaggedObject t c ': xs) where
130− openApiOptionsModifier o = o {sumEncoding = A.ObjectWithSingleField}
99+ openApiOptions = (openApiOptions @xs) {sumEncoding = A.TaggedObject (symbolVal (Proxy @t)) (symbolVal (Proxy @c))}
131
100
132−instance OpenApiOptionModifier SumTwoElemArray where
101+instance (OpenApiOptions xs) => OpenApiOptions (SumUntaggedValue ': xs) where
133− openApiOptionsModifier o = o {sumEncoding = A.TwoElemArray}
102+ openApiOptions = (openApiOptions @xs) {sumEncoding = A.UntaggedValue}
134
103
135−instance OpenApiOptionModifier '[]
104+instance (OpenApiOptions xs) => OpenApiOptions (SumObjectWithSingleField ': xs) where
105+ openApiOptions = (openApiOptions @xs) {sumEncoding = A.ObjectWithSingleField}
136
106
137−instance (OpenApiOptionModifier x, OpenApiOptionModifier xs) => OpenApiOptionModifier (x ': xs) where
107+instance (OpenApiOptions xs) => OpenApiOptions (SumTwoElemArray ': xs) where
138− openApiOptionsModifier = openApiOptionsModifier @xs . openApiOptionsModifier @x
108+ openApiOptions = (openApiOptions @xs) {sumEncoding = A.TwoElemArray}
139− openApiSchemaModifier = openApiSchemaModifier @xs . openApiSchemaModifier @x
stack.yaml+4−1
@@ -1,4 +1,7 @@
1−resolver: lts-18.23
1+resolver: lts-18.21
2
2
3packages:
3packages:
4 - .
4 - .
5+
6+extra-deps:
7+ - openapi3-3.1.0