servant-openapi3 2.0.1.1 → 2.0.1.2
raw patch · 6 files changed
+380/−24 lines, 6 filesdep ~basedep ~doctestdep ~hspecPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: base, doctest, hspec, openapi3
API changes (from Hackage documentation)
- Servant.OpenApi.Internal: instance Data.OpenApi.Internal.Schema.ToSchema a => Data.OpenApi.Internal.Schema.ToSchema (Servant.API.UVerb.WithStatus s a)
- Servant.OpenApi.Internal.Orphans: instance Data.OpenApi.Internal.Schema.ToSchema a => Data.OpenApi.Internal.Schema.ToSchema (Servant.Types.SourceT.SourceT m a)
+ Servant.OpenApi.Internal: instance (Data.Typeable.Internal.Typeable (Servant.API.UVerb.WithStatus s a), Data.OpenApi.Internal.Schema.ToSchema a) => Data.OpenApi.Internal.Schema.ToSchema (Servant.API.UVerb.WithStatus s a)
+ Servant.OpenApi.Internal.Orphans: instance (Data.Typeable.Internal.Typeable (Servant.Types.SourceT.SourceT m a), Data.OpenApi.Internal.Schema.ToSchema a) => Data.OpenApi.Internal.Schema.ToSchema (Servant.Types.SourceT.SourceT m a)
+ Servant.OpenApi.Internal.Test: encodePretty :: ToJSON a => a -> ByteString
Files
- example/example.cabal +1/−1
- servant-openapi3.cabal +6/−5
- src/Servant/OpenApi.hs +353/−6
- src/Servant/OpenApi/Internal.hs +2/−1
- src/Servant/OpenApi/Internal/Orphans.hs +4/−1
- src/Servant/OpenApi/Internal/Test.hs +14/−10
example/example.cabal view
@@ -17,7 +17,7 @@ GHC ==8.4.4 || ==8.6.5 || ==8.8.4- || ==8.10.2+ || ==8.10.4 library ghc-options: -Wall
servant-openapi3.cabal view
@@ -1,5 +1,5 @@ name: servant-openapi3-version: 2.0.1.1+version: 2.0.1.2 synopsis: Generate a Swagger/OpenAPI/OAS 3.0 specification for your servant API. description: Swagger is a project used to describe and document RESTful APIs. The core of the @@ -32,7 +32,7 @@ GHC ==8.4.4 || ==8.6.5 || ==8.8.4- || ==8.10.2+ || ==8.10.4 extra-source-files: README.md@@ -80,10 +80,10 @@ , bytestring >=0.10.8.1 && <0.11 , http-media >=0.7.1.3 && <0.9 , insert-ordered-containers >=0.2.1.0 && <0.3- , lens >=4.17 && <4.20+ , lens >=4.17 && <5.1 , servant >=0.17 && <0.19 , singleton-bool >=0.1.4 && <0.2- , openapi3 >=3.0.0 && <4.0+ , openapi3 >=3.0.0 && <3.2 , text >=1.2.3.0 && <1.3 , unordered-containers >=0.2.9.0 && <0.3 @@ -120,7 +120,8 @@ , lens-aeson >=1.0.2 && <1.2 , servant , servant-openapi3- , openapi3+ -- openapi3 3.1.0 fixes some ordering-related issues, making tests stable+ , openapi3 >= 3.1.0 , text , template-haskell , utf8-string >=1.0.1.1 && <1.1
src/Servant/OpenApi.hs view
@@ -57,6 +57,7 @@ -- >>> import Test.Hspec -- >>> import Test.QuickCheck -- >>> import qualified Data.ByteString.Lazy.Char8 as BSL8+-- >>> import Servant.OpenApi.Internal.Test -- >>> :set -XDataKinds -- >>> :set -XDeriveDataTypeable -- >>> :set -XDeriveGeneric@@ -98,8 +99,114 @@ -- $generate -- In order to generate @'OpenApi'@ specification for a servant API, just use @'toOpenApi'@: ----- >>> BSL8.putStrLn $ encode $ toOpenApi (Proxy :: Proxy UserAPI)--- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/":{"get":{"responses":{"200":{"content":{"application/json;charset=utf-8":{"schema":{"items":{"$ref":"#/components/schemas/User"},"type":"array"}}},"description":""}}},"post":{"requestBody":{"content":{"application/json;charset=utf-8":{"schema":{"$ref":"#/components/schemas/User"}}}},"responses":{"400":{"description":"Invalid `body`"},"200":{"content":{"application/json;charset=utf-8":{"schema":{"$ref":"#/components/schemas/UserId"}}},"description":""}}}},"/{user_id}":{"get":{"parameters":[{"required":true,"schema":{"type":"integer"},"in":"path","name":"user_id"}],"responses":{"404":{"description":"`user_id` not found"},"200":{"content":{"application/json;charset=utf-8":{"schema":{"$ref":"#/components/schemas/User"}}},"description":""}}}}},"components":{"schemas":{"User":{"required":["name","age"],"type":"object","properties":{"age":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"},"name":{"type":"string"}}},"UserId":{"type":"integer"}}}}+-- >>> BSL8.putStrLn $ encodePretty $ toOpenApi (Proxy :: Proxy UserAPI)+-- {+-- "components": {+-- "schemas": {+-- "User": {+-- "properties": {+-- "age": {+-- "maximum": 9223372036854775807,+-- "minimum": -9223372036854775808,+-- "type": "integer"+-- },+-- "name": {+-- "type": "string"+-- }+-- },+-- "required": [+-- "name",+-- "age"+-- ],+-- "type": "object"+-- },+-- "UserId": {+-- "type": "integer"+-- }+-- }+-- },+-- "info": {+-- "title": "",+-- "version": ""+-- },+-- "openapi": "3.0.0",+-- "paths": {+-- "/": {+-- "get": {+-- "responses": {+-- "200": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "items": {+-- "$ref": "#/components/schemas/User"+-- },+-- "type": "array"+-- }+-- }+-- },+-- "description": ""+-- }+-- }+-- },+-- "post": {+-- "requestBody": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "$ref": "#/components/schemas/User"+-- }+-- }+-- }+-- },+-- "responses": {+-- "200": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "$ref": "#/components/schemas/UserId"+-- }+-- }+-- },+-- "description": ""+-- },+-- "400": {+-- "description": "Invalid `body`"+-- }+-- }+-- }+-- },+-- "/{user_id}": {+-- "get": {+-- "parameters": [+-- {+-- "in": "path",+-- "name": "user_id",+-- "required": true,+-- "schema": {+-- "type": "integer"+-- }+-- }+-- ],+-- "responses": {+-- "200": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "$ref": "#/components/schemas/User"+-- }+-- }+-- },+-- "description": ""+-- },+-- "404": {+-- "description": "`user_id` not found"+-- }+-- }+-- }+-- }+-- }+-- } -- -- By default @'toOpenApi'@ will generate specification for all API routes, parameters, headers, responses and data schemas. --@@ -113,14 +220,129 @@ -- We can add this information using field lenses from @"Data.OpenApi"@: -- -- >>> :{--- BSL8.putStrLn $ encode $ toOpenApi (Proxy :: Proxy UserAPI)+-- BSL8.putStrLn $ encodePretty $ toOpenApi (Proxy :: Proxy UserAPI) -- & info.title .~ "User API" -- & info.version .~ "1.0" -- & info.description ?~ "This is an API for the Users service" -- & info.license ?~ "MIT" -- & servers .~ ["https://example.com"] -- :}--- {"openapi":"3.0.0","info":{"version":"1.0","title":"User API","license":{"name":"MIT"},"description":"This is an API for the Users service"},"servers":[{"url":"https://example.com"}],"paths":{"/":{"get":{"responses":{"200":{"content":{"application/json;charset=utf-8":{"schema":{"items":{"$ref":"#/components/schemas/User"},"type":"array"}}},"description":""}}},"post":{"requestBody":{"content":{"application/json;charset=utf-8":{"schema":{"$ref":"#/components/schemas/User"}}}},"responses":{"400":{"description":"Invalid `body`"},"200":{"content":{"application/json;charset=utf-8":{"schema":{"$ref":"#/components/schemas/UserId"}}},"description":""}}}},"/{user_id}":{"get":{"parameters":[{"required":true,"schema":{"type":"integer"},"in":"path","name":"user_id"}],"responses":{"404":{"description":"`user_id` not found"},"200":{"content":{"application/json;charset=utf-8":{"schema":{"$ref":"#/components/schemas/User"}}},"description":""}}}}},"components":{"schemas":{"User":{"required":["name","age"],"type":"object","properties":{"age":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"},"name":{"type":"string"}}},"UserId":{"type":"integer"}}}}+-- {+-- "components": {+-- "schemas": {+-- "User": {+-- "properties": {+-- "age": {+-- "maximum": 9223372036854775807,+-- "minimum": -9223372036854775808,+-- "type": "integer"+-- },+-- "name": {+-- "type": "string"+-- }+-- },+-- "required": [+-- "name",+-- "age"+-- ],+-- "type": "object"+-- },+-- "UserId": {+-- "type": "integer"+-- }+-- }+-- },+-- "info": {+-- "description": "This is an API for the Users service",+-- "license": {+-- "name": "MIT"+-- },+-- "title": "User API",+-- "version": "1.0"+-- },+-- "openapi": "3.0.0",+-- "paths": {+-- "/": {+-- "get": {+-- "responses": {+-- "200": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "items": {+-- "$ref": "#/components/schemas/User"+-- },+-- "type": "array"+-- }+-- }+-- },+-- "description": ""+-- }+-- }+-- },+-- "post": {+-- "requestBody": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "$ref": "#/components/schemas/User"+-- }+-- }+-- }+-- },+-- "responses": {+-- "200": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "$ref": "#/components/schemas/UserId"+-- }+-- }+-- },+-- "description": ""+-- },+-- "400": {+-- "description": "Invalid `body`"+-- }+-- }+-- }+-- },+-- "/{user_id}": {+-- "get": {+-- "parameters": [+-- {+-- "in": "path",+-- "name": "user_id",+-- "required": true,+-- "schema": {+-- "type": "integer"+-- }+-- }+-- ],+-- "responses": {+-- "200": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "$ref": "#/components/schemas/User"+-- }+-- }+-- },+-- "description": ""+-- },+-- "404": {+-- "description": "`user_id` not found"+-- }+-- }+-- }+-- }+-- },+-- "servers": [+-- {+-- "url": "https://example.com"+-- }+-- ]+-- } -- -- It is also useful to annotate or modify certain endpoints. -- @'subOperations'@ provides a convenient way to zoom into a part of an API.@@ -134,11 +356,136 @@ -- >>> let getOps = subOperations (Proxy :: Proxy (GetUsers :<|> GetUser)) (Proxy :: Proxy UserAPI) -- >>> let postOps = subOperations (Proxy :: Proxy PostUser) (Proxy :: Proxy UserAPI) -- >>> :{--- BSL8.putStrLn $ encode $ toOpenApi (Proxy :: Proxy UserAPI)+-- BSL8.putStrLn $ encodePretty $ toOpenApi (Proxy :: Proxy UserAPI) -- & applyTagsFor getOps ["get" & description ?~ "GET operations"] -- & applyTagsFor postOps ["post" & description ?~ "POST operations"] -- :}--- {"openapi":"3.0.0","info":{"version":"","title":""},"paths":{"/":{"get":{"tags":["get"],"responses":{"200":{"content":{"application/json;charset=utf-8":{"schema":{"items":{"$ref":"#/components/schemas/User"},"type":"array"}}},"description":""}}},"post":{"tags":["post"],"requestBody":{"content":{"application/json;charset=utf-8":{"schema":{"$ref":"#/components/schemas/User"}}}},"responses":{"400":{"description":"Invalid `body`"},"200":{"content":{"application/json;charset=utf-8":{"schema":{"$ref":"#/components/schemas/UserId"}}},"description":""}}}},"/{user_id}":{"get":{"tags":["get"],"parameters":[{"required":true,"schema":{"type":"integer"},"in":"path","name":"user_id"}],"responses":{"404":{"description":"`user_id` not found"},"200":{"content":{"application/json;charset=utf-8":{"schema":{"$ref":"#/components/schemas/User"}}},"description":""}}}}},"components":{"schemas":{"User":{"required":["name","age"],"type":"object","properties":{"age":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"},"name":{"type":"string"}}},"UserId":{"type":"integer"}}},"tags":[{"name":"get","description":"GET operations"},{"name":"post","description":"POST operations"}]}+-- {+-- "components": {+-- "schemas": {+-- "User": {+-- "properties": {+-- "age": {+-- "maximum": 9223372036854775807,+-- "minimum": -9223372036854775808,+-- "type": "integer"+-- },+-- "name": {+-- "type": "string"+-- }+-- },+-- "required": [+-- "name",+-- "age"+-- ],+-- "type": "object"+-- },+-- "UserId": {+-- "type": "integer"+-- }+-- }+-- },+-- "info": {+-- "title": "",+-- "version": ""+-- },+-- "openapi": "3.0.0",+-- "paths": {+-- "/": {+-- "get": {+-- "responses": {+-- "200": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "items": {+-- "$ref": "#/components/schemas/User"+-- },+-- "type": "array"+-- }+-- }+-- },+-- "description": ""+-- }+-- },+-- "tags": [+-- "get"+-- ]+-- },+-- "post": {+-- "requestBody": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "$ref": "#/components/schemas/User"+-- }+-- }+-- }+-- },+-- "responses": {+-- "200": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "$ref": "#/components/schemas/UserId"+-- }+-- }+-- },+-- "description": ""+-- },+-- "400": {+-- "description": "Invalid `body`"+-- }+-- },+-- "tags": [+-- "post"+-- ]+-- }+-- },+-- "/{user_id}": {+-- "get": {+-- "parameters": [+-- {+-- "in": "path",+-- "name": "user_id",+-- "required": true,+-- "schema": {+-- "type": "integer"+-- }+-- }+-- ],+-- "responses": {+-- "200": {+-- "content": {+-- "application/json;charset=utf-8": {+-- "schema": {+-- "$ref": "#/components/schemas/User"+-- }+-- }+-- },+-- "description": ""+-- },+-- "404": {+-- "description": "`user_id` not found"+-- }+-- },+-- "tags": [+-- "get"+-- ]+-- }+-- }+-- },+-- "tags": [+-- {+-- "description": "GET operations",+-- "name": "get"+-- },+-- {+-- "description": "POST operations",+-- "name": "post"+-- }+-- ]+-- } -- -- This applies @\"get\"@ tag to the @GET@ endpoints and @\"post\"@ tag to the @POST@ endpoint of the User API.
src/Servant/OpenApi/Internal.hs view
@@ -32,6 +32,7 @@ import Data.Singletons.Bool import Data.Text (Text) import qualified Data.Text as Text+import Data.Typeable (Typeable) import GHC.TypeLits import Network.HTTP.Media (MediaType) import Servant.API@@ -234,7 +235,7 @@ , _openApiExternalDocs = _openApiExternalDocs s <|> _openApiExternalDocs t } -instance ToSchema a => ToSchema (WithStatus s a) where+instance (Typeable (WithStatus s a), ToSchema a) => ToSchema (WithStatus s a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a) #endif
src/Servant/OpenApi/Internal/Orphans.hs view
@@ -1,14 +1,17 @@ {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}+ {-# OPTIONS_GHC -fno-warn-orphans #-} module Servant.OpenApi.Internal.Orphans where import Data.OpenApi import Data.Proxy (Proxy (..))+import Data.Typeable (Typeable) import Servant.Types.SourceT (SourceT) -- | Pretend that 'SourceT m a' is '[a]'. -- -- @since 1.1.7 ---instance ToSchema a => ToSchema (SourceT m a) where+instance (Typeable (SourceT m a), ToSchema a) => ToSchema (SourceT m a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy [a])
src/Servant/OpenApi/Internal/Test.hs view
@@ -7,7 +7,8 @@ module Servant.OpenApi.Internal.Test where import Data.Aeson (ToJSON (..))-import Data.Aeson.Encode.Pretty (encodePretty)+import qualified Data.Aeson.Encode.Pretty as P+import qualified Data.ByteString.Lazy as BSL import Data.OpenApi (Pattern, ToSchema, toSchema) import Data.OpenApi.Schema.Validation import Data.Text (Text)@@ -156,19 +157,19 @@ -- <BLANKLINE> -- OpenApi Schema: -- {--- "required": [--- "name",--- "phone"--- ],--- "type": "object", -- "properties": {--- "phone": {--- "type": "integer"--- }, -- "name": { -- "type": "string"+-- },+-- "phone": {+-- "type": "integer" -- }--- }+-- },+-- "required": [+-- "name",+-- "phone"+-- ],+-- "type": "object" -- } -- <BLANKLINE> --@@ -198,3 +199,6 @@ maybeCounterExample :: Maybe String -> Property maybeCounterExample Nothing = property True maybeCounterExample (Just s) = counterexample s (property False)++encodePretty :: ToJSON a => a -> BSL.ByteString+encodePretty = P.encodePretty' $ P.defConfig { P.confCompare = P.compare }