Skip to content

Commit ddedacb

Browse files
committed
Switch to openapi3
1 parent 0b828aa commit ddedacb

File tree

3 files changed

+294
-271
lines changed

3 files changed

+294
-271
lines changed

src/Servant/Swagger.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ import Servant.Swagger.Internal.Orphans ()
9999
-- In order to generate @'Swagger'@ specification for a servant API, just use @'toSwagger'@:
100100
--
101101
-- >>> BSL8.putStrLn $ encode $ toSwagger (Proxy :: Proxy UserAPI)
102-
-- {"swagger":"2.0","info":{"version":"","title":""},"paths":{"/":{"get":{"produces":["application/json;charset=utf-8"],"responses":{"200":{"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"},"description":""}}},"post":{"consumes":["application/json;charset=utf-8"],"produces":["application/json;charset=utf-8"],"parameters":[{"required":true,"schema":{"$ref":"#/definitions/User"},"in":"body","name":"body"}],"responses":{"400":{"description":"Invalid `body`"},"200":{"schema":{"$ref":"#/definitions/UserId"},"description":""}}}},"/{user_id}":{"get":{"produces":["application/json;charset=utf-8"],"parameters":[{"required":true,"in":"path","name":"user_id","type":"integer"}],"responses":{"404":{"description":"`user_id` not found"},"200":{"schema":{"$ref":"#/definitions/User"},"description":""}}}}},"definitions":{"User":{"required":["name","age"],"properties":{"name":{"type":"string"},"age":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"}},"type":"object"},"UserId":{"type":"integer"}}}
102+
-- {"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"],"properties":{"name":{"type":"string"},"age":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"}},"type":"object"},"UserId":{"type":"integer"}}}}
103103
--
104104
-- By default @'toSwagger'@ will generate specification for all API routes, parameters, headers, responses and data schemas.
105105
--
@@ -118,9 +118,9 @@ import Servant.Swagger.Internal.Orphans ()
118118
-- & info.version .~ "1.0"
119119
-- & info.description ?~ "This is an API for the Users service"
120120
-- & info.license ?~ "MIT"
121-
-- & host ?~ "example.com"
121+
-- & servers .~ ["https://example.com"]
122122
-- :}
123-
-- {"swagger":"2.0","info":{"version":"1.0","title":"User API","license":{"name":"MIT"},"description":"This is an API for the Users service"},"host":"example.com","paths":{"/":{"get":{"produces":["application/json;charset=utf-8"],"responses":{"200":{"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"},"description":""}}},"post":{"consumes":["application/json;charset=utf-8"],"produces":["application/json;charset=utf-8"],"parameters":[{"required":true,"schema":{"$ref":"#/definitions/User"},"in":"body","name":"body"}],"responses":{"400":{"description":"Invalid `body`"},"200":{"schema":{"$ref":"#/definitions/UserId"},"description":""}}}},"/{user_id}":{"get":{"produces":["application/json;charset=utf-8"],"parameters":[{"required":true,"in":"path","name":"user_id","type":"integer"}],"responses":{"404":{"description":"`user_id` not found"},"200":{"schema":{"$ref":"#/definitions/User"},"description":""}}}}},"definitions":{"User":{"required":["name","age"],"properties":{"name":{"type":"string"},"age":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"}},"type":"object"},"UserId":{"type":"integer"}}}
123+
-- {"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"],"properties":{"name":{"type":"string"},"age":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"}},"type":"object"},"UserId":{"type":"integer"}}}}
124124
--
125125
-- It is also useful to annotate or modify certain endpoints.
126126
-- @'subOperations'@ provides a convenient way to zoom into a part of an API.
@@ -138,7 +138,7 @@ import Servant.Swagger.Internal.Orphans ()
138138
-- & applyTagsFor getOps ["get" & description ?~ "GET operations"]
139139
-- & applyTagsFor postOps ["post" & description ?~ "POST operations"]
140140
-- :}
141-
-- {"swagger":"2.0","info":{"version":"","title":""},"paths":{"/":{"get":{"tags":["get"],"produces":["application/json;charset=utf-8"],"responses":{"200":{"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"},"description":""}}},"post":{"tags":["post"],"consumes":["application/json;charset=utf-8"],"produces":["application/json;charset=utf-8"],"parameters":[{"required":true,"schema":{"$ref":"#/definitions/User"},"in":"body","name":"body"}],"responses":{"400":{"description":"Invalid `body`"},"200":{"schema":{"$ref":"#/definitions/UserId"},"description":""}}}},"/{user_id}":{"get":{"tags":["get"],"produces":["application/json;charset=utf-8"],"parameters":[{"required":true,"in":"path","name":"user_id","type":"integer"}],"responses":{"404":{"description":"`user_id` not found"},"200":{"schema":{"$ref":"#/definitions/User"},"description":""}}}}},"definitions":{"User":{"required":["name","age"],"properties":{"name":{"type":"string"},"age":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"}},"type":"object"},"UserId":{"type":"integer"}},"tags":[{"name":"get","description":"GET operations"},{"name":"post","description":"POST operations"}]}
141+
-- {"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"],"properties":{"name":{"type":"string"},"age":{"maximum":9223372036854775807,"minimum":-9223372036854775808,"type":"integer"}},"type":"object"},"UserId":{"type":"integer"}}},"tags":[{"name":"get","description":"GET operations"},{"name":"post","description":"POST operations"}]}
142142
--
143143
-- This applies @\"get\"@ tag to the @GET@ endpoints and @\"post\"@ tag to the @POST@ endpoint of the User API.
144144

src/Servant/Swagger/Internal.hs

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
2323
import Data.Foldable (toList)
2424
import Data.Proxy
2525
import Data.Singletons.Bool
26-
import Data.Swagger hiding (Header)
26+
import Data.Swagger hiding (Header, contentType)
2727
import qualified Data.Swagger as Swagger
2828
import Data.Swagger.Declare
2929
import Data.Text (Text)
@@ -96,7 +96,7 @@ mkEndpoint :: forall a cs hs proxy method status.
9696
-> Swagger
9797
mkEndpoint path proxy
9898
= mkEndpointWithSchemaRef (Just ref) path proxy
99-
& definitions .~ defs
99+
& components.schemas .~ defs
100100
where
101101
(defs, ref) = runDeclare (declareSchemaRef (Proxy :: Proxy a)) mempty
102102

@@ -120,15 +120,15 @@ mkEndpointWithSchemaRef :: forall cs hs proxy method status a.
120120
mkEndpointWithSchemaRef mref path _ = mempty
121121
& paths.at path ?~
122122
(mempty & method ?~ (mempty
123-
& produces ?~ MimeList responseContentTypes
124123
& at code ?~ Inline (mempty
125-
& schema .~ mref
124+
& content .~ InsOrdHashMap.fromList
125+
[(t, mempty & schema .~ mref) | t <- responseContentTypes]
126126
& headers .~ responseHeaders)))
127127
where
128128
method = swaggerMethod (Proxy :: Proxy method)
129129
code = fromIntegral (natVal (Proxy :: Proxy status))
130130
responseContentTypes = allContentType (Proxy :: Proxy cs)
131-
responseHeaders = toAllResponseHeaders (Proxy :: Proxy hs)
131+
responseHeaders = Inline <$> toAllResponseHeaders (Proxy :: Proxy hs)
132132

133133
mkEndpointNoContentVerb :: forall proxy method.
134134
(SwaggerMethod method)
@@ -147,9 +147,9 @@ mkEndpointNoContentVerb path _ = mempty
147147
addParam :: Param -> Swagger -> Swagger
148148
addParam param = allOperations.parameters %~ (Inline param :)
149149

150-
-- | Add accepted content types to every operation in the spec.
151-
addConsumes :: [MediaType] -> Swagger -> Swagger
152-
addConsumes cs = allOperations.consumes %~ (<> Just (MimeList cs))
150+
-- | Add RequestBody to every operations in the spec.
151+
addRequestBody :: RequestBody -> Swagger -> Swagger
152+
addRequestBody rb = allOperations . requestBody ?~ Inline rb
153153

154154
-- | Format given text as inline code in Markdown.
155155
markdownCode :: Text -> Text
@@ -251,8 +251,8 @@ instance (KnownSymbol sym, ToParamSchema a, HasSwagger sub, KnownSymbol (FoldDes
251251
& name .~ tname
252252
& description .~ transDesc (reflectDescription (Proxy :: Proxy mods))
253253
& required ?~ True
254-
& schema .~ ParamOther (mempty
255-
& in_ .~ ParamPath
254+
& in_ .~ ParamPath
255+
& schema ?~ Inline (mempty
256256
& paramSchema .~ toParamSchema (Proxy :: Proxy a))
257257

258258
-- | Swagger Spec doesn't have a notion of CaptureAll, this instance is the best effort.
@@ -279,9 +279,9 @@ instance (KnownSymbol sym, ToParamSchema a, HasSwagger sub, SBoolI (FoldRequired
279279
& name .~ tname
280280
& description .~ transDesc (reflectDescription (Proxy :: Proxy mods))
281281
& required ?~ reflectBool (Proxy :: Proxy (FoldRequired mods))
282-
& schema .~ ParamOther sch
283-
sch = mempty
284282
& in_ .~ ParamQuery
283+
& schema ?~ Inline sch
284+
sch = mempty
285285
& paramSchema .~ toParamSchema (Proxy :: Proxy a)
286286

287287
instance (KnownSymbol sym, ToParamSchema a, HasSwagger sub) => HasSwagger (QueryParams sym a :> sub) where
@@ -292,17 +292,17 @@ instance (KnownSymbol sym, ToParamSchema a, HasSwagger sub) => HasSwagger (Query
292292
tname = Text.pack (symbolVal (Proxy :: Proxy sym))
293293
param = mempty
294294
& name .~ tname
295-
& schema .~ ParamOther sch
296-
sch = mempty
297295
& in_ .~ ParamQuery
296+
& schema ?~ Inline sch
297+
sch = mempty
298298
& paramSchema .~ pschema
299299
pschema = mempty
300300
#if MIN_VERSION_swagger2(2,4,0)
301301
& type_ ?~ SwaggerArray
302302
#else
303303
& type_ .~ SwaggerArray
304304
#endif
305-
& items ?~ SwaggerItemsPrimitive (Just CollectionMulti) (toParamSchema (Proxy :: Proxy a))
305+
& items ?~ SwaggerItemsObject (Inline $ mempty & paramSchema .~ toParamSchema (Proxy :: Proxy a))
306306

307307
instance (KnownSymbol sym, HasSwagger sub) => HasSwagger (QueryFlag sym :> sub) where
308308
toSwagger _ = toSwagger (Proxy :: Proxy sub)
@@ -312,9 +312,9 @@ instance (KnownSymbol sym, HasSwagger sub) => HasSwagger (QueryFlag sym :> sub)
312312
tname = Text.pack (symbolVal (Proxy :: Proxy sym))
313313
param = mempty
314314
& name .~ tname
315-
& schema .~ ParamOther (mempty
316-
& in_ .~ ParamQuery
317-
& allowEmptyValue ?~ True
315+
& in_ .~ ParamQuery
316+
& allowEmptyValue ?~ True
317+
& schema ?~ (Inline $ mempty
318318
& paramSchema .~ (toParamSchema (Proxy :: Proxy Bool)
319319
& default_ ?~ toJSON False))
320320

@@ -330,46 +330,40 @@ instance (KnownSymbol sym, ToParamSchema a, HasSwagger sub, SBoolI (FoldRequired
330330
& name .~ tname
331331
& description .~ transDesc (reflectDescription (Proxy :: Proxy mods))
332332
& required ?~ reflectBool (Proxy :: Proxy (FoldRequired mods))
333-
& schema .~ ParamOther (mempty
334-
& in_ .~ ParamHeader
333+
& in_ .~ ParamHeader
334+
& schema ?~ (Inline $ mempty
335335
& paramSchema .~ toParamSchema (Proxy :: Proxy a))
336336

337337
instance (ToSchema a, AllAccept cs, HasSwagger sub, KnownSymbol (FoldDescription mods)) => HasSwagger (ReqBody' mods cs a :> sub) where
338338
toSwagger _ = toSwagger (Proxy :: Proxy sub)
339-
& addParam param
340-
& addConsumes (allContentType (Proxy :: Proxy cs))
339+
& addRequestBody reqBody
341340
& addDefaultResponse400 tname
342-
& definitions %~ (<> defs)
341+
& components.schemas %~ (<> defs)
343342
where
344343
tname = "body"
345344
transDesc "" = Nothing
346345
transDesc desc = Just (Text.pack desc)
347346
(defs, ref) = runDeclare (declareSchemaRef (Proxy :: Proxy a)) mempty
348-
param = mempty
349-
& name .~ tname
347+
reqBody = (mempty :: RequestBody)
350348
& description .~ transDesc (reflectDescription (Proxy :: Proxy mods))
351-
& required ?~ True
352-
& schema .~ ParamBody ref
349+
& content .~ InsOrdHashMap.fromList [(t, mempty & schema ?~ ref) | t <- allContentType (Proxy :: Proxy cs)]
353350

354351
-- | This instance is an approximation.
355-
--
352+
--
356353
-- @since 1.1.7
357354
instance (ToSchema a, Accept ct, HasSwagger sub, KnownSymbol (FoldDescription mods)) => HasSwagger (StreamBody' mods fr ct a :> sub) where
358355
toSwagger _ = toSwagger (Proxy :: Proxy sub)
359-
& addParam param
360-
& addConsumes (toList (contentTypes (Proxy :: Proxy ct)))
356+
& addRequestBody reqBody
361357
& addDefaultResponse400 tname
362-
& definitions %~ (<> defs)
358+
& components.schemas %~ (<> defs)
363359
where
364360
tname = "body"
365361
transDesc "" = Nothing
366362
transDesc desc = Just (Text.pack desc)
367363
(defs, ref) = runDeclare (declareSchemaRef (Proxy :: Proxy a)) mempty
368-
param = mempty
369-
& name .~ tname
364+
reqBody = (mempty :: RequestBody)
370365
& description .~ transDesc (reflectDescription (Proxy :: Proxy mods))
371-
& required ?~ True
372-
& schema .~ ParamBody ref
366+
& content .~ InsOrdHashMap.fromList [(t, mempty & schema ?~ ref) | t <- toList $ contentTypes (Proxy :: Proxy ct)]
373367

374368
-- =======================================================================
375369
-- Below are the definitions that should be in Servant.API.ContentTypes
@@ -391,7 +385,7 @@ instance (KnownSymbol sym, ToParamSchema a) => ToResponseHeader (Header sym a) w
391385
toResponseHeader _ = (hname, Swagger.Header Nothing hschema)
392386
where
393387
hname = Text.pack (symbolVal (Proxy :: Proxy sym))
394-
hschema = toParamSchema (Proxy :: Proxy a)
388+
hschema = Just $ Inline $ mempty & paramSchema .~ toParamSchema (Proxy :: Proxy a)
395389

396390
class AllToResponseHeader hs where
397391
toAllResponseHeaders :: Proxy hs -> InsOrdHashMap HeaderName Swagger.Header

0 commit comments

Comments
 (0)