Skip to content

UVerb support #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# This Travis job script has been generated by a script via
#
# haskell-ci 'cabal.project'
# haskell-ci 'cabal.project' '--config' 'cabal.haskell-ci'
#
# To regenerate the script (for example after adjusting tested-with) run
#
# haskell-ci regenerate
#
# For more information, see https://github.com/haskell-CI/haskell-ci
#
# version: 0.10.1
# version: 0.10.3
#
version: ~> 1.0
language: c
Expand All @@ -17,6 +17,9 @@ dist: xenial
git:
# whether to recursively clone submodules
submodules: false
branches:
only:
- master
cache:
directories:
- $HOME/.cabal/packages
Expand All @@ -33,8 +36,11 @@ before_cache:
- rm -rfv $CABALHOME/packages/head.hackage
jobs:
include:
- compiler: ghc-8.8.3
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.8.3","cabal-install-3.2"]}}
- compiler: ghc-8.10.2
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.10.2","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.8.4
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.8.4","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.6.5
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.6.5","cabal-install-3.2"]}}
Expand Down Expand Up @@ -96,10 +102,6 @@ install:
- echo 'package example' >> cabal.project
- "echo ' ghc-options: -Werror=missing-methods' >> cabal.project"
- |
echo "source-repository-package" >> cabal.project
echo " type: git" >> cabal.project
echo " location: https://github.com/biocad/openapi3/" >> cabal.project
echo " tag: bd9df532f2381c4b22fe86ef722715088f5cfa68" >> cabal.project
- "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(example|servant-openapi3)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
- cat cabal.project || true
- cat cabal.project.local || true
Expand Down Expand Up @@ -132,10 +134,6 @@ script:
- echo 'package example' >> cabal.project
- "echo ' ghc-options: -Werror=missing-methods' >> cabal.project"
- |
echo "source-repository-package" >> cabal.project
echo " type: git" >> cabal.project
echo " location: https://github.com/biocad/openapi3/" >> cabal.project
echo " tag: bd9df532f2381c4b22fe86ef722715088f5cfa68" >> cabal.project
- "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(example|servant-openapi3)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
- cat cabal.project || true
- cat cabal.project.local || true
Expand All @@ -155,6 +153,14 @@ script:
# Building without installed constraints for packages in global-db...
- rm -f cabal.project.local
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all
# Constraint sets
- rm -rf cabal.project.local
# Constraint set servant-0.17
- if [ $HCNUMVER -lt 81000 ] ; then ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='servant ==0.17.*' all ; fi
# Constraint set servant-0.18
- if [ $HCNUMVER -ge 80800 ] ; then ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='servant ==0.18' all ; fi
# Constraint set servant-0.18.1
- if [ $HCNUMVER -ge 80800 ] ; then ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='servant ==0.18.1' all ; fi

# REGENDATA ("0.10.1",["cabal.project"])
# REGENDATA ("0.10.3",["cabal.project","--config","cabal.haskell-ci"])
# EOF
13 changes: 13 additions & 0 deletions cabal.haskell-ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
branches: master

constraint-set servant-0.17
ghc: >= 8.0 && <8.10
constraints: servant ==0.17.*

constraint-set servant-0.18
ghc: >= 8.8 && <8.12
constraints: servant ==0.18

constraint-set servant-0.18.1
ghc: >= 8.8 && <8.12
constraints: servant ==0.18.1
5 changes: 0 additions & 5 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,3 @@ packages:
servant-openapi3.cabal,
example/example.cabal
tests: true

source-repository-package
type: git
location: https://github.com/biocad/openapi3/
tag: bd9df532f2381c4b22fe86ef722715088f5cfa68
3 changes: 2 additions & 1 deletion example/example.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ data-files:
tested-with:
GHC ==8.4.4
|| ==8.6.5
|| ==8.8.3
|| ==8.8.4
|| ==8.10.2

library
ghc-options: -Wall
Expand Down
5 changes: 3 additions & 2 deletions servant-openapi3.cabal
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: servant-openapi3
version: 2.0.0.1
version: 2.0.1.0
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
Expand Down Expand Up @@ -31,7 +31,8 @@ cabal-version: 1.18
tested-with:
GHC ==8.4.4
|| ==8.6.5
|| ==8.8.3
|| ==8.8.4
|| ==8.10.2

extra-source-files:
README.md
Expand Down
55 changes: 55 additions & 0 deletions src/Servant/OpenApi/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@
#if __GLASGOW_HASKELL__ >= 806
{-# LANGUAGE UndecidableInstances #-}
#endif
{-# OPTIONS_GHC -Wno-orphans #-}
module Servant.OpenApi.Internal where

import Prelude ()
import Prelude.Compat

#if MIN_VERSION_servant(0,18,1)
import Control.Applicative ((<|>))
#endif
import Control.Lens
import Data.Aeson
import Data.Foldable (toList)
Expand Down Expand Up @@ -183,6 +187,57 @@ instance OpenApiMethod 'OPTIONS where openApiMethod _ = options
instance OpenApiMethod 'HEAD where openApiMethod _ = head_
instance OpenApiMethod 'PATCH where openApiMethod _ = patch

#if MIN_VERSION_servant(0,18,1)
instance HasOpenApi (UVerb method cs '[]) where
toOpenApi _ = mempty

-- | @since <2.0.1.0>
instance
{-# OVERLAPPABLE #-}
( ToSchema a,
HasStatus a,
AllAccept cs,
OpenApiMethod method,
HasOpenApi (UVerb method cs as)
) =>
HasOpenApi (UVerb method cs (a ': as))
where
toOpenApi _ =
toOpenApi (Proxy :: Proxy (Verb method (StatusOf a) cs a))
`combineSwagger` toOpenApi (Proxy :: Proxy (UVerb method cs as))
where
-- workaround for https://github.com/GetShopTV/swagger2/issues/218
combinePathItem :: PathItem -> PathItem -> PathItem
combinePathItem s t = PathItem
{ _pathItemGet = _pathItemGet s <> _pathItemGet t
, _pathItemPut = _pathItemPut s <> _pathItemPut t
, _pathItemPost = _pathItemPost s <> _pathItemPost t
, _pathItemDelete = _pathItemDelete s <> _pathItemDelete t
, _pathItemOptions = _pathItemOptions s <> _pathItemOptions t
, _pathItemHead = _pathItemHead s <> _pathItemHead t
, _pathItemPatch = _pathItemPatch s <> _pathItemPatch t
, _pathItemTrace = _pathItemTrace s <> _pathItemTrace t
, _pathItemParameters = _pathItemParameters s <> _pathItemParameters t
, _pathItemSummary = _pathItemSummary s <|> _pathItemSummary t
, _pathItemDescription = _pathItemDescription s <|> _pathItemDescription t
, _pathItemServers = _pathItemServers s <> _pathItemServers t
}

combineSwagger :: OpenApi -> OpenApi -> OpenApi
combineSwagger s t = OpenApi
{ _openApiInfo = _openApiInfo s <> _openApiInfo t
, _openApiServers = _openApiServers s <> _openApiServers t
, _openApiPaths = InsOrdHashMap.unionWith combinePathItem (_openApiPaths s) (_openApiPaths t)
, _openApiComponents = _openApiComponents s <> _openApiComponents t
, _openApiSecurity = _openApiSecurity s <> _openApiSecurity t
, _openApiTags = _openApiTags s <> _openApiTags t
, _openApiExternalDocs = _openApiExternalDocs s <|> _openApiExternalDocs t
}

instance ToSchema a => ToSchema (WithStatus s a) where
declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a)
#endif

instance {-# OVERLAPPABLE #-} (ToSchema a, AllAccept cs, KnownNat status, OpenApiMethod method) => HasOpenApi (Verb method status cs a) where
toOpenApi _ = toOpenApi (Proxy :: Proxy (Verb method status cs (Headers '[] a)))

Expand Down
109 changes: 109 additions & 0 deletions test/Servant/OpenApiSpec.hs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE PackageImports #-}
#if MIN_VERSION_servant(0,18,1)
{-# LANGUAGE TypeFamilies #-}
#endif
module Servant.OpenApiSpec where

import Control.Lens
Expand Down Expand Up @@ -37,6 +41,9 @@ spec = describe "HasOpenApi" $ do
it "Comprehensive API" $ do
let _x = toOpenApi comprehensiveAPI
True `shouldBe` True -- type-level test
#if MIN_VERSION_servant(0,18,1)
it "UVerb API" $ checkOpenApi uverbOpenApi uverbAPI
#endif

main :: IO ()
main = hspec spec
Expand Down Expand Up @@ -418,3 +425,105 @@ getPostAPI = [aesonQQ|
}
|]

-- =======================================================================
-- UVerb API
-- =======================================================================

#if MIN_VERSION_servant(0,18,1)

data FisxUser = FisxUser {name :: String}
deriving (Eq, Show, Generic)

instance ToSchema FisxUser

instance HasStatus FisxUser where
type StatusOf FisxUser = 203

data ArianUser = ArianUser
deriving (Eq, Show, Generic)

instance ToSchema ArianUser

type UVerbAPI = "fisx" :> UVerb 'GET '[JSON] '[FisxUser, WithStatus 303 String]
:<|> "arian" :> UVerb 'POST '[JSON] '[WithStatus 201 ArianUser]

uverbOpenApi :: OpenApi
uverbOpenApi = toOpenApi (Proxy :: Proxy UVerbAPI)

uverbAPI :: Value
uverbAPI = [aesonQQ|
{
"openapi": "3.0.0",
"info": {
"version": "",
"title": ""
},
"components": {
"schemas": {
"ArianUser": {
"type": "string",
"enum": [
"ArianUser"
]
},
"FisxUser": {
"required": [
"name"
],
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
}
},
"paths": {
"/arian": {
"post": {
"responses": {
"201": {
"content": {
"application/json;charset=utf-8": {
"schema": {
"$ref": "#/components/schemas/ArianUser"
}
}
},
"description": ""
}
}
}
},
"/fisx": {
"get": {
"responses": {
"303": {
"content": {
"application/json;charset=utf-8": {
"schema": {
"type": "string"
}
}
},
"description": ""
},
"203": {
"content": {
"application/json;charset=utf-8": {
"schema": {
"$ref": "#/components/schemas/FisxUser"
}
}
},
"description": ""
}
}
}
}
}
}
|]

#endif