From 1fabcac31593a39f83d72681139294427f8490c9 Mon Sep 17 00:00:00 2001 From: Magesh Date: Mon, 8 Feb 2021 20:18:27 +0530 Subject: [PATCH 1/9] Extension support for OpenAPI Info Contact License Server ServerVariable PathItem Operation RequestBody MediaType Encoding Example Link Response Tag --- src/Data/OpenApi.hs | 1 + src/Data/OpenApi/Internal.hs | 176 ++++++++++++++++++++++++++--------- 2 files changed, 133 insertions(+), 44 deletions(-) diff --git a/src/Data/OpenApi.hs b/src/Data/OpenApi.hs index fcd7e302..fd0453f2 100644 --- a/src/Data/OpenApi.hs +++ b/src/Data/OpenApi.hs @@ -118,6 +118,7 @@ module Data.OpenApi ( -- ** Miscellaneous MimeList(..), URL(..), + SpecificationExtensions(..), ) where import Data.OpenApi.Lens diff --git a/src/Data/OpenApi/Internal.hs b/src/Data/OpenApi/Internal.hs index ad193bc7..62f133a5 100644 --- a/src/Data/OpenApi/Internal.hs +++ b/src/Data/OpenApi/Internal.hs @@ -29,6 +29,7 @@ import qualified Data.HashMap.Strict as HashMap import Data.HashSet.InsOrd (InsOrdHashSet) import Data.Map (Map) import qualified Data.Map as Map +import Data.Maybe (catMaybes) import Data.Monoid (Monoid (..)) import Data.Scientific (Scientific) import Data.Semigroup.Compat (Semigroup (..)) @@ -97,6 +98,9 @@ data OpenApi = OpenApi -- | Additional external documentation. , _openApiExternalDocs :: Maybe ExternalDocs + + -- | Specification Extensions + , _openApiExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | The object provides metadata about the API. @@ -122,6 +126,9 @@ data Info = Info -- | The version of the OpenAPI document (which is distinct from the -- OpenAPI Specification version or the API implementation version). , _infoVersion :: Text + + -- | Specification Extensions + , _infoExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | Contact information for the exposed API. @@ -134,6 +141,9 @@ data Contact = Contact -- | The email address of the contact person/organization. , _contactEmail :: Maybe Text + + -- | Specification Extensions + , _contactExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | License information for the exposed API. @@ -143,10 +153,13 @@ data License = License -- | A URL to the license used for the API. , _licenseUrl :: Maybe URL + + -- | Specification Extensions + , _licenseExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) instance IsString License where - fromString s = License (fromString s) Nothing + fromString s = License (fromString s) Nothing mempty -- | An object representing a Server. data Server = Server @@ -163,6 +176,9 @@ data Server = Server -- | A map between a variable name and its value. -- The value is used for substitution in the server's URL template. , _serverVariables :: InsOrdHashMap Text ServerVariable + + -- | Specification Extensions + , _serverExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) data ServerVariable = ServerVariable @@ -179,10 +195,13 @@ data ServerVariable = ServerVariable -- | An optional description for the server variable. -- [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. , _serverVariableDescription :: Maybe Text + + -- | Specification Extensions + , _serverVariableExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) instance IsString Server where - fromString s = Server (fromString s) Nothing mempty + fromString s = Server (fromString s) Nothing mempty mempty -- | Holds a set of reusable objects for different aspects of the OAS. -- All objects defined within the components object will have no effect on the API @@ -243,6 +262,9 @@ data PathItem = PathItem -- The list MUST NOT include duplicated parameters. -- A unique parameter is defined by a combination of a name and location. , _pathItemParameters :: [Referenced Param] + + -- | Specification Extensions + , _pathItemExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | Describes a single API operation on a path. @@ -308,6 +330,9 @@ data Operation = Operation -- If an alternative server object is specified at the 'PathItem' Object or Root level, -- it will be overridden by this value. , _operationServers :: [Server] + + -- | Specification Extensions + , _operationExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- This instance should be in @http-media@. @@ -341,6 +366,9 @@ data RequestBody = RequestBody -- | Determines if the request body is required in the request. -- Defaults to 'False'. , _requestBodyRequired :: Maybe Bool + + -- | Specification Extensions + , _requestBodyExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | Each Media Type Object provides schema and examples for the media type identified by its key. @@ -360,6 +388,9 @@ data MediaTypeObject = MediaTypeObject -- The encoding object SHALL only apply to 'RequestBody' objects when the media type -- is @multipart@ or @application/x-www-form-urlencoded@. , _mediaTypeObjectEncoding :: InsOrdHashMap Text Encoding + + -- | Specification Extensions + , _mediaTypeObjectExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | In order to support common ways of serializing simple parameters, a set of style values are defined. @@ -423,6 +454,9 @@ data Encoding = Encoding -- The default value is @false@. This property SHALL be ignored if the request body media type -- is not @application/x-www-form-urlencoded@. , _encodingAllowReserved :: Maybe Bool + + -- | Specification Extensions + , _encodingExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) newtype MimeList = MimeList { getMimeList :: [MediaType] } @@ -533,6 +567,9 @@ data Example = Example -- in JSON or YAML documents. The '_exampleValue' field -- and '_exampleExternalValue' field are mutually exclusive. , _exampleExternalValue :: Maybe URL + + -- | Specification Extensions + , _exampleExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Typeable, Data) data ExpressionOrValue @@ -569,6 +606,9 @@ data Link = Link -- | A server object to be used by the target operation. , _linkServer :: Maybe Server + + -- | Specification Extensions + , _linkExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Typeable, Data) -- | Items for @'OpenApiArray'@ schemas. @@ -742,10 +782,13 @@ data Response = Response -- The key of the map is a short name for the link, following the naming -- constraints of the names for 'Component' Objects. , _responseLinks :: InsOrdHashMap Text (Referenced Link) + + -- | Specification Extensions + , _responseExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) instance IsString Response where - fromString s = Response (fromString s) mempty mempty mempty + fromString s = Response (fromString s) mempty mempty mempty mempty -- | A map of possible out-of band callbacks related to the parent operation. -- Each value in the map is a 'PathItem' Object that describes a set of requests that @@ -879,12 +922,15 @@ data Tag = Tag -- | Additional external documentation for this tag. , _tagExternalDocs :: Maybe ExternalDocs - } deriving (Eq, Ord, Show, Generic, Data, Typeable) + + -- | Specification Extensions + , _tagExtensions :: SpecificationExtensions + } deriving (Eq, Show, Generic, Data, Typeable) instance Hashable Tag instance IsString Tag where - fromString s = Tag (fromString s) Nothing Nothing + fromString s = Tag (fromString s) Nothing Nothing mempty -- | Allows referencing an external resource for extended documentation. data ExternalDocs = ExternalDocs @@ -918,6 +964,10 @@ data AdditionalProperties | AdditionalPropertiesSchema (Referenced Schema) deriving (Eq, Show, Data, Typeable) +newtype SpecificationExtensions = SpecificationExtensions { getSpecificationExtensions :: Definitions Value} + deriving (Eq, Show, Hashable, Data, Typeable, Semigroup, Monoid, SwaggerMonoid, AesonDefaultValue) + + ------------------------------------------------------------------------------- -- Generic instances ------------------------------------------------------------------------------- @@ -940,6 +990,11 @@ deriveGeneric ''OpenApi deriveGeneric ''Example deriveGeneric ''Encoding deriveGeneric ''Link +deriveGeneric ''Info +deriveGeneric ''Contact +deriveGeneric ''License +deriveGeneric ''ServerVariable +deriveGeneric ''Tag -- ======================================================================= -- Monoid instances @@ -1115,27 +1170,12 @@ instance ToJSON OpenApiType where instance ToJSON ParamLocation where toJSON = genericToJSON (jsonPrefix "Param") -instance ToJSON Info where - toJSON = genericToJSON (jsonPrefix "Info") - -instance ToJSON Contact where - toJSON = genericToJSON (jsonPrefix "Contact") - -instance ToJSON License where - toJSON = genericToJSON (jsonPrefix "License") - -instance ToJSON ServerVariable where - toJSON = genericToJSON (jsonPrefix "ServerVariable") - instance ToJSON ApiKeyLocation where toJSON = genericToJSON (jsonPrefix "ApiKey") instance ToJSON ApiKeyParams where toJSON = genericToJSON (jsonPrefix "apiKey") -instance ToJSON Tag where - toJSON = genericToJSON (jsonPrefix "Tag") - instance ToJSON ExternalDocs where toJSON = genericToJSON (jsonPrefix "ExternalDocs") @@ -1170,27 +1210,12 @@ instance FromJSON OpenApiType where instance FromJSON ParamLocation where parseJSON = genericParseJSON (jsonPrefix "Param") -instance FromJSON Info where - parseJSON = genericParseJSON (jsonPrefix "Info") - -instance FromJSON Contact where - parseJSON = genericParseJSON (jsonPrefix "Contact") - -instance FromJSON License where - parseJSON = genericParseJSON (jsonPrefix "License") - -instance FromJSON ServerVariable where - parseJSON = genericParseJSON (jsonPrefix "ServerVariable") - instance FromJSON ApiKeyLocation where parseJSON = genericParseJSON (jsonPrefix "ApiKey") instance FromJSON ApiKeyParams where parseJSON = genericParseJSON (jsonPrefix "apiKey") -instance FromJSON Tag where - parseJSON = genericParseJSON (jsonPrefix "Tag") - instance FromJSON ExternalDocs where parseJSON = genericParseJSON (jsonPrefix "ExternalDocs") @@ -1250,10 +1275,26 @@ instance ToJSON OpenApi where else id toEncoding = sopSwaggerGenericToEncoding +instance ToJSON Info where + toJSON = sopSwaggerGenericToJSON + toEncoding = sopSwaggerGenericToEncoding + +instance ToJSON Contact where + toJSON = sopSwaggerGenericToJSON + toEncoding = sopSwaggerGenericToEncoding + +instance ToJSON License where + toJSON = sopSwaggerGenericToJSON + toEncoding = sopSwaggerGenericToEncoding + instance ToJSON Server where toJSON = sopSwaggerGenericToJSON toEncoding = sopSwaggerGenericToEncoding +instance ToJSON ServerVariable where + toJSON = sopSwaggerGenericToJSON + toEncoding = sopSwaggerGenericToEncoding + instance ToJSON SecurityScheme where toJSON = sopSwaggerGenericToJSON toEncoding = sopSwaggerGenericToEncoding @@ -1328,6 +1369,10 @@ instance ToJSON Link where toJSON = sopSwaggerGenericToJSON toEncoding = sopSwaggerGenericToEncoding +instance ToJSON Tag where + toJSON = sopSwaggerGenericToJSON + toEncoding = sopSwaggerGenericToEncoding + instance ToJSON SecurityDefinitions where toJSON (SecurityDefinitions sd) = toJSON sd @@ -1358,6 +1403,12 @@ instance ToJSON ExpressionOrValue where instance ToJSON Callback where toJSON (Callback ps) = toJSON ps +instance ToJSON SpecificationExtensions where + toJSON = toJSON . addExtPrefix . getSpecificationExtensions + where + addExtPrefix = InsOrdHashMap.mapKeys ("x-" <>) + + -- ======================================================================= -- Manual FromJSON instances -- ======================================================================= @@ -1389,9 +1440,21 @@ instance FromJSON SecuritySchemeType where instance FromJSON OpenApi where parseJSON = sopSwaggerGenericParseJSON +instance FromJSON Info where + parseJSON = sopSwaggerGenericParseJSON + +instance FromJSON Contact where + parseJSON = sopSwaggerGenericParseJSON + +instance FromJSON License where + parseJSON = sopSwaggerGenericParseJSON + instance FromJSON Server where parseJSON = sopSwaggerGenericParseJSON +instance FromJSON ServerVariable where + parseJSON = sopSwaggerGenericParseJSON + instance FromJSON SecurityScheme where parseJSON = sopSwaggerGenericParseJSON @@ -1457,6 +1520,9 @@ instance FromJSON Encoding where instance FromJSON Link where parseJSON = sopSwaggerGenericParseJSON +instance FromJSON Tag where + parseJSON = sopSwaggerGenericParseJSON + instance FromJSON Reference where parseJSON (Object o) = Reference <$> o .: "$ref" parseJSON _ = empty @@ -1498,8 +1564,14 @@ instance FromJSON ExpressionOrValue where instance FromJSON Callback where parseJSON = fmap Callback . parseJSON +instance FromJSON SpecificationExtensions where + parseJSON = withObject "SpecificationExtensions" extFieldsParser + where + extFieldsParser = pure . SpecificationExtensions . InsOrdHashMap.fromList . catMaybes . filterExtFields + filterExtFields = fmap (\(k,v) -> fmap (\k' -> (k',v)) $ Text.stripPrefix "x-" k) . HashMap.toList + instance HasSwaggerAesonOptions Server where - swaggerAesonOptions _ = mkSwaggerAesonOptions "server" + swaggerAesonOptions _ = mkSwaggerAesonOptions "server" & saoSubObject ?~ "extensions" instance HasSwaggerAesonOptions Components where swaggerAesonOptions _ = mkSwaggerAesonOptions "components" instance HasSwaggerAesonOptions Header where @@ -1509,17 +1581,17 @@ instance AesonDefaultValue p => HasSwaggerAesonOptions (OAuth2Flow p) where instance HasSwaggerAesonOptions OAuth2Flows where swaggerAesonOptions _ = mkSwaggerAesonOptions "oauth2Flows" instance HasSwaggerAesonOptions Operation where - swaggerAesonOptions _ = mkSwaggerAesonOptions "operation" + swaggerAesonOptions _ = mkSwaggerAesonOptions "operation" & saoSubObject ?~ "extensions" instance HasSwaggerAesonOptions Param where swaggerAesonOptions _ = mkSwaggerAesonOptions "param" instance HasSwaggerAesonOptions PathItem where - swaggerAesonOptions _ = mkSwaggerAesonOptions "pathItem" + swaggerAesonOptions _ = mkSwaggerAesonOptions "pathItem" & saoSubObject ?~ "extensions" instance HasSwaggerAesonOptions Response where - swaggerAesonOptions _ = mkSwaggerAesonOptions "response" + swaggerAesonOptions _ = mkSwaggerAesonOptions "response" & saoSubObject ?~ "extensions" instance HasSwaggerAesonOptions RequestBody where - swaggerAesonOptions _ = mkSwaggerAesonOptions "requestBody" + swaggerAesonOptions _ = mkSwaggerAesonOptions "requestBody" & saoSubObject ?~ "extensions" instance HasSwaggerAesonOptions MediaTypeObject where - swaggerAesonOptions _ = mkSwaggerAesonOptions "mediaTypeObject" + swaggerAesonOptions _ = mkSwaggerAesonOptions "mediaTypeObject" & saoSubObject ?~ "extensions" instance HasSwaggerAesonOptions Responses where swaggerAesonOptions _ = mkSwaggerAesonOptions "responses" & saoSubObject ?~ "responses" instance HasSwaggerAesonOptions SecurityScheme where @@ -1528,13 +1600,29 @@ instance HasSwaggerAesonOptions Schema where swaggerAesonOptions _ = mkSwaggerAesonOptions "schema" & saoSubObject ?~ "paramSchema" instance HasSwaggerAesonOptions OpenApi where swaggerAesonOptions _ = mkSwaggerAesonOptions "swagger" & saoAdditionalPairs .~ [("openapi", "3.0.0")] + & saoSubObject ?~ "extensions" instance HasSwaggerAesonOptions Example where - swaggerAesonOptions _ = mkSwaggerAesonOptions "example" + swaggerAesonOptions _ = mkSwaggerAesonOptions "example" & saoSubObject ?~ "extensions" instance HasSwaggerAesonOptions Encoding where - swaggerAesonOptions _ = mkSwaggerAesonOptions "encoding" + swaggerAesonOptions _ = mkSwaggerAesonOptions "encoding" & saoSubObject ?~ "extensions" instance HasSwaggerAesonOptions Link where - swaggerAesonOptions _ = mkSwaggerAesonOptions "link" + swaggerAesonOptions _ = mkSwaggerAesonOptions "link" & saoSubObject ?~ "extensions" + +instance HasSwaggerAesonOptions Info where + swaggerAesonOptions _ = mkSwaggerAesonOptions "info" & saoSubObject ?~ "extensions" + +instance HasSwaggerAesonOptions Contact where + swaggerAesonOptions _ = mkSwaggerAesonOptions "contact" & saoSubObject ?~ "extensions" + +instance HasSwaggerAesonOptions License where + swaggerAesonOptions _ = mkSwaggerAesonOptions "license" & saoSubObject ?~ "extensions" + +instance HasSwaggerAesonOptions ServerVariable where + swaggerAesonOptions _ = mkSwaggerAesonOptions "serverVariable" & saoSubObject ?~ "extensions" + +instance HasSwaggerAesonOptions Tag where + swaggerAesonOptions _ = mkSwaggerAesonOptions "tag" & saoSubObject ?~ "extensions" instance AesonDefaultValue Server instance AesonDefaultValue Components From 2a4d092fec3ce25a0784ba57e9a1d91366622004 Mon Sep 17 00:00:00 2001 From: Sreenidhi Date: Fri, 9 Apr 2021 13:04:58 +0530 Subject: [PATCH 2/9] explicit ToEncoding for Schema and Referenced --- src/Data/OpenApi/Internal.hs | 20 +++++++++++++++++--- src/Data/OpenApi/Internal/AesonUtils.hs | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/Data/OpenApi/Internal.hs b/src/Data/OpenApi/Internal.hs index 64a54c84..d14c6592 100644 --- a/src/Data/OpenApi/Internal.hs +++ b/src/Data/OpenApi/Internal.hs @@ -56,7 +56,8 @@ import Data.OpenApi.Internal.AesonUtils (sopSwaggerGenericToJSON ,saoAdditionalPairs ,saoSubObject) import Data.OpenApi.Internal.Utils -import Data.OpenApi.Internal.AesonUtils (sopSwaggerGenericToEncoding) +import Data.OpenApi.Internal.AesonUtils (sopSwaggerGenericToEncoding + ,sopSwaggerGenericToEncodingWithOpts) -- $setup -- >>> :seti -XDataKinds @@ -1338,6 +1339,8 @@ instance ToJSON SecurityScheme where instance ToJSON Schema where toJSON = sopSwaggerGenericToJSONWithOpts $ mkSwaggerAesonOptions "schema" & saoSubObject ?~ "items" + toEncoding = sopSwaggerGenericToEncodingWithOpts $ + mkSwaggerAesonOptions "schema" & saoSubObject ?~ "items" instance ToJSON Header where toJSON = sopSwaggerGenericToJSON @@ -1414,15 +1417,26 @@ instance ToJSON SecurityDefinitions where instance ToJSON Reference where toJSON (Reference ref) = object [ "$ref" .= ref ] + toEncoding (Reference ref) = pairs ("$ref" .= ref) referencedToJSON :: ToJSON a => Text -> Referenced a -> Value referencedToJSON prefix (Ref (Reference ref)) = object [ "$ref" .= (prefix <> ref) ] referencedToJSON _ (Inline x) = toJSON x -instance ToJSON (Referenced Schema) where toJSON = referencedToJSON "#/components/schemas/" +referencedToEncoding :: ToJSON a => Text -> Referenced a -> JSON.Encoding +referencedToEncoding prefix (Ref (Reference ref)) = pairs ("$ref" .= (prefix <> ref) ) +referencedToEncoding _ (Inline x) = toEncoding x + +instance ToJSON (Referenced Schema) where + toJSON = referencedToJSON "#/components/schemas/" + toEncoding = referencedToEncoding "#/components/schemas/" + +instance ToJSON (Referenced RequestBody) where + toJSON = referencedToJSON "#/components/requestBodies/" + toEncoding = referencedToEncoding "#/components/requestBodies/" + instance ToJSON (Referenced Param) where toJSON = referencedToJSON "#/components/parameters/" instance ToJSON (Referenced Response) where toJSON = referencedToJSON "#/components/responses/" -instance ToJSON (Referenced RequestBody) where toJSON = referencedToJSON "#/components/requestBodies/" instance ToJSON (Referenced Example) where toJSON = referencedToJSON "#/components/examples/" instance ToJSON (Referenced Header) where toJSON = referencedToJSON "#/components/headers/" instance ToJSON (Referenced Link) where toJSON = referencedToJSON "#/components/links/" diff --git a/src/Data/OpenApi/Internal/AesonUtils.hs b/src/Data/OpenApi/Internal/AesonUtils.hs index 98e1ce06..f5e14308 100644 --- a/src/Data/OpenApi/Internal/AesonUtils.hs +++ b/src/Data/OpenApi/Internal/AesonUtils.hs @@ -11,6 +11,7 @@ module Data.OpenApi.Internal.AesonUtils ( sopSwaggerGenericToJSON, sopSwaggerGenericToEncoding, sopSwaggerGenericToJSONWithOpts, + sopSwaggerGenericToEncodingWithOpts, sopSwaggerGenericParseJSON, -- * Options HasSwaggerAesonOptions(..), @@ -267,6 +268,24 @@ sopSwaggerGenericToEncoding x = proxy = Proxy :: Proxy a opts = swaggerAesonOptions proxy +sopSwaggerGenericToEncodingWithOpts + :: forall a xs. + ( HasDatatypeInfo a + , HasSwaggerAesonOptions a + , All2 ToJSON (Code a) + , All2 Eq (Code a) + , Code a ~ '[xs] + ) + => SwaggerAesonOptions + -> a + -> Encoding +sopSwaggerGenericToEncodingWithOpts opts x = + let ps = sopSwaggerGenericToEncoding' opts (from x) (datatypeInfo proxy) defs + in pairs (pairsToSeries (opts ^. saoAdditionalPairs) <> ps) + where + proxy = Proxy :: Proxy a + defs = hcpure (Proxy :: Proxy AesonDefaultValue) defaultValue + pairsToSeries :: [Pair] -> Series pairsToSeries = foldMap (\(k, v) -> (k .= v)) From d0d79a7e95b001344c3d8c2fa682fe58b2349fb9 Mon Sep 17 00:00:00 2001 From: Magesh Date: Wed, 21 Apr 2021 00:17:32 +0530 Subject: [PATCH 3/9] Made SubObjects as List and Added extensions for following ExternalDocumentation Responses Schema XML Security Scheme OAUTH Flows OAUTH Flow --- src/Data/OpenApi/Internal.hs | 118 ++++++++++++++++-------- src/Data/OpenApi/Internal/AesonUtils.hs | 10 +- 2 files changed, 84 insertions(+), 44 deletions(-) diff --git a/src/Data/OpenApi/Internal.hs b/src/Data/OpenApi/Internal.hs index 64a54c84..aa2b1877 100644 --- a/src/Data/OpenApi/Internal.hs +++ b/src/Data/OpenApi/Internal.hs @@ -698,6 +698,9 @@ data Schema = Schema , _schemaUniqueItems :: Maybe Bool , _schemaEnum :: Maybe [Value] , _schemaMultipleOf :: Maybe Scientific + + -- | Specification Extensions + , _schemaExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | Regex pattern for @string@ type. @@ -744,6 +747,9 @@ data Xml = Xml -- Default value is @False@. -- The definition takes effect only when defined alongside type being array (outside the items). , _xmlWrapped :: Maybe Bool + + -- | Specification Extensions + , _xmlExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | A container for the expected responses of an operation. @@ -759,6 +765,9 @@ data Responses = Responses -- | Any HTTP status code can be used as the property name (one property per HTTP status code). -- Describes the expected response for those HTTP status codes. , _responsesResponses :: InsOrdHashMap HttpStatusCode (Referenced Response) + + -- | Specification Extensions + , _responsesExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) type HttpStatusCode = Int @@ -865,6 +874,9 @@ data OAuth2Flow p = OAuth2Flow -- A map between the scope name and a short description for it. -- The map MAY be empty. , _oAuth2Scopes :: InsOrdHashMap Text Text + + -- | Specification Extensions + , _oAuth2Extensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) data OAuth2Flows = OAuth2Flows @@ -879,6 +891,9 @@ data OAuth2Flows = OAuth2Flows -- | Configuration for the OAuth Authorization Code flow , _oAuth2FlowsAuthorizationCode :: Maybe (OAuth2Flow OAuth2AuthorizationCodeFlow) + + -- | Specification Extensions + , _oAuth2FlowsExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) type BearerFormat = Text @@ -919,6 +934,9 @@ data SecurityScheme = SecurityScheme -- | A short description for security scheme. , _securitySchemeDescription :: Maybe Text + + -- | Specification Extensions + , _securitySchemeExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) newtype SecurityDefinitions @@ -965,7 +983,10 @@ data ExternalDocs = ExternalDocs -- | The URL for the target documentation. , _externalDocsUrl :: URL - } deriving (Eq, Ord, Show, Generic, Data, Typeable) + + -- | Specification Extensions + , _externalDocsExtensions :: SpecificationExtensions + } deriving (Eq, Show, Generic, Data, Typeable) instance Hashable ExternalDocs @@ -982,7 +1003,7 @@ data Referenced a instance IsString a => IsString (Referenced a) where fromString = Inline . fromString -newtype URL = URL { getUrl :: Text } deriving (Eq, Ord, Show, Hashable, ToJSON, FromJSON, Data, Typeable) +newtype URL = URL { getUrl :: Text } deriving (Eq, Ord, Show, Hashable, ToJSON, FromJSON, Data, Typeable, AesonDefaultValue) data AdditionalProperties = AdditionalPropertiesAllowed Bool @@ -1020,6 +1041,8 @@ deriveGeneric ''Contact deriveGeneric ''License deriveGeneric ''ServerVariable deriveGeneric ''Tag +deriveGeneric ''Xml +deriveGeneric ''ExternalDocs -- ======================================================================= -- Monoid instances @@ -1121,6 +1144,7 @@ instance Semigroup OAuth2Flows where , _oAuth2FlowsPassword = _oAuth2FlowsPassword l <> _oAuth2FlowsPassword r , _oAuth2FlowsClientCredentials = _oAuth2FlowsClientCredentials l <> _oAuth2FlowsClientCredentials r , _oAuth2FlowsAuthorizationCode = _oAuth2FlowsAuthorizationCode l <> _oAuth2FlowsAuthorizationCode r + , _oAuth2FlowsExtensions = _oAuth2FlowsExtensions l <> _oAuth2FlowsExtensions r } instance Monoid OAuth2Flows where @@ -1128,9 +1152,9 @@ instance Monoid OAuth2Flows where mappend = (<>) instance Semigroup SecurityScheme where - SecurityScheme (SecuritySchemeOAuth2 lFlows) lDesc - <> SecurityScheme (SecuritySchemeOAuth2 rFlows) rDesc = - SecurityScheme (SecuritySchemeOAuth2 $ lFlows <> rFlows) (swaggerMappend lDesc rDesc) + SecurityScheme (SecuritySchemeOAuth2 lFlows) lDesc lExt + <> SecurityScheme (SecuritySchemeOAuth2 rFlows) rDesc rExt = + SecurityScheme (SecuritySchemeOAuth2 $ lFlows <> rFlows) (swaggerMappend lDesc rDesc) (lExt <> rExt) l <> _ = l instance Semigroup SecurityDefinitions where @@ -1201,12 +1225,6 @@ instance ToJSON ApiKeyLocation where instance ToJSON ApiKeyParams where toJSON = genericToJSON (jsonPrefix "apiKey") -instance ToJSON ExternalDocs where - toJSON = genericToJSON (jsonPrefix "ExternalDocs") - -instance ToJSON Xml where - toJSON = genericToJSON (jsonPrefix "Xml") - instance ToJSON Discriminator where toJSON = genericToJSON (jsonPrefix "Discriminator") @@ -1241,9 +1259,6 @@ instance FromJSON ApiKeyLocation where instance FromJSON ApiKeyParams where parseJSON = genericParseJSON (jsonPrefix "apiKey") -instance FromJSON ExternalDocs where - parseJSON = genericParseJSON (jsonPrefix "ExternalDocs") - instance FromJSON Discriminator where parseJSON = genericParseJSON (jsonPrefix "Discriminator") @@ -1337,7 +1352,7 @@ instance ToJSON SecurityScheme where instance ToJSON Schema where toJSON = sopSwaggerGenericToJSONWithOpts $ - mkSwaggerAesonOptions "schema" & saoSubObject ?~ "items" + mkSwaggerAesonOptions "schema" & saoSubObject .~ ["items", "extensions"] instance ToJSON Header where toJSON = sopSwaggerGenericToJSON @@ -1409,6 +1424,14 @@ instance ToJSON Tag where toJSON = sopSwaggerGenericToJSON toEncoding = sopSwaggerGenericToEncoding +instance ToJSON Xml where + toJSON = sopSwaggerGenericToJSON + toEncoding = sopSwaggerGenericToEncoding + +instance ToJSON ExternalDocs where + toJSON = sopSwaggerGenericToJSON + toEncoding = sopSwaggerGenericToEncoding + instance ToJSON SecurityDefinitions where toJSON (SecurityDefinitions sd) = toJSON sd @@ -1531,9 +1554,17 @@ instance FromJSON Param where instance FromJSON Responses where parseJSON (Object o) = Responses <$> o .:? "default" - <*> parseJSON (Object (HashMap.delete "default" o)) + <*> parseJSON (Object (HashMap.filterWithKey (\k _ -> not $ isExt k) + $ HashMap.delete "default" o)) + <*> case HashMap.filterWithKey (\k _ -> isExt k) o of + exts | HashMap.null exts -> pure (SpecificationExtensions mempty) + | otherwise -> parseJSON (Object exts) parseJSON _ = empty +isExt :: Text -> Bool +isExt = Text.isPrefixOf "x-" + + instance FromJSON Example where parseJSON = sopSwaggerGenericParseJSON @@ -1564,6 +1595,12 @@ instance FromJSON Link where instance FromJSON Tag where parseJSON = sopSwaggerGenericParseJSON +instance FromJSON Xml where + parseJSON = sopSwaggerGenericParseJSON + +instance FromJSON ExternalDocs where + parseJSON = sopSwaggerGenericParseJSON + instance FromJSON Reference where parseJSON (Object o) = Reference <$> o .: "$ref" parseJSON _ = empty @@ -1590,9 +1627,6 @@ instance FromJSON (Referenced Header) where parseJSON = referencedParseJSON "# instance FromJSON (Referenced Link) where parseJSON = referencedParseJSON "#/components/links/" instance FromJSON (Referenced Callback) where parseJSON = referencedParseJSON "#/components/callbacks/" -instance FromJSON Xml where - parseJSON = genericParseJSON (jsonPrefix "xml") - instance FromJSON AdditionalProperties where parseJSON (Bool b) = pure $ AdditionalPropertiesAllowed b parseJSON js = AdditionalPropertiesSchema <$> parseJSON js @@ -1612,58 +1646,64 @@ instance FromJSON SpecificationExtensions where filterExtFields = fmap (\(k,v) -> fmap (\k' -> (k',v)) $ Text.stripPrefix "x-" k) . HashMap.toList instance HasSwaggerAesonOptions Server where - swaggerAesonOptions _ = mkSwaggerAesonOptions "server" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "server" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Components where swaggerAesonOptions _ = mkSwaggerAesonOptions "components" instance HasSwaggerAesonOptions Header where swaggerAesonOptions _ = mkSwaggerAesonOptions "header" instance AesonDefaultValue p => HasSwaggerAesonOptions (OAuth2Flow p) where - swaggerAesonOptions _ = mkSwaggerAesonOptions "oauth2" & saoSubObject ?~ "params" + swaggerAesonOptions _ = mkSwaggerAesonOptions "oauth2" & saoSubObject .~ ["params", "extensions"] instance HasSwaggerAesonOptions OAuth2Flows where - swaggerAesonOptions _ = mkSwaggerAesonOptions "oauth2Flows" + swaggerAesonOptions _ = mkSwaggerAesonOptions "oauth2Flows" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Operation where - swaggerAesonOptions _ = mkSwaggerAesonOptions "operation" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "operation" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Param where swaggerAesonOptions _ = mkSwaggerAesonOptions "param" instance HasSwaggerAesonOptions PathItem where - swaggerAesonOptions _ = mkSwaggerAesonOptions "pathItem" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "pathItem" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Response where - swaggerAesonOptions _ = mkSwaggerAesonOptions "response" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "response" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions RequestBody where - swaggerAesonOptions _ = mkSwaggerAesonOptions "requestBody" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "requestBody" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions MediaTypeObject where - swaggerAesonOptions _ = mkSwaggerAesonOptions "mediaTypeObject" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "mediaTypeObject" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Responses where - swaggerAesonOptions _ = mkSwaggerAesonOptions "responses" & saoSubObject ?~ "responses" + swaggerAesonOptions _ = mkSwaggerAesonOptions "responses" & saoSubObject .~ ["responses", "extensions"] instance HasSwaggerAesonOptions SecurityScheme where - swaggerAesonOptions _ = mkSwaggerAesonOptions "securityScheme" & saoSubObject ?~ "type" + swaggerAesonOptions _ = mkSwaggerAesonOptions "securityScheme" & saoSubObject .~ ["type", "extensions"] instance HasSwaggerAesonOptions Schema where - swaggerAesonOptions _ = mkSwaggerAesonOptions "schema" & saoSubObject ?~ "paramSchema" + swaggerAesonOptions _ = mkSwaggerAesonOptions "schema" & saoSubObject .~ ["paramSchema", "extensions"] instance HasSwaggerAesonOptions OpenApi where swaggerAesonOptions _ = mkSwaggerAesonOptions "swagger" & saoAdditionalPairs .~ [("openapi", "3.0.0")] - & saoSubObject ?~ "extensions" + & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Example where - swaggerAesonOptions _ = mkSwaggerAesonOptions "example" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "example" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Encoding where - swaggerAesonOptions _ = mkSwaggerAesonOptions "encoding" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "encoding" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Link where - swaggerAesonOptions _ = mkSwaggerAesonOptions "link" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "link" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Info where - swaggerAesonOptions _ = mkSwaggerAesonOptions "info" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "info" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Contact where - swaggerAesonOptions _ = mkSwaggerAesonOptions "contact" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "contact" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions License where - swaggerAesonOptions _ = mkSwaggerAesonOptions "license" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "license" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions ServerVariable where - swaggerAesonOptions _ = mkSwaggerAesonOptions "serverVariable" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "serverVariable" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Tag where - swaggerAesonOptions _ = mkSwaggerAesonOptions "tag" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "tag" & saoSubObject .~ ["extensions"] + +instance HasSwaggerAesonOptions Xml where + swaggerAesonOptions _ = mkSwaggerAesonOptions "xml" & saoSubObject .~ ["extensions"] + +instance HasSwaggerAesonOptions ExternalDocs where + swaggerAesonOptions _ = mkSwaggerAesonOptions "externalDocs" & saoSubObject .~ ["extensions"] instance AesonDefaultValue Server instance AesonDefaultValue Components diff --git a/src/Data/OpenApi/Internal/AesonUtils.hs b/src/Data/OpenApi/Internal/AesonUtils.hs index 98e1ce06..a0cf08dc 100644 --- a/src/Data/OpenApi/Internal/AesonUtils.hs +++ b/src/Data/OpenApi/Internal/AesonUtils.hs @@ -48,13 +48,13 @@ import qualified Data.HashSet.InsOrd as InsOrdHS data SwaggerAesonOptions = SwaggerAesonOptions { _saoPrefix :: String , _saoAdditionalPairs :: [(Text, Value)] - , _saoSubObject :: Maybe String + , _saoSubObject :: [String] } mkSwaggerAesonOptions :: String -- ^ prefix -> SwaggerAesonOptions -mkSwaggerAesonOptions pfx = SwaggerAesonOptions pfx [] Nothing +mkSwaggerAesonOptions pfx = SwaggerAesonOptions pfx [] [] makeLenses ''SwaggerAesonOptions @@ -153,7 +153,7 @@ sopSwaggerGenericToJSON'' (SwaggerAesonOptions prefix _ sub) = go go :: (All ToJSON ys, All Eq ys) => NP I ys -> NP FieldInfo ys -> NP Maybe ys -> [Pair] go Nil Nil Nil = [] go (I x :* xs) (FieldInfo name :* names) (def :* defs) - | Just name' == sub = case json of + | name' `elem` sub = case json of Object m -> HM.toList m ++ rest Null -> rest _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show json @@ -226,7 +226,7 @@ sopSwaggerGenericParseJSON'' (SwaggerAesonOptions prefix _ sub) obj = go go :: (All FromJSON ys, All Eq ys) => NP FieldInfo ys -> NP Maybe ys -> Parser (NP I ys) go Nil Nil = pure Nil go (FieldInfo name :* names) (def :* defs) - | Just name' == sub = + | name' `elem` sub = -- Note: we might strip fields of outer structure. cons <$> (withDef $ parseJSON $ Object obj) <*> rest | otherwise = case def of @@ -293,7 +293,7 @@ sopSwaggerGenericToEncoding'' (SwaggerAesonOptions prefix _ sub) = go go :: (All ToJSON ys, All Eq ys) => NP I ys -> NP FieldInfo ys -> NP Maybe ys -> Series go Nil Nil Nil = mempty go (I x :* xs) (FieldInfo name :* names) (def :* defs) - | Just name' == sub = case toJSON x of + | name' `elem` sub = case toJSON x of Object m -> pairsToSeries (HM.toList m) <> rest Null -> rest _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show (toJSON x) From 7a30d5ae83e0868414dac338ee825f9ab692df3c Mon Sep 17 00:00:00 2001 From: Magesh Date: Wed, 21 Apr 2021 02:54:22 +0530 Subject: [PATCH 4/9] Fixed the tests --- src/Data/OpenApi.hs | 14 ++++++------ src/Data/OpenApi/Internal/ParamSchema.hs | 2 +- src/Data/OpenApi/Internal/Schema.hs | 28 ++++++++++++------------ src/Data/OpenApi/Operation.hs | 8 +++---- src/Data/OpenApi/Optics.hs | 4 ++-- test/Data/OpenApiSpec.hs | 28 ++++++++++++++++++------ 6 files changed, 49 insertions(+), 35 deletions(-) diff --git a/src/Data/OpenApi.hs b/src/Data/OpenApi.hs index 17b3aa9f..91d34af2 100644 --- a/src/Data/OpenApi.hs +++ b/src/Data/OpenApi.hs @@ -155,7 +155,7 @@ import Data.OpenApi.Internal -- In this library you can use @'mempty'@ for a default/empty value. For instance: -- -- >>> BSL.putStrLn $ encode (mempty :: OpenApi) --- {"openapi":"3.0.0","info":{"version":"","title":""},"components":{}} +-- {"openapi":"3.0.0","info":{"title":"","version":""},"components":{}} -- -- As you can see some spec properties (e.g. @"version"@) are there even when the spec is empty. -- That is because these properties are actually required ones. @@ -164,12 +164,12 @@ import Data.OpenApi.Internal -- although it is not strictly necessary: -- -- >>> BSL.putStrLn $ encode mempty { _infoTitle = "Todo API", _infoVersion = "1.0" } --- {"version":"1.0","title":"Todo API"} +-- {"title":"Todo API","version":"1.0"} -- -- You can merge two values using @'mappend'@ or its infix version @('<>')@: -- -- >>> BSL.putStrLn $ encode $ mempty { _infoTitle = "Todo API" } <> mempty { _infoVersion = "1.0" } --- {"version":"1.0","title":"Todo API"} +-- {"title":"Todo API","version":"1.0"} -- -- This can be useful for combining specifications of endpoints into a whole API specification: -- @@ -202,7 +202,7 @@ import Data.OpenApi.Internal -- & at 200 ?~ ("OK" & _Inline.content.at "application/json" ?~ (mempty & schema ?~ Ref (Reference "User"))) -- & at 404 ?~ "User info not found")) ] -- :} --- {"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"}}}} +-- {"openapi":"3.0.0","info":{"title":"","version":""},"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"}}}} -- -- In the snippet above we declare an API with a single path @/user@. This path provides method @GET@ -- which produces @application/json@ output. It should respond with code @200@ and body specified @@ -221,7 +221,7 @@ import Data.OpenApi.Internal -- & type_ ?~ OpenApiBoolean -- & description ?~ "To be or not to be" -- :} --- {"type":"boolean","description":"To be or not to be"} +-- {"description":"To be or not to be","type":"boolean"} -- -- Additionally, to simplify working with @'Response'@, both @'Operation'@ and @'Responses'@ -- have direct access to it via @'at' code@. Example: @@ -280,7 +280,7 @@ import Data.OpenApi.Internal -- >>> BSL.putStrLn $ encode (Person "David" 28) -- {"age":28,"name":"David"} -- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Person) --- {"required":["name","age"],"type":"object","properties":{"age":{"type":"integer"},"name":{"type":"string"}}} +-- {"required":["name","age"],"properties":{"name":{"type":"string"},"age":{"type":"integer"}},"type":"object"} -- -- This package implements OpenAPI 3.0 spec, which supports @oneOf@ in schemas, allowing any sum types -- to be faithfully represented. All sum encodings supported by @aeson@ are supported here as well, with @@ -292,7 +292,7 @@ import Data.OpenApi.Internal -- >>> instance ToJSON Error -- >>> instance ToSchema Error -- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Error) --- {"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"} +-- {"oneOf":[{"required":["userId","tag"],"properties":{"userId":{"type":"integer","maximum":9223372036854775807,"minimum":-9223372036854775808},"tag":{"type":"string","enum":["ErrorNoUser"]}},"type":"object"},{"required":["requiredPermission","tag"],"properties":{"requiredPermission":{"type":"string"},"tag":{"type":"string","enum":["ErrorAccessDenied"]}},"type":"object"}],"type":"object"} -- $manipulation -- Sometimes you have to work with an imported or generated @'Swagger'@. diff --git a/src/Data/OpenApi/Internal/ParamSchema.hs b/src/Data/OpenApi/Internal/ParamSchema.hs index ede6aa57..68856ea2 100644 --- a/src/Data/OpenApi/Internal/ParamSchema.hs +++ b/src/Data/OpenApi/Internal/ParamSchema.hs @@ -156,7 +156,7 @@ instance ToParamSchema Word64 where -- | Default plain schema for @'Bounded'@, @'Integral'@ types. -- -- >>> encode $ toParamSchemaBoundedIntegral (Proxy :: Proxy Int8) --- "{\"maximum\":127,\"minimum\":-128,\"type\":\"integer\"}" +-- "{\"type\":\"integer\",\"maximum\":127,\"minimum\":-128}" toParamSchemaBoundedIntegral :: forall a t. (Bounded a, Integral a) => Proxy a -> Schema toParamSchemaBoundedIntegral _ = mempty & type_ ?~ OpenApiInteger diff --git a/src/Data/OpenApi/Internal/Schema.hs b/src/Data/OpenApi/Internal/Schema.hs index 1ea3ead0..aab705ab 100644 --- a/src/Data/OpenApi/Internal/Schema.hs +++ b/src/Data/OpenApi/Internal/Schema.hs @@ -162,7 +162,7 @@ declareSchema = fmap _namedSchemaSchema . declareNamedSchema -- >>> toNamedSchema (Proxy :: Proxy Day) ^. name -- Just "Day" -- >>> BSL.putStrLn $ encode (toNamedSchema (Proxy :: Proxy Day) ^. schema) --- {"example":"2016-07-22","format":"date","type":"string"} +-- {"example":"2016-07-22","type":"string","format":"date"} toNamedSchema :: ToSchema a => Proxy a -> NamedSchema toNamedSchema = undeclare . declareNamedSchema @@ -179,10 +179,10 @@ schemaName = _namedSchemaName . toNamedSchema -- | Convert a type into a schema. -- -- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Int8) --- {"maximum":127,"minimum":-128,"type":"integer"} +-- {"type":"integer","maximum":127,"minimum":-128} -- -- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy [Day]) --- {"items":{"$ref":"#/components/schemas/Day"},"type":"array"} +-- {"type":"array","items":{"$ref":"#/components/schemas/Day"}} toSchema :: ToSchema a => Proxy a -> Schema toSchema = _namedSchemaSchema . toNamedSchema @@ -262,7 +262,7 @@ inlineAllSchemas = inlineSchemasWhen (const True) -- | Convert a type into a schema without references. -- -- >>> BSL.putStrLn $ encode $ toInlinedSchema (Proxy :: Proxy [Day]) --- {"items":{"example":"2016-07-22","format":"date","type":"string"},"type":"array"} +-- {"type":"array","items":{"example":"2016-07-22","format":"date","type":"string"}} -- -- __WARNING:__ @'toInlinedSchema'@ will produce infinite schema -- when inlining recursive schemas. @@ -299,15 +299,15 @@ inlineNonRecursiveSchemas defs = inlineSchemasWhen nonRecursive defs -- {"example":"hello","type":"string"} -- -- >>> BSL.putStrLn $ encode $ sketchSchema (1, 2, 3) --- {"example":[1,2,3],"items":{"type":"number"},"type":"array"} +-- {"example":[1,2,3],"type":"array","items":{"type":"number"}} -- -- >>> BSL.putStrLn $ encode $ sketchSchema ("Jack", 25) --- {"example":["Jack",25],"items":[{"type":"string"},{"type":"number"}],"type":"array"} +-- {"example":["Jack",25],"type":"array","items":[{"type":"string"},{"type":"number"}]} -- -- >>> data Person = Person { name :: String, age :: Int } deriving (Generic) -- >>> instance ToJSON Person -- >>> BSL.putStrLn $ encode $ sketchSchema (Person "Jack" 25) --- {"example":{"age":25,"name":"Jack"},"required":["age","name"],"type":"object","properties":{"age":{"type":"number"},"name":{"type":"string"}}} +-- {"required":["age","name"],"properties":{"age":{"type":"number"},"name":{"type":"string"}},"example":{"age":25,"name":"Jack"},"type":"object"} sketchSchema :: ToJSON a => a -> Schema sketchSchema = sketch . toJSON where @@ -340,18 +340,18 @@ sketchSchema = sketch . toJSON -- Produced schema uses as much constraints as possible. -- -- >>> BSL.putStrLn $ encode $ sketchStrictSchema "hello" --- {"maxLength":5,"pattern":"hello","minLength":5,"type":"string","enum":["hello"]} +-- {"type":"string","maxLength":5,"minLength":5,"pattern":"hello","enum":["hello"]} -- -- >>> BSL.putStrLn $ encode $ sketchStrictSchema (1, 2, 3) --- {"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]]} +-- {"type":"array","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,"minItems":3,"uniqueItems":true,"enum":[[1,2,3]]} -- -- >>> BSL.putStrLn $ encode $ sketchStrictSchema ("Jack", 25) --- {"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]]} +-- {"type":"array","items":[{"maxLength":4,"pattern":"Jack","minLength":4,"type":"string","enum":["Jack"]},{"maximum":25,"minimum":25,"multipleOf":25,"type":"number","enum":[25]}],"maxItems":2,"minItems":2,"uniqueItems":true,"enum":[["Jack",25]]} -- -- >>> data Person = Person { name :: String, age :: Int } deriving (Generic) -- >>> instance ToJSON Person -- >>> BSL.putStrLn $ encode $ sketchStrictSchema (Person "Jack" 25) --- {"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"]}}} +-- {"required":["age","name"],"properties":{"age":{"type":"number","maximum":25,"minimum":25,"enum":[25],"multipleOf":25},"name":{"type":"string","maxLength":4,"minLength":4,"pattern":"Jack","enum":["Jack"]}},"maxProperties":2,"minProperties":2,"type":"object","enum":[{"age":25,"name":"Jack"}]} sketchStrictSchema :: ToJSON a => a -> Schema sketchStrictSchema = go . toJSON where @@ -554,7 +554,7 @@ instance ToSchema a => ToSchema (Identity a) where declareNamedSchema _ = declar -- | Default schema for @'Bounded'@, @'Integral'@ types. -- -- >>> BSL.putStrLn $ encode $ toSchemaBoundedIntegral (Proxy :: Proxy Int16) --- {"maximum":32767,"minimum":-32768,"type":"integer"} +-- {"type":"integer","maximum":32767,"minimum":-32768} toSchemaBoundedIntegral :: forall a. (Bounded a, Integral a) => Proxy a -> Schema toSchemaBoundedIntegral _ = mempty & type_ ?~ OpenApiInteger @@ -587,7 +587,7 @@ genericDeclareNamedSchemaNewtype opts f proxy = genericNameSchema opts proxy <$> -- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show) -- >>> type ImageUrl = T.Text -- >>> BSL.putStrLn $ encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl)) --- {"type":"object","properties":{"Focus":{"type":"string"},"Disabled":{"type":"string"},"Active":{"type":"string"},"Neutral":{"type":"string"},"Hover":{"type":"string"}}} +-- {"properties":{"Neutral":{"type":"string"},"Focus":{"type":"string"},"Active":{"type":"string"},"Hover":{"type":"string"},"Disabled":{"type":"string"}},"type":"object"} -- -- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'. -- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used. @@ -615,7 +615,7 @@ declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key o -- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show) -- >>> type ImageUrl = T.Text -- >>> BSL.putStrLn $ encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl)) --- {"type":"object","properties":{"Focus":{"type":"string"},"Disabled":{"type":"string"},"Active":{"type":"string"},"Neutral":{"type":"string"},"Hover":{"type":"string"}}} +-- {"properties":{"Neutral":{"type":"string"},"Focus":{"type":"string"},"Active":{"type":"string"},"Hover":{"type":"string"},"Disabled":{"type":"string"}},"type":"object"} -- -- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'. -- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used. diff --git a/src/Data/OpenApi/Operation.hs b/src/Data/OpenApi/Operation.hs index 5558aa33..999d01ae 100644 --- a/src/Data/OpenApi/Operation.hs +++ b/src/Data/OpenApi/Operation.hs @@ -83,9 +83,9 @@ allOperations = paths.traverse.template -- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ ok & post ?~ ok)] -- >>> let sub = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)] -- >>> BSL.putStrLn $ encode api --- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/user":{"get":{"responses":{"200":{"description":"OK"}}},"post":{"responses":{"200":{"description":"OK"}}}}},"components":{}} +-- {"openapi":"3.0.0","info":{"title":"","version":""},"paths":{"/user":{"get":{"responses":{"200":{"description":"OK"}}},"post":{"responses":{"200":{"description":"OK"}}}}},"components":{}} -- >>> BSL.putStrLn $ encode $ api & operationsOf sub . at 404 ?~ "Not found" --- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/user":{"get":{"responses":{"404":{"description":"Not found"},"200":{"description":"OK"}}},"post":{"responses":{"200":{"description":"OK"}}}}},"components":{}} +-- {"openapi":"3.0.0","info":{"title":"","version":""},"paths":{"/user":{"get":{"responses":{"404":{"description":"Not found"},"200":{"description":"OK"}}},"post":{"responses":{"200":{"description":"OK"}}}}},"components":{}} operationsOf :: OpenApi -> Traversal' OpenApi Operation operationsOf sub = paths.itraversed.withIndex.subops where @@ -124,7 +124,7 @@ applyTagsFor ops ts swag = swag -- FIXME doc -- -- >>> BSL.putStrLn $ encode $ runDeclare (declareResponse "application/json" (Proxy :: Proxy Day)) mempty --- [{"Day":{"example":"2016-07-22","format":"date","type":"string"}},{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Day"}}}}] +-- [{"Day":{"example":"2016-07-22","type":"string","format":"date"}},{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Day"}}}}] declareResponse :: ToSchema a => MediaType -> Proxy a -> Declare (Definitions Schema) Response declareResponse cType proxy = do s <- declareSchemaRef proxy @@ -144,7 +144,7 @@ declareResponse cType proxy = do -- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)] -- >>> let res = declareResponse "application/json" (Proxy :: Proxy Day) -- >>> BSL.putStrLn $ encode $ api & setResponse 200 res --- {"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"}}}} +-- {"openapi":"3.0.0","info":{"title":"","version":""},"paths":{"/user":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Day"}}},"description":""}}}}},"components":{"schemas":{"Day":{"example":"2016-07-22","type":"string","format":"date"}}}} -- -- See also @'setResponseWith'@. setResponse :: HttpStatusCode -> Declare (Definitions Schema) Response -> OpenApi -> OpenApi diff --git a/src/Data/OpenApi/Optics.hs b/src/Data/OpenApi/Optics.hs index 5dbbbc0c..6cbb20e6 100644 --- a/src/Data/OpenApi/Optics.hs +++ b/src/Data/OpenApi/Optics.hs @@ -29,7 +29,7 @@ -- & at 200 ?~ ("OK" & #_Inline % #content % at "application/json" ?~ (mempty & #schema ?~ Ref (Reference "User"))) -- & at 404 ?~ "User info not found")) ] -- :} --- {"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"}}}} +-- {"openapi":"3.0.0","info":{"title":"","version":""},"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"}}}} -- -- For convenience optics are defined as /labels/. It means that field accessor -- names can be overloaded for different types. One such common field is @@ -43,7 +43,7 @@ -- & #type ?~ OpenApiBoolean -- & #description ?~ "To be or not to be" -- :} --- {"type":"boolean","description":"To be or not to be"} +-- {"description":"To be or not to be","type":"boolean"} -- -- Additionally, to simplify working with @'Response'@, both @'Operation'@ and -- @'Responses'@ have direct access to it via @'Optics.Core.At.at'@. Example: diff --git a/test/Data/OpenApiSpec.hs b/test/Data/OpenApiSpec.hs index fe724d52..1d409d5d 100644 --- a/test/Data/OpenApiSpec.hs +++ b/test/Data/OpenApiSpec.hs @@ -13,6 +13,7 @@ import Data.Aeson import Data.Aeson.QQ.Simple import Data.HashMap.Strict (HashMap) import qualified Data.HashSet.InsOrd as InsOrdHS +import qualified Data.HashMap.Strict.InsOrd as InsOrdHM import Data.Text (Text) import Data.OpenApi @@ -144,6 +145,7 @@ operationExample = mempty & at 200 ?~ "Pet updated." & at 405 ?~ "Invalid input" & security .~ [SecurityRequirement [("petstore_auth", ["write:pets", "read:pets"])]] + & extensions .~ SpecificationExtensions (InsOrdHM.fromList [("ext1", toJSON True)]) operationExampleJSON :: Value operationExampleJSON = [aesonQQ| @@ -198,7 +200,8 @@ operationExampleJSON = [aesonQQ| "read:pets" ] } - ] + ], + "x-ext1": true } |] @@ -230,6 +233,7 @@ schemaSimpleModelExample = mempty & minimum_ ?~ 0 & type_ ?~ OpenApiInteger & format ?~ "int32" ) ] + & extensions .~ SpecificationExtensions (InsOrdHM.fromList [("ext1", toJSON True)]) schemaSimpleModelExampleJSON :: Value schemaSimpleModelExampleJSON = [aesonQQ| @@ -247,7 +251,8 @@ schemaSimpleModelExampleJSON = [aesonQQ| "type": "integer" } }, - "type": "object" + "type": "object", + "x-ext1": true } |] @@ -448,15 +453,18 @@ securityDefinitionsExample :: SecurityDefinitions securityDefinitionsExample = SecurityDefinitions [ ("api_key", SecurityScheme { _securitySchemeType = SecuritySchemeApiKey (ApiKeyParams "api_key" ApiKeyHeader) - , _securitySchemeDescription = Nothing }) + , _securitySchemeDescription = Nothing + , _securitySchemeExtensions = mempty}) , ("petstore_auth", SecurityScheme { _securitySchemeType = SecuritySchemeOAuth2 (mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog" , _oAath2RefreshUrl = Nothing + , _oAuth2Extensions = mempty , _oAuth2Scopes = [ ("write:pets", "modify pets in your account") , ("read:pets", "read your pets") ] } ) - , _securitySchemeDescription = Nothing }) ] + , _securitySchemeDescription = Nothing + , _securitySchemeExtensions = SpecificationExtensions (InsOrdHM.fromList [("ext1", toJSON True)])}) ] securityDefinitionsExampleJSON :: Value securityDefinitionsExampleJSON = [aesonQQ| @@ -476,7 +484,8 @@ securityDefinitionsExampleJSON = [aesonQQ| }, "authorizationUrl": "http://swagger.io/api/oauth/dialog" } - } + }, + "x-ext1": true } } @@ -488,9 +497,11 @@ oAuth2SecurityDefinitionsReadExample = SecurityDefinitions { _securitySchemeType = SecuritySchemeOAuth2 (mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog" , _oAath2RefreshUrl = Nothing + , _oAuth2Extensions = mempty , _oAuth2Scopes = [ ("read:pets", "read your pets") ] } ) - , _securitySchemeDescription = Nothing }) + , _securitySchemeDescription = Nothing + , _securitySchemeExtensions = mempty }) ] oAuth2SecurityDefinitionsWriteExample :: SecurityDefinitions @@ -499,9 +510,12 @@ oAuth2SecurityDefinitionsWriteExample = SecurityDefinitions { _securitySchemeType = SecuritySchemeOAuth2 (mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog" , _oAath2RefreshUrl = Nothing + , _oAuth2Extensions = mempty , _oAuth2Scopes = [ ("write:pets", "modify pets in your account") ] } ) - , _securitySchemeDescription = Nothing }) + , _securitySchemeDescription = Nothing + , _securitySchemeExtensions = mempty + }) ] oAuth2SecurityDefinitionsExample :: SecurityDefinitions From a06c4859ba2d0eb3de69713daffdc5bdb351b716 Mon Sep 17 00:00:00 2001 From: Magesh Date: Wed, 21 Apr 2021 00:17:32 +0530 Subject: [PATCH 5/9] Made SubObjects as List and Added extensions for following ExternalDocumentation Responses Schema XML Security Scheme OAUTH Flows OAUTH Flow --- src/Data/OpenApi/Internal.hs | 122 ++++++++++++++++-------- src/Data/OpenApi/Internal/AesonUtils.hs | 16 +++- 2 files changed, 96 insertions(+), 42 deletions(-) diff --git a/src/Data/OpenApi/Internal.hs b/src/Data/OpenApi/Internal.hs index 0125c4d3..242d44e6 100644 --- a/src/Data/OpenApi/Internal.hs +++ b/src/Data/OpenApi/Internal.hs @@ -702,6 +702,9 @@ data Schema = Schema , _schemaUniqueItems :: Maybe Bool , _schemaEnum :: Maybe [Value] , _schemaMultipleOf :: Maybe Scientific + + -- | Specification Extensions + , _schemaExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | Regex pattern for @string@ type. @@ -748,6 +751,9 @@ data Xml = Xml -- Default value is @False@. -- The definition takes effect only when defined alongside type being array (outside the items). , _xmlWrapped :: Maybe Bool + + -- | Specification Extensions + , _xmlExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) -- | A container for the expected responses of an operation. @@ -763,6 +769,9 @@ data Responses = Responses -- | Any HTTP status code can be used as the property name (one property per HTTP status code). -- Describes the expected response for those HTTP status codes. , _responsesResponses :: InsOrdHashMap HttpStatusCode (Referenced Response) + + -- | Specification Extensions + , _responsesExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) type HttpStatusCode = Int @@ -869,6 +878,9 @@ data OAuth2Flow p = OAuth2Flow -- A map between the scope name and a short description for it. -- The map MAY be empty. , _oAuth2Scopes :: InsOrdHashMap Text Text + + -- | Specification Extensions + , _oAuth2Extensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) data OAuth2Flows = OAuth2Flows @@ -883,6 +895,9 @@ data OAuth2Flows = OAuth2Flows -- | Configuration for the OAuth Authorization Code flow , _oAuth2FlowsAuthorizationCode :: Maybe (OAuth2Flow OAuth2AuthorizationCodeFlow) + + -- | Specification Extensions + , _oAuth2FlowsExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) type BearerFormat = Text @@ -940,6 +955,9 @@ data SecurityScheme = SecurityScheme -- | A short description for security scheme. , _securitySchemeDescription :: Maybe Text + + -- | Specification Extensions + , _securitySchemeExtensions :: SpecificationExtensions } deriving (Eq, Show, Generic, Data, Typeable) newtype SecurityDefinitions @@ -986,7 +1004,10 @@ data ExternalDocs = ExternalDocs -- | The URL for the target documentation. , _externalDocsUrl :: URL - } deriving (Eq, Ord, Show, Generic, Data, Typeable) + + -- | Specification Extensions + , _externalDocsExtensions :: SpecificationExtensions + } deriving (Eq, Show, Generic, Data, Typeable) instance Hashable ExternalDocs @@ -1003,7 +1024,7 @@ data Referenced a instance IsString a => IsString (Referenced a) where fromString = Inline . fromString -newtype URL = URL { getUrl :: Text } deriving (Eq, Ord, Show, Hashable, ToJSON, FromJSON, Data, Typeable) +newtype URL = URL { getUrl :: Text } deriving (Eq, Ord, Show, Hashable, ToJSON, FromJSON, Data, Typeable, AesonDefaultValue) data AdditionalProperties = AdditionalPropertiesAllowed Bool @@ -1041,6 +1062,8 @@ deriveGeneric ''Contact deriveGeneric ''License deriveGeneric ''ServerVariable deriveGeneric ''Tag +deriveGeneric ''Xml +deriveGeneric ''ExternalDocs -- ======================================================================= -- Monoid instances @@ -1142,6 +1165,7 @@ instance Semigroup OAuth2Flows where , _oAuth2FlowsPassword = _oAuth2FlowsPassword l <> _oAuth2FlowsPassword r , _oAuth2FlowsClientCredentials = _oAuth2FlowsClientCredentials l <> _oAuth2FlowsClientCredentials r , _oAuth2FlowsAuthorizationCode = _oAuth2FlowsAuthorizationCode l <> _oAuth2FlowsAuthorizationCode r + , _oAuth2FlowsExtensions = _oAuth2FlowsExtensions l <> _oAuth2FlowsExtensions r } instance Monoid OAuth2Flows where @@ -1149,9 +1173,9 @@ instance Monoid OAuth2Flows where mappend = (<>) instance Semigroup SecurityScheme where - SecurityScheme (SecuritySchemeOAuth2 lFlows) lDesc - <> SecurityScheme (SecuritySchemeOAuth2 rFlows) rDesc = - SecurityScheme (SecuritySchemeOAuth2 $ lFlows <> rFlows) (swaggerMappend lDesc rDesc) + SecurityScheme (SecuritySchemeOAuth2 lFlows) lDesc lExt + <> SecurityScheme (SecuritySchemeOAuth2 rFlows) rDesc rExt = + SecurityScheme (SecuritySchemeOAuth2 $ lFlows <> rFlows) (swaggerMappend lDesc rDesc) (lExt <> rExt) l <> _ = l instance Semigroup SecurityDefinitions where @@ -1223,12 +1247,6 @@ instance ToJSON ApiKeyLocation where instance ToJSON ApiKeyParams where toJSON = genericToJSON (jsonPrefix "apiKey") -instance ToJSON ExternalDocs where - toJSON = genericToJSON (jsonPrefix "ExternalDocs") - -instance ToJSON Xml where - toJSON = genericToJSON (jsonPrefix "Xml") - instance ToJSON Discriminator where toJSON = genericToJSON (jsonPrefix "Discriminator") @@ -1263,9 +1281,6 @@ instance FromJSON ApiKeyLocation where instance FromJSON ApiKeyParams where parseJSON = genericParseJSON (jsonPrefix "apiKey") -instance FromJSON ExternalDocs where - parseJSON = genericParseJSON (jsonPrefix "ExternalDocs") - instance FromJSON Discriminator where parseJSON = genericParseJSON (jsonPrefix "Discriminator") @@ -1359,9 +1374,9 @@ instance ToJSON SecurityScheme where instance ToJSON Schema where toJSON = sopSwaggerGenericToJSONWithOpts $ - mkSwaggerAesonOptions "schema" & saoSubObject ?~ "items" + mkSwaggerAesonOptions "schema" & saoSubObject ?~ ["items", "extensions"] toEncoding = sopSwaggerGenericToEncodingWithOpts $ - mkSwaggerAesonOptions "schema" & saoSubObject ?~ "items" + mkSwaggerAesonOptions "schema" & saoSubObject ?~ ["items", "extensions"] instance ToJSON Header where toJSON = sopSwaggerGenericToJSON @@ -1437,6 +1452,14 @@ instance ToJSON Tag where toJSON = sopSwaggerGenericToJSON toEncoding = sopSwaggerGenericToEncoding +instance ToJSON Xml where + toJSON = sopSwaggerGenericToJSON + toEncoding = sopSwaggerGenericToEncoding + +instance ToJSON ExternalDocs where + toJSON = sopSwaggerGenericToJSON + toEncoding = sopSwaggerGenericToEncoding + instance ToJSON SecurityDefinitions where toJSON (SecurityDefinitions sd) = toJSON sd @@ -1570,9 +1593,21 @@ instance FromJSON Param where instance FromJSON Responses where parseJSON (Object o) = Responses <$> o .:? "default" +<<<<<<< HEAD <*> parseJSON (Object (deleteKey "default" o)) +======= + <*> parseJSON (Object (HashMap.filterWithKey (\k _ -> not $ isExt k) + $ HashMap.delete "default" o)) + <*> case HashMap.filterWithKey (\k _ -> isExt k) o of + exts | HashMap.null exts -> pure (SpecificationExtensions mempty) + | otherwise -> parseJSON (Object exts) +>>>>>>> Made SubObjects as List and Added extensions for following parseJSON _ = empty +isExt :: Text -> Bool +isExt = Text.isPrefixOf "x-" + + instance FromJSON Example where parseJSON = sopSwaggerGenericParseJSON @@ -1603,6 +1638,12 @@ instance FromJSON Link where instance FromJSON Tag where parseJSON = sopSwaggerGenericParseJSON +instance FromJSON Xml where + parseJSON = sopSwaggerGenericParseJSON + +instance FromJSON ExternalDocs where + parseJSON = sopSwaggerGenericParseJSON + instance FromJSON Reference where parseJSON (Object o) = Reference <$> o .: "$ref" parseJSON _ = empty @@ -1629,9 +1670,6 @@ instance FromJSON (Referenced Header) where parseJSON = referencedParseJSON "# instance FromJSON (Referenced Link) where parseJSON = referencedParseJSON "#/components/links/" instance FromJSON (Referenced Callback) where parseJSON = referencedParseJSON "#/components/callbacks/" -instance FromJSON Xml where - parseJSON = genericParseJSON (jsonPrefix "xml") - instance FromJSON AdditionalProperties where parseJSON (Bool b) = pure $ AdditionalPropertiesAllowed b parseJSON js = AdditionalPropertiesSchema <$> parseJSON js @@ -1651,58 +1689,64 @@ instance FromJSON SpecificationExtensions where filterExtFields = fmap (\(k,v) -> fmap (\k' -> (k',v)) $ Text.stripPrefix "x-" k) . HashMap.toList instance HasSwaggerAesonOptions Server where - swaggerAesonOptions _ = mkSwaggerAesonOptions "server" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "server" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Components where swaggerAesonOptions _ = mkSwaggerAesonOptions "components" instance HasSwaggerAesonOptions Header where swaggerAesonOptions _ = mkSwaggerAesonOptions "header" instance AesonDefaultValue p => HasSwaggerAesonOptions (OAuth2Flow p) where - swaggerAesonOptions _ = mkSwaggerAesonOptions "oauth2" & saoSubObject ?~ "params" + swaggerAesonOptions _ = mkSwaggerAesonOptions "oauth2" & saoSubObject .~ ["params", "extensions"] instance HasSwaggerAesonOptions OAuth2Flows where - swaggerAesonOptions _ = mkSwaggerAesonOptions "oauth2Flows" + swaggerAesonOptions _ = mkSwaggerAesonOptions "oauth2Flows" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Operation where - swaggerAesonOptions _ = mkSwaggerAesonOptions "operation" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "operation" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Param where swaggerAesonOptions _ = mkSwaggerAesonOptions "param" instance HasSwaggerAesonOptions PathItem where - swaggerAesonOptions _ = mkSwaggerAesonOptions "pathItem" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "pathItem" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Response where - swaggerAesonOptions _ = mkSwaggerAesonOptions "response" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "response" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions RequestBody where - swaggerAesonOptions _ = mkSwaggerAesonOptions "requestBody" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "requestBody" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions MediaTypeObject where - swaggerAesonOptions _ = mkSwaggerAesonOptions "mediaTypeObject" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "mediaTypeObject" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Responses where - swaggerAesonOptions _ = mkSwaggerAesonOptions "responses" & saoSubObject ?~ "responses" + swaggerAesonOptions _ = mkSwaggerAesonOptions "responses" & saoSubObject .~ ["responses", "extensions"] instance HasSwaggerAesonOptions SecurityScheme where - swaggerAesonOptions _ = mkSwaggerAesonOptions "securityScheme" & saoSubObject ?~ "type" + swaggerAesonOptions _ = mkSwaggerAesonOptions "securityScheme" & saoSubObject .~ ["type", "extensions"] instance HasSwaggerAesonOptions Schema where - swaggerAesonOptions _ = mkSwaggerAesonOptions "schema" & saoSubObject ?~ "paramSchema" + swaggerAesonOptions _ = mkSwaggerAesonOptions "schema" & saoSubObject .~ ["paramSchema", "extensions"] instance HasSwaggerAesonOptions OpenApi where swaggerAesonOptions _ = mkSwaggerAesonOptions "swagger" & saoAdditionalPairs .~ [("openapi", "3.0.0")] - & saoSubObject ?~ "extensions" + & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Example where - swaggerAesonOptions _ = mkSwaggerAesonOptions "example" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "example" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Encoding where - swaggerAesonOptions _ = mkSwaggerAesonOptions "encoding" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "encoding" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Link where - swaggerAesonOptions _ = mkSwaggerAesonOptions "link" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "link" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Info where - swaggerAesonOptions _ = mkSwaggerAesonOptions "info" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "info" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Contact where - swaggerAesonOptions _ = mkSwaggerAesonOptions "contact" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "contact" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions License where - swaggerAesonOptions _ = mkSwaggerAesonOptions "license" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "license" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions ServerVariable where - swaggerAesonOptions _ = mkSwaggerAesonOptions "serverVariable" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "serverVariable" & saoSubObject .~ ["extensions"] instance HasSwaggerAesonOptions Tag where - swaggerAesonOptions _ = mkSwaggerAesonOptions "tag" & saoSubObject ?~ "extensions" + swaggerAesonOptions _ = mkSwaggerAesonOptions "tag" & saoSubObject .~ ["extensions"] + +instance HasSwaggerAesonOptions Xml where + swaggerAesonOptions _ = mkSwaggerAesonOptions "xml" & saoSubObject .~ ["extensions"] + +instance HasSwaggerAesonOptions ExternalDocs where + swaggerAesonOptions _ = mkSwaggerAesonOptions "externalDocs" & saoSubObject .~ ["extensions"] instance AesonDefaultValue Server instance AesonDefaultValue Components diff --git a/src/Data/OpenApi/Internal/AesonUtils.hs b/src/Data/OpenApi/Internal/AesonUtils.hs index 1722dadd..2a5297a5 100644 --- a/src/Data/OpenApi/Internal/AesonUtils.hs +++ b/src/Data/OpenApi/Internal/AesonUtils.hs @@ -50,13 +50,13 @@ import Data.OpenApi.Aeson.Compat (keyToString, objectToList, stringToKey) data SwaggerAesonOptions = SwaggerAesonOptions { _saoPrefix :: String , _saoAdditionalPairs :: [Pair] - , _saoSubObject :: Maybe String + , _saoSubObject :: [String] } mkSwaggerAesonOptions :: String -- ^ prefix -> SwaggerAesonOptions -mkSwaggerAesonOptions pfx = SwaggerAesonOptions pfx [] Nothing +mkSwaggerAesonOptions pfx = SwaggerAesonOptions pfx [] [] makeLenses ''SwaggerAesonOptions @@ -155,8 +155,13 @@ sopSwaggerGenericToJSON'' (SwaggerAesonOptions prefix _ sub) = go go :: (All ToJSON ys, All Eq ys) => NP I ys -> NP FieldInfo ys -> NP Maybe ys -> [Pair] go Nil Nil Nil = [] go (I x :* xs) (FieldInfo name :* names) (def :* defs) +<<<<<<< HEAD | Just name' == sub = case json of Object m -> objectToList m ++ rest +======= + | name' `elem` sub = case json of + Object m -> HM.toList m ++ rest +>>>>>>> Made SubObjects as List and Added extensions for following Null -> rest _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show json -- If default value: omit it. @@ -228,7 +233,7 @@ sopSwaggerGenericParseJSON'' (SwaggerAesonOptions prefix _ sub) obj = go go :: (All FromJSON ys, All Eq ys) => NP FieldInfo ys -> NP Maybe ys -> Parser (NP I ys) go Nil Nil = pure Nil go (FieldInfo name :* names) (def :* defs) - | Just name' == sub = + | name' `elem` sub = -- Note: we might strip fields of outer structure. cons <$> (withDef $ parseJSON $ Object obj) <*> rest | otherwise = case def of @@ -313,8 +318,13 @@ sopSwaggerGenericToEncoding'' (SwaggerAesonOptions prefix _ sub) = go go :: (All ToJSON ys, All Eq ys) => NP I ys -> NP FieldInfo ys -> NP Maybe ys -> Series go Nil Nil Nil = mempty go (I x :* xs) (FieldInfo name :* names) (def :* defs) +<<<<<<< HEAD | Just name' == sub = case toJSON x of Object m -> pairsToSeries (objectToList m) <> rest +======= + | name' `elem` sub = case toJSON x of + Object m -> pairsToSeries (HM.toList m) <> rest +>>>>>>> Made SubObjects as List and Added extensions for following Null -> rest _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show (toJSON x) -- If default value: omit it. From 6a62b6c74d035a28ff3baa35d7a926ac268bc7fc Mon Sep 17 00:00:00 2001 From: Magesh Date: Wed, 21 Apr 2021 02:54:22 +0530 Subject: [PATCH 6/9] Fixed the tests --- src/Data/OpenApi.hs | 2 -- src/Data/OpenApi/Internal/Schema.hs | 11 +++++++++++ src/Data/OpenApi/Operation.hs | 11 +++++++++++ test/Data/OpenApiSpec.hs | 28 +++++++++++++++++++++------- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Data/OpenApi.hs b/src/Data/OpenApi.hs index 330ed2e3..f41997ad 100644 --- a/src/Data/OpenApi.hs +++ b/src/Data/OpenApi.hs @@ -185,7 +185,6 @@ import Data.OpenApi.Internal -- "title": "Todo API", -- "version": "1.0" -- } - -- -- This can be useful for combining specifications of endpoints into a whole API specification: -- @@ -413,7 +412,6 @@ import Data.OpenApi.Internal -- ], -- "type": "object" -- } - -- $manipulation -- Sometimes you have to work with an imported or generated @'Swagger'@. -- For instance, generates basic @'Swagger'@ diff --git a/src/Data/OpenApi/Internal/Schema.hs b/src/Data/OpenApi/Internal/Schema.hs index f8649640..131a5f7c 100644 --- a/src/Data/OpenApi/Internal/Schema.hs +++ b/src/Data/OpenApi/Internal/Schema.hs @@ -171,6 +171,7 @@ declareSchema = fmap _namedSchemaSchema . declareNamedSchema -- "format": "date", -- "type": "string" -- } +>>>>>>> Fixed the tests toNamedSchema :: ToSchema a => Proxy a -> NamedSchema toNamedSchema = undeclare . declareNamedSchema @@ -381,6 +382,16 @@ inlineNonRecursiveSchemas defs = inlineSchemasWhen nonRecursive defs -- ], -- "type": "object" -- } +-- >>> BSL.putStrLn $ encode $ sketchSchema (1, 2, 3) +-- {"example":[1,2,3],"type":"array","items":{"type":"number"}} +-- +-- >>> BSL.putStrLn $ encode $ sketchSchema ("Jack", 25) +-- {"example":["Jack",25],"type":"array","items":[{"type":"string"},{"type":"number"}]} +-- +-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic) +-- >>> instance ToJSON Person +-- >>> BSL.putStrLn $ encode $ sketchSchema (Person "Jack" 25) +-- {"required":["age","name"],"properties":{"age":{"type":"number"},"name":{"type":"string"}},"example":{"age":25,"name":"Jack"},"type":"object"} sketchSchema :: ToJSON a => a -> Schema sketchSchema = sketch . toJSON where diff --git a/src/Data/OpenApi/Operation.hs b/src/Data/OpenApi/Operation.hs index 9a2484b1..d3f3986b 100644 --- a/src/Data/OpenApi/Operation.hs +++ b/src/Data/OpenApi/Operation.hs @@ -142,6 +142,7 @@ allOperations = paths.traverse.template -- } -- } -- } + operationsOf :: OpenApi -> Traversal' OpenApi Operation operationsOf sub = paths.itraversed.withIndex.subops where @@ -179,6 +180,7 @@ applyTagsFor ops ts swag = swag -- -- FIXME doc -- +<<<<<<< HEAD -- >>> BSL.putStrLn $ encodePretty $ runDeclare (declareResponse "application/json" (Proxy :: Proxy Day)) mempty -- [ -- { @@ -199,6 +201,10 @@ applyTagsFor ops ts swag = swag -- "description": "" -- } -- ] +======= +-- >>> BSL.putStrLn $ encode $ runDeclare (declareResponse "application/json" (Proxy :: Proxy Day)) mempty +-- [{"Day":{"example":"2016-07-22","type":"string","format":"date"}},{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Day"}}}}] +>>>>>>> Fixed the tests declareResponse :: ToSchema a => MediaType -> Proxy a -> Declare (Definitions Schema) Response declareResponse cType proxy = do s <- declareSchemaRef proxy @@ -217,6 +223,7 @@ declareResponse cType proxy = do -- -- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)] -- >>> let res = declareResponse "application/json" (Proxy :: Proxy Day) +<<<<<<< HEAD -- >>> BSL.putStrLn $ encodePretty $ api & setResponse 200 res -- { -- "components": { @@ -252,6 +259,10 @@ declareResponse cType proxy = do -- } -- } -- } +======= +-- >>> BSL.putStrLn $ encode $ api & setResponse 200 res +-- {"openapi":"3.0.0","info":{"title":"","version":""},"paths":{"/user":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Day"}}},"description":""}}}}},"components":{"schemas":{"Day":{"example":"2016-07-22","type":"string","format":"date"}}}} +>>>>>>> Fixed the tests -- -- See also @'setResponseWith'@. setResponse :: HttpStatusCode -> Declare (Definitions Schema) Response -> OpenApi -> OpenApi diff --git a/test/Data/OpenApiSpec.hs b/test/Data/OpenApiSpec.hs index eb31d267..edb353fc 100644 --- a/test/Data/OpenApiSpec.hs +++ b/test/Data/OpenApiSpec.hs @@ -13,6 +13,7 @@ import Data.Aeson import Data.Aeson.QQ.Simple import Data.HashMap.Strict (HashMap) import qualified Data.HashSet.InsOrd as InsOrdHS +import qualified Data.HashMap.Strict.InsOrd as InsOrdHM import Data.Text (Text) import Data.OpenApi @@ -148,6 +149,7 @@ operationExample = mempty & at 200 ?~ "Pet updated." & at 405 ?~ "Invalid input" & security .~ [SecurityRequirement [("petstore_auth", ["write:pets", "read:pets"])]] + & extensions .~ SpecificationExtensions (InsOrdHM.fromList [("ext1", toJSON True)]) operationExampleJSON :: Value operationExampleJSON = [aesonQQ| @@ -202,7 +204,8 @@ operationExampleJSON = [aesonQQ| "read:pets" ] } - ] + ], + "x-ext1": true } |] @@ -234,6 +237,7 @@ schemaSimpleModelExample = mempty & minimum_ ?~ 0 & type_ ?~ OpenApiInteger & format ?~ "int32" ) ] + & extensions .~ SpecificationExtensions (InsOrdHM.fromList [("ext1", toJSON True)]) schemaSimpleModelExampleJSON :: Value schemaSimpleModelExampleJSON = [aesonQQ| @@ -251,7 +255,8 @@ schemaSimpleModelExampleJSON = [aesonQQ| "type": "integer" } }, - "type": "object" + "type": "object", + "x-ext1": true } |] @@ -452,15 +457,18 @@ securityDefinitionsExample :: SecurityDefinitions securityDefinitionsExample = SecurityDefinitions [ ("api_key", SecurityScheme { _securitySchemeType = SecuritySchemeApiKey (ApiKeyParams "api_key" ApiKeyHeader) - , _securitySchemeDescription = Nothing }) + , _securitySchemeDescription = Nothing + , _securitySchemeExtensions = mempty}) , ("petstore_auth", SecurityScheme { _securitySchemeType = SecuritySchemeOAuth2 (mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog" , _oAath2RefreshUrl = Nothing + , _oAuth2Extensions = mempty , _oAuth2Scopes = [ ("write:pets", "modify pets in your account") , ("read:pets", "read your pets") ] } ) - , _securitySchemeDescription = Nothing }) ] + , _securitySchemeDescription = Nothing + , _securitySchemeExtensions = SpecificationExtensions (InsOrdHM.fromList [("ext1", toJSON True)])}) ] securityDefinitionsExampleJSON :: Value securityDefinitionsExampleJSON = [aesonQQ| @@ -480,7 +488,8 @@ securityDefinitionsExampleJSON = [aesonQQ| }, "authorizationUrl": "http://swagger.io/api/oauth/dialog" } - } + }, + "x-ext1": true } } @@ -492,9 +501,11 @@ oAuth2SecurityDefinitionsReadExample = SecurityDefinitions { _securitySchemeType = SecuritySchemeOAuth2 (mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog" , _oAath2RefreshUrl = Nothing + , _oAuth2Extensions = mempty , _oAuth2Scopes = [ ("read:pets", "read your pets") ] } ) - , _securitySchemeDescription = Nothing }) + , _securitySchemeDescription = Nothing + , _securitySchemeExtensions = mempty }) ] oAuth2SecurityDefinitionsWriteExample :: SecurityDefinitions @@ -503,9 +514,12 @@ oAuth2SecurityDefinitionsWriteExample = SecurityDefinitions { _securitySchemeType = SecuritySchemeOAuth2 (mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog" , _oAath2RefreshUrl = Nothing + , _oAuth2Extensions = mempty , _oAuth2Scopes = [ ("write:pets", "modify pets in your account") ] } ) - , _securitySchemeDescription = Nothing }) + , _securitySchemeDescription = Nothing + , _securitySchemeExtensions = mempty + }) ] oAuth2SecurityDefinitionsExample :: SecurityDefinitions From 0088a5d7ff220ea2e7d7d346c74e65fea0616020 Mon Sep 17 00:00:00 2001 From: Sreenidhi Date: Thu, 11 Aug 2022 10:29:44 +0530 Subject: [PATCH 7/9] Fix aeson compat issues and some more conflicts Compiles with 9.2 now --- src/Data/OpenApi/Aeson/Compat.hs | 7 +++++++ src/Data/OpenApi/Internal.hs | 25 ++++++++++--------------- src/Data/OpenApi/Internal/AesonUtils.hs | 14 ++------------ src/Data/OpenApi/Internal/Schema.hs | 1 - src/Data/OpenApi/Operation.hs | 10 ---------- 5 files changed, 19 insertions(+), 38 deletions(-) diff --git a/src/Data/OpenApi/Aeson/Compat.hs b/src/Data/OpenApi/Aeson/Compat.hs index c516a4e1..f74e70a8 100644 --- a/src/Data/OpenApi/Aeson/Compat.hs +++ b/src/Data/OpenApi/Aeson/Compat.hs @@ -43,6 +43,10 @@ lookupKey = KeyMap.lookup . Key.fromText hasKey :: T.Text -> KeyMap.KeyMap a -> Bool hasKey = KeyMap.member . Key.fromText + +filterWithKey :: (Key -> v -> Bool) -> KeyMap.KeyMap v -> KeyMap.KeyMap v +filterWithKey = KeyMap.filterWithKey + #else deleteKey :: T.Text -> HM.HashMap T.Text v -> HM.HashMap T.Text v deleteKey = HM.delete @@ -73,4 +77,7 @@ lookupKey = HM.lookup hasKey :: T.Text -> HM.HashMap T.Text a -> Bool hasKey = HM.member + +filterWithKey :: (T.Text -> v -> Bool) -> HashMap T.Text v -> HashMap T.Text v +filterWithKey = HM.filterWithKey #endif diff --git a/src/Data/OpenApi/Internal.hs b/src/Data/OpenApi/Internal.hs index 242d44e6..112c29cd 100644 --- a/src/Data/OpenApi/Internal.hs +++ b/src/Data/OpenApi/Internal.hs @@ -24,6 +24,7 @@ import Control.Lens ((&), (.~), (?~)) import Data.Aeson hiding (Encoding) #if MIN_VERSION_aeson(2,0,0) import qualified Data.Aeson.KeyMap as KeyMap +import qualified Data.Aeson.Key as KeyMap #endif import qualified Data.Aeson.Types as JSON import Data.Data (Constr, Data (..), DataType, Fixity (..), Typeable, @@ -50,7 +51,7 @@ import Text.Read (readMaybe) import Data.HashMap.Strict.InsOrd (InsOrdHashMap) import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap -import Data.OpenApi.Aeson.Compat (deleteKey) +import Data.OpenApi.Aeson.Compat (deleteKey, keyToText, filterWithKey, objectToList) import Data.OpenApi.Internal.AesonUtils (AesonDefaultValue (..), HasSwaggerAesonOptions (..), mkSwaggerAesonOptions, saoAdditionalPairs, saoSubObject, sopSwaggerGenericParseJSON, sopSwaggerGenericToEncoding, @@ -1374,9 +1375,9 @@ instance ToJSON SecurityScheme where instance ToJSON Schema where toJSON = sopSwaggerGenericToJSONWithOpts $ - mkSwaggerAesonOptions "schema" & saoSubObject ?~ ["items", "extensions"] + mkSwaggerAesonOptions "schema" & saoSubObject .~ ["items", "extensions"] toEncoding = sopSwaggerGenericToEncodingWithOpts $ - mkSwaggerAesonOptions "schema" & saoSubObject ?~ ["items", "extensions"] + mkSwaggerAesonOptions "schema" & saoSubObject .~ ["items", "extensions"] instance ToJSON Header where toJSON = sopSwaggerGenericToJSON @@ -1593,20 +1594,14 @@ instance FromJSON Param where instance FromJSON Responses where parseJSON (Object o) = Responses <$> o .:? "default" -<<<<<<< HEAD - <*> parseJSON (Object (deleteKey "default" o)) -======= - <*> parseJSON (Object (HashMap.filterWithKey (\k _ -> not $ isExt k) - $ HashMap.delete "default" o)) - <*> case HashMap.filterWithKey (\k _ -> isExt k) o of - exts | HashMap.null exts -> pure (SpecificationExtensions mempty) + <*> parseJSON (Object (filterWithKey (\k _ -> not $ isExt k) + $ deleteKey "default" o)) + <*> case filterWithKey (\k _ -> isExt k) o of + exts | null exts -> pure (SpecificationExtensions mempty) | otherwise -> parseJSON (Object exts) ->>>>>>> Made SubObjects as List and Added extensions for following parseJSON _ = empty -isExt :: Text -> Bool -isExt = Text.isPrefixOf "x-" - +isExt = Text.isPrefixOf "x-" . keyToText instance FromJSON Example where parseJSON = sopSwaggerGenericParseJSON @@ -1686,7 +1681,7 @@ instance FromJSON SpecificationExtensions where parseJSON = withObject "SpecificationExtensions" extFieldsParser where extFieldsParser = pure . SpecificationExtensions . InsOrdHashMap.fromList . catMaybes . filterExtFields - filterExtFields = fmap (\(k,v) -> fmap (\k' -> (k',v)) $ Text.stripPrefix "x-" k) . HashMap.toList + filterExtFields = fmap (\(k,v) -> fmap (\k' -> (k',v)) $ Text.stripPrefix "x-" $ keyToText k) . objectToList instance HasSwaggerAesonOptions Server where swaggerAesonOptions _ = mkSwaggerAesonOptions "server" & saoSubObject .~ ["extensions"] diff --git a/src/Data/OpenApi/Internal/AesonUtils.hs b/src/Data/OpenApi/Internal/AesonUtils.hs index 2a5297a5..a699f3e2 100644 --- a/src/Data/OpenApi/Internal/AesonUtils.hs +++ b/src/Data/OpenApi/Internal/AesonUtils.hs @@ -155,13 +155,8 @@ sopSwaggerGenericToJSON'' (SwaggerAesonOptions prefix _ sub) = go go :: (All ToJSON ys, All Eq ys) => NP I ys -> NP FieldInfo ys -> NP Maybe ys -> [Pair] go Nil Nil Nil = [] go (I x :* xs) (FieldInfo name :* names) (def :* defs) -<<<<<<< HEAD - | Just name' == sub = case json of - Object m -> objectToList m ++ rest -======= | name' `elem` sub = case json of - Object m -> HM.toList m ++ rest ->>>>>>> Made SubObjects as List and Added extensions for following + Object m -> objectToList m ++ rest Null -> rest _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show json -- If default value: omit it. @@ -318,13 +313,8 @@ sopSwaggerGenericToEncoding'' (SwaggerAesonOptions prefix _ sub) = go go :: (All ToJSON ys, All Eq ys) => NP I ys -> NP FieldInfo ys -> NP Maybe ys -> Series go Nil Nil Nil = mempty go (I x :* xs) (FieldInfo name :* names) (def :* defs) -<<<<<<< HEAD - | Just name' == sub = case toJSON x of - Object m -> pairsToSeries (objectToList m) <> rest -======= | name' `elem` sub = case toJSON x of - Object m -> pairsToSeries (HM.toList m) <> rest ->>>>>>> Made SubObjects as List and Added extensions for following + Object m -> pairsToSeries (objectToList m) <> rest Null -> rest _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show (toJSON x) -- If default value: omit it. diff --git a/src/Data/OpenApi/Internal/Schema.hs b/src/Data/OpenApi/Internal/Schema.hs index 131a5f7c..c8dc612b 100644 --- a/src/Data/OpenApi/Internal/Schema.hs +++ b/src/Data/OpenApi/Internal/Schema.hs @@ -171,7 +171,6 @@ declareSchema = fmap _namedSchemaSchema . declareNamedSchema -- "format": "date", -- "type": "string" -- } ->>>>>>> Fixed the tests toNamedSchema :: ToSchema a => Proxy a -> NamedSchema toNamedSchema = undeclare . declareNamedSchema diff --git a/src/Data/OpenApi/Operation.hs b/src/Data/OpenApi/Operation.hs index d3f3986b..03e3afd3 100644 --- a/src/Data/OpenApi/Operation.hs +++ b/src/Data/OpenApi/Operation.hs @@ -180,7 +180,6 @@ applyTagsFor ops ts swag = swag -- -- FIXME doc -- -<<<<<<< HEAD -- >>> BSL.putStrLn $ encodePretty $ runDeclare (declareResponse "application/json" (Proxy :: Proxy Day)) mempty -- [ -- { @@ -201,10 +200,6 @@ applyTagsFor ops ts swag = swag -- "description": "" -- } -- ] -======= --- >>> BSL.putStrLn $ encode $ runDeclare (declareResponse "application/json" (Proxy :: Proxy Day)) mempty --- [{"Day":{"example":"2016-07-22","type":"string","format":"date"}},{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Day"}}}}] ->>>>>>> Fixed the tests declareResponse :: ToSchema a => MediaType -> Proxy a -> Declare (Definitions Schema) Response declareResponse cType proxy = do s <- declareSchemaRef proxy @@ -223,7 +218,6 @@ declareResponse cType proxy = do -- -- >>> let api = (mempty :: OpenApi) & paths .~ [("/user", mempty & get ?~ mempty)] -- >>> let res = declareResponse "application/json" (Proxy :: Proxy Day) -<<<<<<< HEAD -- >>> BSL.putStrLn $ encodePretty $ api & setResponse 200 res -- { -- "components": { @@ -259,10 +253,6 @@ declareResponse cType proxy = do -- } -- } -- } -======= --- >>> BSL.putStrLn $ encode $ api & setResponse 200 res --- {"openapi":"3.0.0","info":{"title":"","version":""},"paths":{"/user":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Day"}}},"description":""}}}}},"components":{"schemas":{"Day":{"example":"2016-07-22","type":"string","format":"date"}}}} ->>>>>>> Fixed the tests -- -- See also @'setResponseWith'@. setResponse :: HttpStatusCode -> Declare (Definitions Schema) Response -> OpenApi -> OpenApi From d40655f1c88d35f204793dee21428501b95f09e2 Mon Sep 17 00:00:00 2001 From: Sreenidhi Date: Thu, 11 Aug 2022 11:20:42 +0530 Subject: [PATCH 8/9] Add qualification for compat fun --- src/Data/OpenApi/Aeson/Compat.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/OpenApi/Aeson/Compat.hs b/src/Data/OpenApi/Aeson/Compat.hs index f74e70a8..7e967152 100644 --- a/src/Data/OpenApi/Aeson/Compat.hs +++ b/src/Data/OpenApi/Aeson/Compat.hs @@ -78,6 +78,6 @@ lookupKey = HM.lookup hasKey :: T.Text -> HM.HashMap T.Text a -> Bool hasKey = HM.member -filterWithKey :: (T.Text -> v -> Bool) -> HashMap T.Text v -> HashMap T.Text v +filterWithKey :: (T.Text -> v -> Bool) -> HM.HashMap T.Text v -> HM.HashMap T.Text v filterWithKey = HM.filterWithKey #endif From 0a69890dcea08c16001c14aaea0bb5b6c82a268e Mon Sep 17 00:00:00 2001 From: Sreenidhi Date: Wed, 2 Nov 2022 15:40:26 +0530 Subject: [PATCH 9/9] Add explicit toEncoding for OpenApiItems and SpecficationExtensions --- src/Data/OpenApi/Internal.hs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Data/OpenApi/Internal.hs b/src/Data/OpenApi/Internal.hs index 112c29cd..3a3936de 100644 --- a/src/Data/OpenApi/Internal.hs +++ b/src/Data/OpenApi/Internal.hs @@ -1402,6 +1402,15 @@ instance ToJSON OpenApiItems where ] toJSON (OpenApiItemsArray x) = object [ "items" .= x ] + toEncoding (OpenApiItemsObject x) = pairs ("items" .= x ) + toEncoding (OpenApiItemsArray []) = pairs + ( + "items" .= JSON.emptyObject + <> "maxItems" .= (0 :: Int) + <> "example" .= JSON.emptyArray + ) + toEncoding (OpenApiItemsArray x) = pairs ( "items" .= x ) + instance ToJSON Components where toJSON = sopSwaggerGenericToJSON toEncoding = sopSwaggerGenericToEncoding @@ -1506,6 +1515,9 @@ instance ToJSON SpecificationExtensions where toJSON = toJSON . addExtPrefix . getSpecificationExtensions where addExtPrefix = InsOrdHashMap.mapKeys ("x-" <>) + toEncoding = toEncoding . addExtPrefix . getSpecificationExtensions + where + addExtPrefix = InsOrdHashMap.mapKeys ("x-" <>) -- =======================================================================