Skip to content

Commit 1909e44

Browse files
authored
Swagger instances for UVerb (#127)
1 parent 2c0bf47 commit 1909e44

File tree

7 files changed

+151
-9
lines changed

7 files changed

+151
-9
lines changed

.travis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ script:
149149
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='swagger2 ==2.4.*' all
150150
# Constraint set swagger2-2.5
151151
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='swagger2 ==2.5.*' all
152-
# Constraint set servant-0.17
153-
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='servant == 0.17.*' all
152+
# Constraint set servant-0.18.1
153+
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='servant == 0.18.1' all
154154

155155
# REGENDATA ("0.9.20200121",["--config=cabal.haskell-ci","cabal.project"])
156156
# EOF

servant-swagger.cabal

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: servant-swagger
2-
version: 1.1.10
2+
version: 1.1.11
33
synopsis: Generate a Swagger/OpenAPI/OAS 2.0 specification for your servant API.
44
description:
55
Swagger is a project used to describe and document RESTful APIs. The core of the
@@ -82,7 +82,7 @@ library
8282
, http-media >=0.7.1.3 && <0.9
8383
, insert-ordered-containers >=0.2.1.0 && <0.3
8484
, lens >=4.17 && <4.20
85-
, servant >=0.17 && <0.19
85+
, servant >=0.18.1 && <0.19
8686
, singleton-bool >=0.1.4 && <0.2
8787
, swagger2 >=2.3.0.1 && <2.7
8888
, text >=1.2.3.0 && <1.3

src/Servant/Swagger/Internal.hs

+51
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module Servant.Swagger.Internal where
1616
import Prelude ()
1717
import Prelude.Compat
1818

19+
import Control.Applicative ((<|>))
1920
import Control.Lens
2021
import Data.Aeson
2122
import Data.HashMap.Strict.InsOrd (InsOrdHashMap)
@@ -184,6 +185,56 @@ instance SwaggerMethod 'OPTIONS where swaggerMethod _ = options
184185
instance SwaggerMethod 'HEAD where swaggerMethod _ = head_
185186
instance SwaggerMethod 'PATCH where swaggerMethod _ = patch
186187

188+
instance HasSwagger (UVerb method cs '[]) where
189+
toSwagger _ = mempty
190+
191+
-- | @since <TODO>
192+
instance
193+
{-# OVERLAPPABLE #-}
194+
( ToSchema a,
195+
HasStatus a,
196+
AllAccept cs,
197+
SwaggerMethod method,
198+
HasSwagger (UVerb method cs as)
199+
) =>
200+
HasSwagger (UVerb method cs (a ': as))
201+
where
202+
toSwagger _ =
203+
toSwagger (Proxy :: Proxy (Verb method (StatusOf a) cs a))
204+
`combineSwagger` toSwagger (Proxy :: Proxy (UVerb method cs as))
205+
where
206+
-- workaround for https://github.com/GetShopTV/swagger2/issues/218
207+
-- We'd like to juse use (<>) but the instances are wrong
208+
combinePathItem :: PathItem -> PathItem -> PathItem
209+
combinePathItem s t = PathItem
210+
{ _pathItemGet = _pathItemGet s <> _pathItemGet t
211+
, _pathItemPut = _pathItemPut s <> _pathItemPut t
212+
, _pathItemPost = _pathItemPost s <> _pathItemPost t
213+
, _pathItemDelete = _pathItemDelete s <> _pathItemDelete t
214+
, _pathItemOptions = _pathItemOptions s <> _pathItemOptions t
215+
, _pathItemHead = _pathItemHead s <> _pathItemHead t
216+
, _pathItemPatch = _pathItemPatch s <> _pathItemPatch t
217+
, _pathItemParameters = _pathItemParameters s <> _pathItemParameters t
218+
}
219+
220+
combineSwagger :: Swagger -> Swagger -> Swagger
221+
combineSwagger s t = Swagger
222+
{ _swaggerInfo = _swaggerInfo s <> _swaggerInfo t
223+
, _swaggerHost = _swaggerHost s <|> _swaggerHost t
224+
, _swaggerBasePath = _swaggerBasePath s <|> _swaggerBasePath t
225+
, _swaggerSchemes = _swaggerSchemes s <> _swaggerSchemes t
226+
, _swaggerConsumes = _swaggerConsumes s <> _swaggerConsumes t
227+
, _swaggerProduces = _swaggerProduces s <> _swaggerProduces t
228+
, _swaggerPaths = InsOrdHashMap.unionWith combinePathItem (_swaggerPaths s) (_swaggerPaths t)
229+
, _swaggerDefinitions = _swaggerDefinitions s <> _swaggerDefinitions t
230+
, _swaggerParameters = _swaggerParameters s <> _swaggerParameters t
231+
, _swaggerResponses = _swaggerResponses s <> _swaggerResponses t
232+
, _swaggerSecurityDefinitions = _swaggerSecurityDefinitions s <> _swaggerSecurityDefinitions t
233+
, _swaggerSecurity = _swaggerSecurity s <> _swaggerSecurity t
234+
, _swaggerTags = _swaggerTags s <> _swaggerTags t
235+
, _swaggerExternalDocs = _swaggerExternalDocs s <|> _swaggerExternalDocs t
236+
}
237+
187238
instance {-# OVERLAPPABLE #-} (ToSchema a, AllAccept cs, KnownNat status, SwaggerMethod method) => HasSwagger (Verb method status cs a) where
188239
toSwagger _ = toSwagger (Proxy :: Proxy (Verb method status cs (Headers '[] a)))
189240

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1-
{-# LANGUAGE ScopedTypeVariables #-}
2-
{-# OPTIONS_GHC -fno-warn-orphans #-}
1+
{-# OPTIONS_GHC -fno-warn-orphans #-}
2+
{-# LANGUAGE CPP #-}
3+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+
{-# LANGUAGE ScopedTypeVariables #-}
5+
{-# LANGUAGE StandaloneDeriving #-}
36
module Servant.Swagger.Internal.Orphans where
47

58
import Data.Proxy
69
(Proxy (..))
710
import Data.Swagger
811
import Servant.Types.SourceT
912
(SourceT)
13+
#if __GLASGOW_HASKELL__ >= 881
14+
import Servant.API (WithStatus(..))
15+
#endif
1016

1117
-- | Pretend that 'SourceT m a' is '[a]'.
1218
--
1319
-- @since 1.1.7
1420
--
1521
instance ToSchema a => ToSchema (SourceT m a) where
1622
declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy [a])
23+
24+
#if __GLASGOW_HASKELL__ >= 881
25+
-- @since 1.1.11
26+
deriving instance ToSchema a => ToSchema (WithStatus s a)
27+
#endif

src/Servant/Swagger/Internal/TypeLevel/API.hs

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
{-# LANGUAGE UndecidableInstances #-}
88
module Servant.Swagger.Internal.TypeLevel.API where
99

10-
import Data.Type.Bool (If)
1110
import GHC.Exts (Constraint)
1211
import Servant.API
1312

stack.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ packages:
44
- example/
55

66
extra-deps:
7-
- servant-0.18
8-
- servant-server-0.18
7+
- servant-0.18.1
8+
- servant-server-0.18.1

test/Servant/SwaggerSpec.hs

+81
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{-# LANGUAGE DeriveGeneric #-}
55
{-# LANGUAGE OverloadedStrings #-}
66
{-# LANGUAGE QuasiQuotes #-}
7+
{-# LANGUAGE TypeFamilies #-}
78
{-# LANGUAGE TypeOperators #-}
89
{-# LANGUAGE PackageImports #-}
910
module Servant.SwaggerSpec where
@@ -40,6 +41,7 @@ spec = describe "HasSwagger" $ do
4041
it "Todo API" $ checkAPI (Proxy :: Proxy TodoAPI) todoAPI
4142
it "Hackage API (with tags)" $ checkSwagger hackageSwaggerWithTags hackageAPI
4243
it "GetPost API (test subOperations)" $ checkSwagger getPostSwagger getPostAPI
44+
it "UVerb API" $ checkSwagger uverbSwagger uverbAPI
4345
it "Comprehensive API" $ do
4446
let _x = toSwagger comprehensiveAPI
4547
True `shouldBe` True -- type-level test
@@ -406,3 +408,82 @@ getPostAPI = [aesonQQ|
406408
}
407409
|]
408410

411+
-- =======================================================================
412+
-- UVerb API
413+
-- =======================================================================
414+
415+
data Lunch = Lunch {name :: String}
416+
deriving (Eq, Show, Generic)
417+
418+
instance ToSchema Lunch
419+
420+
instance HasStatus Lunch where
421+
type StatusOf Lunch = 200
422+
423+
data NoLunch = NoLunch
424+
deriving (Eq, Show, Generic)
425+
426+
instance ToSchema NoLunch
427+
428+
instance HasStatus NoLunch where
429+
type StatusOf NoLunch = 404
430+
431+
type UVerbAPI2 =
432+
"lunch" :> UVerb 'GET '[JSON] '[Lunch, NoLunch]
433+
434+
uverbSwagger :: Swagger
435+
uverbSwagger = toSwagger (Proxy :: Proxy UVerbAPI2)
436+
437+
uverbAPI :: Value
438+
uverbAPI =
439+
[aesonQQ|
440+
{
441+
"swagger": "2.0",
442+
"info": {
443+
"version": "",
444+
"title": ""
445+
},
446+
"definitions": {
447+
"Lunch": {
448+
"required": [
449+
"name"
450+
],
451+
"type": "object",
452+
"properties": {
453+
"name": {
454+
"type": "string"
455+
}
456+
}
457+
},
458+
"NoLunch": {
459+
"type": "string",
460+
"enum": [
461+
"NoLunch"
462+
]
463+
}
464+
},
465+
"paths": {
466+
"/lunch": {
467+
"get": {
468+
"responses": {
469+
"404": {
470+
"schema": {
471+
"$ref": "#/definitions/NoLunch"
472+
},
473+
"description": ""
474+
},
475+
"200": {
476+
"schema": {
477+
"$ref": "#/definitions/Lunch"
478+
},
479+
"description": ""
480+
}
481+
},
482+
"produces": [
483+
"application/json;charset=utf-8"
484+
]
485+
}
486+
}
487+
}
488+
}
489+
|]

0 commit comments

Comments
 (0)