{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} module Data.OpenApiSpec where import Control.Lens import Data.Aeson import Data.Aeson.QQ.Simple import Data.HashMap.Strict (HashMap) import Data.HashSet.InsOrd.Compat qualified as InsOrdHS import Data.OpenApi import Data.Text (Text) import Prelude.Compat import SpecCommon import Test.Hspec hiding (example) import Prelude () spec :: Spec spec = do describe "License Object" $ licenseExample <=> licenseExampleJSON describe "Contact Object" $ contactExample <=> contactExampleJSON describe "Info Object" $ infoExample <=> infoExampleJSON describe "Operation Object" $ operationExample <=> operationExampleJSON describe "Schema Object" $ do context "Primitive Sample" $ schemaPrimitiveExample <=> schemaPrimitiveExampleJSON context "Simple Model" $ schemaSimpleModelExample <=> schemaSimpleModelExampleJSON context "Model with Map/Dictionary Properties" $ schemaModelDictExample <=> schemaModelDictExampleJSON context "Model with Arbitrary Properties" $ schemaAdditionalExample <=> schemaAdditionalExampleJSON context "Model with Example" $ schemaWithExampleExample <=> schemaWithExampleExampleJSON describe "Definitions Object" $ definitionsExample <=> definitionsExampleJSON describe "Parameters Definition Object" $ paramsDefinitionExample <=> paramsDefinitionExampleJSON describe "Responses Definition Object" $ responsesDefinitionExample <=> responsesDefinitionExampleJSON describe "Status Code Range Responses" $ statusRangeResponsesExample <=> statusRangeResponsesExampleJSON describe "Responses with default, exact, range and $ref" $ responsesMixedExample <=> responsesMixedExampleJSON describe "Issue #1: range status code parses" $ it "decodes an operation whose responses include a 4XX range" $ do let js = [aesonQQ| { "operationId": "test_endpoint", "responses": { "200": { "description": "200 response" }, "429": { "description": "too many requests" }, "4XX": { "description": "client error" } } } |] case fromJSON js :: Result Operation of Success op -> (op ^. responses . at (StatusRange R4XX)) `shouldBe` Just (Inline (mempty & description .~ "client error")) Error e -> expectationFailure ("expected successful parse, got: " <> e) describe "Responses key parsing" $ do it "rejects a lowercase range key" $ (fromJSON [aesonQQ| { "4xx": { "description": "x" } } |] :: Result Responses) `shouldSatisfy` isError it "rejects an out-of-range class key" $ (fromJSON [aesonQQ| { "6XX": { "description": "x" } } |] :: Result Responses) `shouldSatisfy` isError it "accepts every valid range class" $ ( fromJSON [aesonQQ| { "1XX": {"description":"a"}, "5XX": {"description":"b"} } |] :: Result Responses ) `shouldSatisfy` isSuccess describe "Security Definitions Object" $ securityDefinitionsExample <=> securityDefinitionsExampleJSON describe "OAuth2 Security Definitions with merged Scope" $ oAuth2SecurityDefinitionsExample <=> oAuth2SecurityDefinitionsExampleJSON describe "OAuth2 Security Definitions with empty Scope" $ oAuth2SecurityDefinitionsEmptyExample <=> oAuth2SecurityDefinitionsEmptyExampleJSON describe "Composition Schema Example" $ compositionSchemaExample <=> compositionSchemaExampleJSON describe "Swagger Object" $ do context "Example with no paths" $ do emptyPathsFieldExample <=> emptyPathsFieldExampleJSON it "fails to parse a spec with a wrong Openapi spec version" $ do (fromJSON wrongVersionExampleJSON :: Result OpenApi) `shouldBe` Error "The provided version 3.0.4 is out of the allowed range >=3.1.0 && <=3.1.1" context "Todo Example" $ swaggerExample <=> swaggerExampleJSON context "PetStore Example" $ do it "decodes successfully" $ do fromJSON petstoreExampleJSON `shouldSatisfy` (\x -> case x of Success (_ :: OpenApi) -> True; _ -> False) it "roundtrips: fmap toJSON . fromJSON" $ do (toJSON :: OpenApi -> Value) <$> fromJSON petstoreExampleJSON `shouldBe` Success petstoreExampleJSON context "Security schemes" $ do it "merged correctly" $ do let merged = oAuth2SecurityDefinitionsReadOpenApi <> oAuth2SecurityDefinitionsWriteOpenApi <> oAuth2SecurityDefinitionsEmptyOpenApi merged `shouldBe` oAuth2SecurityDefinitionsOpenApi main :: IO () main = hspec spec isError :: Result a -> Bool isError (Error _) = True isError _ = False isSuccess :: Result a -> Bool isSuccess (Success _) = True isSuccess _ = False -- ======================================================================= -- Info object -- ======================================================================= infoExample :: Info infoExample = mempty & title .~ "Swagger Sample App" & description ?~ "This is a sample server Petstore server." & termsOfService ?~ "http://swagger.io/terms/" & contact ?~ contactExample & license ?~ licenseExample & version .~ "1.0.1" infoExampleJSON :: Value infoExampleJSON = [aesonQQ| { "title": "Swagger Sample App", "description": "This is a sample server Petstore server.", "termsOfService": "http://swagger.io/terms/", "contact": { "name": "API Support", "url": "http://www.swagger.io/support", "email": "support@swagger.io" }, "license": { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, "version": "1.0.1" } |] -- ======================================================================= -- Contact object -- ======================================================================= contactExample :: Contact contactExample = mempty & name ?~ "API Support" & url ?~ URL "http://www.swagger.io/support" & email ?~ "support@swagger.io" contactExampleJSON :: Value contactExampleJSON = [aesonQQ| { "name": "API Support", "url": "http://www.swagger.io/support", "email": "support@swagger.io" } |] -- ======================================================================= -- License object -- ======================================================================= licenseExample :: License licenseExample = "Apache 2.0" & url ?~ URL "http://www.apache.org/licenses/LICENSE-2.0.html" licenseExampleJSON :: Value licenseExampleJSON = [aesonQQ| { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" } |] -- ======================================================================= -- Operation object -- ======================================================================= operationExample :: Operation operationExample = mempty & tags .~ InsOrdHS.fromList ["pet"] & summary ?~ "Updates a pet in the store with form data" & description ?~ "" & operationId ?~ "updatePetWithForm" & parameters .~ [ Inline ( mempty & name .~ "petId" & description ?~ "ID of pet that needs to be updated" & required ?~ True & in_ .~ ParamPath & schema ?~ Inline (mempty & type_ ?~ OpenApiTypeSingle OpenApiString) ) ] & requestBody ?~ Inline ( mempty & content . at "application/x-www-form-urlencoded" ?~ ( mempty & schema ?~ ( Inline ( mempty & properties . at "petId" ?~ Inline ( mempty & description ?~ "Updated name of the pet" & type_ ?~ OpenApiTypeSingle OpenApiString ) & properties . at "status" ?~ Inline ( mempty & description ?~ "Updated status of the pet" & type_ ?~ OpenApiTypeSingle OpenApiString ) ) ) ) ) & at 200 ?~ "Pet updated." & at 405 ?~ "Invalid input" & security .~ [SecurityRequirement [("petstore_auth", ["write:pets", "read:pets"])]] operationExampleJSON :: Value operationExampleJSON = [aesonQQ| { "tags": [ "pet" ], "summary": "Updates a pet in the store with form data", "description": "", "operationId": "updatePetWithForm", "parameters": [ { "required": true, "schema": { "type": "string" }, "in": "path", "name": "petId", "description": "ID of pet that needs to be updated" } ], "requestBody": { "content": { "application/x-www-form-urlencoded": { "schema": { "properties": { "petId": { "type": "string", "description": "Updated name of the pet" }, "status": { "type": "string", "description": "Updated status of the pet" } } } } } }, "responses": { "200": { "description": "Pet updated." }, "405": { "description": "Invalid input" } }, "security": [ { "petstore_auth": [ "write:pets", "read:pets" ] } ] } |] -- ======================================================================= -- Schema object -- ======================================================================= schemaPrimitiveExample :: Schema schemaPrimitiveExample = mempty & type_ ?~ OpenApiTypeSingle OpenApiString & format ?~ "email" schemaPrimitiveExampleJSON :: Value schemaPrimitiveExampleJSON = [aesonQQ| { "type": "string", "format": "email" } |] schemaSimpleModelExample :: Schema schemaSimpleModelExample = mempty & type_ ?~ OpenApiTypeSingle OpenApiObject & required .~ ["name"] & properties .~ [ ("name", Inline (mempty & type_ ?~ OpenApiTypeSingle OpenApiString)), ("address", Ref (Reference "Address")), ( "age", Inline $ mempty & minimum_ ?~ 0 & type_ ?~ OpenApiTypeSingle OpenApiInteger & format ?~ "int32" ) ] schemaSimpleModelExampleJSON :: Value schemaSimpleModelExampleJSON = [aesonQQ| { "required": [ "name" ], "properties": { "name": { "type": "string" }, "address": { "$ref": "#/components/schemas/Address" }, "age": { "format": "int32", "minimum": 0, "type": "integer" } }, "type": "object" } |] schemaModelDictExample :: Schema schemaModelDictExample = mempty & type_ ?~ OpenApiTypeSingle OpenApiObject & additionalProperties ?~ AdditionalPropertiesSchema (Inline (mempty & type_ ?~ OpenApiTypeSingle OpenApiString)) schemaModelDictExampleJSON :: Value schemaModelDictExampleJSON = [aesonQQ| { "type": "object", "additionalProperties": { "type": "string" } } |] schemaAdditionalExample :: Schema schemaAdditionalExample = mempty & type_ ?~ OpenApiTypeSingle OpenApiObject & additionalProperties ?~ AdditionalPropertiesAllowed True schemaAdditionalExampleJSON :: Value schemaAdditionalExampleJSON = [aesonQQ| { "type": "object", "additionalProperties": true } |] schemaWithExampleExample :: Schema schemaWithExampleExample = mempty & type_ ?~ OpenApiTypeSingle OpenApiObject & properties .~ [ ( "id", Inline $ mempty & type_ ?~ OpenApiTypeSingle OpenApiInteger & format ?~ "int64" ), ( "name", Inline $ mempty & type_ ?~ OpenApiTypeSingle OpenApiString ) ] & required .~ ["name"] & example ?~ [aesonQQ| { "name": "Puma", "id": 1 } |] schemaWithExampleExampleJSON :: Value schemaWithExampleExampleJSON = [aesonQQ| { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" } }, "required": [ "name" ], "example": { "name": "Puma", "id": 1 } } |] -- ======================================================================= -- Definitions object -- ======================================================================= definitionsExample :: HashMap Text Schema definitionsExample = [ ( "Category", mempty & type_ ?~ OpenApiTypeSingle OpenApiObject & properties .~ [ ( "id", Inline $ mempty & type_ ?~ OpenApiTypeSingle OpenApiInteger & format ?~ "int64" ), ("name", Inline (mempty & type_ ?~ OpenApiTypeSingle OpenApiString)) ] ), ( "Tag", mempty & type_ ?~ OpenApiTypeSingle OpenApiObject & properties .~ [ ( "id", Inline $ mempty & type_ ?~ OpenApiTypeSingle OpenApiInteger & format ?~ "int64" ), ("name", Inline (mempty & type_ ?~ OpenApiTypeSingle OpenApiString)) ] ) ] definitionsExampleJSON :: Value definitionsExampleJSON = [aesonQQ| { "Category": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" } } }, "Tag": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" } } } } |] -- ======================================================================= -- Parameters Definition object -- ======================================================================= paramsDefinitionExample :: HashMap Text Param paramsDefinitionExample = [ ( "skipParam", mempty & name .~ "skip" & description ?~ "number of items to skip" & required ?~ True & in_ .~ ParamQuery & schema ?~ Inline ( mempty & type_ ?~ OpenApiTypeSingle OpenApiInteger & format ?~ "int32" ) ), ( "limitParam", mempty & name .~ "limit" & description ?~ "max records to return" & required ?~ True & in_ .~ ParamQuery & schema ?~ Inline ( mempty & type_ ?~ OpenApiTypeSingle OpenApiInteger & format ?~ "int32" ) ) ] paramsDefinitionExampleJSON :: Value paramsDefinitionExampleJSON = [aesonQQ| { "skipParam": { "name": "skip", "in": "query", "description": "number of items to skip", "required": true, "schema": { "type": "integer", "format": "int32" } }, "limitParam": { "name": "limit", "in": "query", "description": "max records to return", "required": true, "schema": { "type": "integer", "format": "int32" } } } |] -- ======================================================================= -- Responses Definition object -- ======================================================================= responsesDefinitionExample :: HashMap Text Response responsesDefinitionExample = [ ("NotFound", mempty & description .~ "Entity not found."), ("IllegalInput", mempty & description .~ "Illegal input for operation.") ] responsesDefinitionExampleJSON :: Value responsesDefinitionExampleJSON = [aesonQQ| { "NotFound": { "description": "Entity not found." }, "IllegalInput": { "description": "Illegal input for operation." } } |] statusRangeResponsesExample :: Responses statusRangeResponsesExample = mempty & at 200 ?~ Inline (mempty & description .~ "OK") & at (StatusRange R4XX) ?~ Inline (mempty & description .~ "Client error") statusRangeResponsesExampleJSON :: Value statusRangeResponsesExampleJSON = [aesonQQ| { "200": { "description": "OK" }, "4XX": { "description": "Client error" } } |] responsesMixedExample :: Responses responsesMixedExample = mempty & default_ ?~ Inline (mempty & description .~ "Unexpected error") & at 200 ?~ Inline (mempty & description .~ "OK") & at (StatusRange R4XX) ?~ Inline (mempty & description .~ "Client error") & at (StatusRange R5XX) ?~ Ref (Reference "ServerError") responsesMixedExampleJSON :: Value responsesMixedExampleJSON = [aesonQQ| { "default": { "description": "Unexpected error" }, "200": { "description": "OK" }, "4XX": { "description": "Client error" }, "5XX": { "$ref": "#/components/responses/ServerError" } } |] -- ======================================================================= -- Security Definition object -- ======================================================================= securityDefinitionsExample :: SecurityDefinitions securityDefinitionsExample = SecurityDefinitions [ ( "api_key", SecurityScheme { _securitySchemeType = SecuritySchemeApiKey (ApiKeyParams "api_key" ApiKeyHeader), _securitySchemeDescription = Nothing } ), ( "petstore_auth", SecurityScheme { _securitySchemeType = SecuritySchemeOAuth2 ( mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog", _oAath2RefreshUrl = Nothing, _oAuth2Scopes = [ ("write:pets", "modify pets in your account"), ("read:pets", "read your pets") ] } ), _securitySchemeDescription = Nothing } ) ] securityDefinitionsExampleJSON :: Value securityDefinitionsExampleJSON = [aesonQQ| { "api_key": { "in": "header", "name": "api_key", "type": "apiKey" }, "petstore_auth": { "type": "oauth2", "flows": { "implicit": { "scopes": { "write:pets": "modify pets in your account", "read:pets": "read your pets" }, "authorizationUrl": "http://swagger.io/api/oauth/dialog" } } } } |] oAuth2SecurityDefinitionsReadExample :: SecurityDefinitions oAuth2SecurityDefinitionsReadExample = SecurityDefinitions [ ( "petstore_auth", SecurityScheme { _securitySchemeType = SecuritySchemeOAuth2 ( mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog", _oAath2RefreshUrl = Nothing, _oAuth2Scopes = [("read:pets", "read your pets")] } ), _securitySchemeDescription = Nothing } ) ] oAuth2SecurityDefinitionsWriteExample :: SecurityDefinitions oAuth2SecurityDefinitionsWriteExample = SecurityDefinitions [ ( "petstore_auth", SecurityScheme { _securitySchemeType = SecuritySchemeOAuth2 ( mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog", _oAath2RefreshUrl = Nothing, _oAuth2Scopes = [("write:pets", "modify pets in your account")] } ), _securitySchemeDescription = Nothing } ) ] oAuth2SecurityDefinitionsEmptyExample :: SecurityDefinitions oAuth2SecurityDefinitionsEmptyExample = SecurityDefinitions [ ( "petstore_auth", SecurityScheme { _securitySchemeType = SecuritySchemeOAuth2 ( mempty & implicit ?~ OAuth2Flow { _oAuth2Params = OAuth2ImplicitFlow "http://swagger.io/api/oauth/dialog", _oAath2RefreshUrl = Nothing, _oAuth2Scopes = [] } ), _securitySchemeDescription = Nothing } ) ] oAuth2SecurityDefinitionsExample :: SecurityDefinitions oAuth2SecurityDefinitionsExample = oAuth2SecurityDefinitionsWriteExample <> oAuth2SecurityDefinitionsReadExample <> oAuth2SecurityDefinitionsEmptyExample oAuth2SecurityDefinitionsExampleJSON :: Value oAuth2SecurityDefinitionsExampleJSON = [aesonQQ| { "petstore_auth": { "type": "oauth2", "flows": { "implicit": { "scopes": { "write:pets": "modify pets in your account", "read:pets": "read your pets" }, "authorizationUrl": "http://swagger.io/api/oauth/dialog" } } } } |] oAuth2SecurityDefinitionsEmptyExampleJSON :: Value oAuth2SecurityDefinitionsEmptyExampleJSON = [aesonQQ| { "petstore_auth": { "type": "oauth2", "flows": { "implicit": { "scopes": {}, "authorizationUrl": "http://swagger.io/api/oauth/dialog" } } } } |] oAuth2SecurityDefinitionsReadOpenApi :: OpenApi oAuth2SecurityDefinitionsReadOpenApi = mempty & components . securitySchemes .~ oAuth2SecurityDefinitionsReadExample oAuth2SecurityDefinitionsWriteOpenApi :: OpenApi oAuth2SecurityDefinitionsWriteOpenApi = mempty & components . securitySchemes .~ oAuth2SecurityDefinitionsWriteExample oAuth2SecurityDefinitionsEmptyOpenApi :: OpenApi oAuth2SecurityDefinitionsEmptyOpenApi = mempty & components . securitySchemes .~ oAuth2SecurityDefinitionsEmptyExample oAuth2SecurityDefinitionsOpenApi :: OpenApi oAuth2SecurityDefinitionsOpenApi = mempty & components . securitySchemes .~ oAuth2SecurityDefinitionsExample -- ======================================================================= -- Swagger object -- ======================================================================= emptyPathsFieldExample :: OpenApi emptyPathsFieldExample = mempty wrongVersionExampleJSON :: Value wrongVersionExampleJSON = [aesonQQ| { "openapi": "3.0.4", "info": {"version": "", "title": ""}, "paths": {}, "components": {} } |] emptyPathsFieldExampleJSON :: Value emptyPathsFieldExampleJSON = [aesonQQ| { "openapi": "3.1.0", "info": {"version": "", "title": ""}, "paths": {}, "components": {} } |] swaggerExample :: OpenApi swaggerExample = mempty -- & basePath ?~ "/" -- & schemes ?~ [Http] & info .~ ( mempty & version .~ "1.0" & title .~ "Todo API" & license ?~ "MIT" & license . _Just . url ?~ URL "http://mit.com" & description ?~ "This is an API that tests servant-swagger support for a Todo API" ) & paths . at "/todo/{id}" ?~ ( mempty & get ?~ ( (mempty :: Operation) & responses . at 200 ?~ Inline ( mempty & description .~ "OK" & content . at "application/json" ?~ ( mempty & schema ?~ Inline ( mempty & type_ ?~ OpenApiTypeSingle OpenApiObject & example ?~ [aesonQQ| { "created": 100, "description": "get milk" } |] & description ?~ "This is some real Todo right here" & properties .~ [ ( "created", Inline $ mempty & type_ ?~ OpenApiTypeSingle OpenApiInteger & format ?~ "int32" ), ("description", Inline (mempty & type_ ?~ OpenApiTypeSingle OpenApiString)) ] ) ) ) & parameters .~ [ Inline $ mempty & required ?~ True & name .~ "id" & description ?~ "TodoId param" & in_ .~ ParamPath & schema ?~ Inline ( mempty & type_ ?~ OpenApiTypeSingle OpenApiString ) ] & tags .~ InsOrdHS.fromList ["todo"] ) ) swaggerExampleJSON :: Value swaggerExampleJSON = [aesonQQ| { "openapi": "3.1.0", "info": { "version": "1.0", "title": "Todo API", "license": { "url": "http://mit.com", "name": "MIT" }, "description": "This is an API that tests servant-swagger support for a Todo API" }, "paths": { "/todo/{id}": { "get": { "tags": [ "todo" ], "parameters": [ { "required": true, "schema": { "type": "string" }, "in": "path", "name": "id", "description": "TodoId param" } ], "responses": { "200": { "content": { "application/json": { "schema": { "example": { "created": 100, "description": "get milk" }, "type": "object", "description": "This is some real Todo right here", "properties": { "created": { "format": "int32", "type": "integer" }, "description": { "type": "string" } } } } }, "description": "OK" } } } } }, "components": {} } |] petstoreExampleJSON :: Value petstoreExampleJSON = [aesonQQ| { "openapi": "3.1.0", "info": { "version": "1.0.0", "title": "Swagger Petstore", "license": { "name": "MIT" } }, "servers": [ { "url": "http://petstore.swagger.io/v1" } ], "paths": { "/pets": { "get": { "summary": "List all pets", "operationId": "listPets", "tags": [ "pets" ], "parameters": [ { "name": "limit", "in": "query", "description": "How many items to return at one time (max 100)", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "A paged array of pets", "headers": { "x-next": { "description": "A link to the next page of responses", "schema": { "type": "string" } } }, "content": { "application/json": { "schema": { "type": "array", "items": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "tag": { "type": "string" } } } } } } }, "default": { "description": "unexpected error", "content": { "application/json": { "schema": { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } } } } } }, "post": { "summary": "Create a pet", "operationId": "createPets", "tags": [ "pets" ], "responses": { "201": { "description": "Null response" }, "default": { "description": "unexpected error", "content": { "application/json": { "schema": { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } } } } } } }, "/pets/{petId}": { "get": { "summary": "Info for a specific pet", "operationId": "showPetById", "tags": [ "pets" ], "parameters": [ { "name": "petId", "in": "path", "required": true, "description": "The id of the pet to retrieve", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Expected response to a valid request", "content": { "application/json": { "schema": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "tag": { "type": "string" } } } } } }, "default": { "description": "unexpected error", "content": { "application/json": { "schema": { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } } } } } } } }, "components": { "schemas": { "Pet": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "tag": { "type": "string" } } }, "Pets": { "type": "array", "items": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "tag": { "type": "string" } } } }, "Error": { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } } } } |] compositionSchemaExample :: Schema compositionSchemaExample = mempty & Data.OpenApi.allOf ?~ [ Ref (Reference "Other"), Inline ( mempty & type_ ?~ OpenApiTypeSingle OpenApiObject & properties .~ [ ( "greet", Inline $ mempty & type_ ?~ OpenApiTypeSingle OpenApiString ) ] ) ] compositionSchemaExampleJSON :: Value compositionSchemaExampleJSON = [aesonQQ| { "allOf": [ { "$ref": "#/components/schemas/Other" }, { "type": "object", "properties": { "greet": { "type": "string" } } } ] } |]