packages feed

openapi3-3.2.5: test/Data/OpenApiSpec.hs

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE QuasiQuotes #-}
module Data.OpenApiSpec where

import Prelude ()
import Prelude.Compat

import Control.Lens

import Data.Aeson
import Data.Aeson.QQ.Simple
import Data.HashMap.Strict (HashMap)
import qualified Data.HashSet.InsOrd as InsOrdHS
import Data.Text (Text)

import Data.OpenApi
import SpecCommon
import Test.Hspec hiding (example)

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 "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.0.0 && <=3.0.3"
    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

-- =======================================================================
-- 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_ ?~ 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_ ?~ OpenApiString)
      & properties . at "status" ?~ Inline (mempty
        & description ?~ "Updated status of the pet"
        & type_ ?~ 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_  ?~ OpenApiString
  & format ?~ "email"

schemaPrimitiveExampleJSON :: Value
schemaPrimitiveExampleJSON = [aesonQQ|
{
    "type": "string",
    "format": "email"
}
|]

schemaSimpleModelExample :: Schema
schemaSimpleModelExample = mempty
  & type_ ?~ OpenApiObject
  & required .~ [ "name" ]
  & properties .~
      [ ("name", Inline (mempty & type_ ?~ OpenApiString))
      , ("address", Ref (Reference "Address"))
      , ("age", Inline $ mempty
            & minimum_ ?~ 0
            & type_    ?~ 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_ ?~ OpenApiObject
  & additionalProperties ?~ AdditionalPropertiesSchema (Inline (mempty & type_ ?~ OpenApiString))

schemaModelDictExampleJSON :: Value
schemaModelDictExampleJSON = [aesonQQ|
{
  "type": "object",
  "additionalProperties": {
    "type": "string"
  }
}
|]

schemaAdditionalExample :: Schema
schemaAdditionalExample = mempty
  & type_ ?~ OpenApiObject
  & additionalProperties ?~ AdditionalPropertiesAllowed True

schemaAdditionalExampleJSON :: Value
schemaAdditionalExampleJSON = [aesonQQ|
{
  "type": "object",
  "additionalProperties": true
}
|]

schemaWithExampleExample :: Schema
schemaWithExampleExample = mempty
  & type_ ?~ OpenApiObject
  & properties .~
      [ ("id", Inline $ mempty
            & type_  ?~ OpenApiInteger
            & format ?~ "int64" )
      , ("name", Inline $ mempty
            & type_ ?~ 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_ ?~ OpenApiObject
      & properties .~
          [ ("id", Inline $ mempty
              & type_  ?~ OpenApiInteger
              & format ?~ "int64")
          , ("name", Inline (mempty & type_ ?~ OpenApiString)) ] )
  , ("Tag", mempty
      & type_ ?~ OpenApiObject
      & properties .~
          [ ("id", Inline $ mempty
              & type_  ?~ OpenApiInteger
              & format ?~ "int64")
          , ("name", Inline (mempty & type_ ?~ 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_  ?~ OpenApiInteger
          & format ?~ "int32" ))
  , ("limitParam", mempty
      & name .~ "limit"
      & description ?~ "max records to return"
      & required ?~ True
      & in_    .~ ParamQuery
      & schema ?~ Inline (mempty
          & type_  ?~ 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."
  }
}
|]

-- =======================================================================
-- 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.0.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_ ?~ OpenApiObject
                  & example ?~ [aesonQQ|
                      {
                        "created": 100,
                        "description": "get milk"
                      } |]
                  & description ?~ "This is some real Todo right here"
                  & properties .~
                     [ ("created", Inline $ mempty
                         & type_  ?~ OpenApiInteger
                         & format ?~ "int32")
                     , ("description", Inline (mempty & type_ ?~ OpenApiString))])))
      & parameters .~
          [ Inline $ mempty
              & required ?~ True
              & name .~ "id"
              & description ?~ "TodoId param"
              & in_ .~ ParamPath
              & schema ?~ Inline (mempty
                  & type_ ?~ OpenApiString ) ]
      & tags .~ InsOrdHS.fromList [ "todo" ] ))

swaggerExampleJSON :: Value
swaggerExampleJSON = [aesonQQ|
{
    "openapi": "3.0.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.0.3",
  "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_ ?~ OpenApiObject
             & properties .~
                  [ ("greet", Inline $ mempty
                            & type_ ?~ OpenApiString) ])
  ]

compositionSchemaExampleJSON :: Value
compositionSchemaExampleJSON = [aesonQQ|
{
  "allOf": [
      {
         "$ref": "#/components/schemas/Other"
      },
      {
        "type": "object",
        "properties": {
          "greet": { "type": "string" }
        }
      }
  ]
}
|]