packages feed

servant-openapi-hs 5.0.0 → 5.1.0

raw patch · 5 files changed

+90/−56 lines, 5 filesdep ~openapi-hsPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: openapi-hs

API changes (from Hackage documentation)

- Servant.OpenApi.Internal: instance (GHC.Internal.TypeLits.KnownSymbol desc, Data.OpenApi.Internal.Schema.ToSchema a) => Servant.OpenApi.Internal.IsSwaggerResponse (Servant.API.MultiVerb.Respond s desc a)
- Servant.OpenApi.Internal: instance (GHC.Internal.TypeLits.KnownSymbol desc, Data.OpenApi.Internal.Schema.ToSchema a, Servant.API.ContentTypes.Accept ct) => Servant.OpenApi.Internal.IsSwaggerResponse (Servant.API.MultiVerb.RespondAs ct s desc a)
- Servant.OpenApi.Internal: instance (Servant.OpenApi.Internal.AllToResponseHeader hs, Servant.OpenApi.Internal.IsSwaggerResponse r) => Servant.OpenApi.Internal.IsSwaggerResponse (Servant.API.MultiVerb.WithHeaders hs a r)
- Servant.OpenApi.Internal: instance (Servant.OpenApi.Internal.IsSwaggerResponse a, GHC.Internal.TypeNats.KnownNat (Servant.OpenApi.Internal.MultiVerbStatus a), Servant.OpenApi.Internal.IsSwaggerResponseList as) => Servant.OpenApi.Internal.IsSwaggerResponseList (a : as)
- Servant.OpenApi.Internal: instance (Servant.OpenApi.Internal.OpenApiMethod method, Servant.OpenApi.Internal.IsSwaggerResponseList as) => Servant.OpenApi.Internal.HasOpenApi (Servant.API.MultiVerb.MultiVerb method '() as r)
- Servant.OpenApi.Internal: instance (Servant.OpenApi.Internal.OpenApiMethod method, Servant.OpenApi.Internal.IsSwaggerResponseList as, Servant.API.ContentTypes.AllMime cs) => Servant.OpenApi.Internal.HasOpenApi (Servant.API.MultiVerb.MultiVerb method cs as r)
- Servant.OpenApi.Internal: instance GHC.Internal.TypeLits.KnownSymbol desc => Servant.OpenApi.Internal.IsSwaggerResponse (Servant.API.MultiVerb.RespondEmpty s desc)
- Servant.OpenApi.Internal: instance Servant.OpenApi.Internal.IsSwaggerResponseList '[]
+ Servant.OpenApi.Internal: instance (GHC.Internal.TypeLits.KnownSymbol desc, Data.OpenApi.Internal.Schema.ToSchema a) => Servant.OpenApi.Internal.IsSwaggerResponse '() (Servant.API.MultiVerb.Respond s desc a)
+ Servant.OpenApi.Internal: instance (GHC.Internal.TypeLits.KnownSymbol desc, Data.OpenApi.Internal.Schema.ToSchema a, Servant.API.ContentTypes.AllMime cs) => Servant.OpenApi.Internal.IsSwaggerResponse cs (Servant.API.MultiVerb.Respond s desc a)
+ Servant.OpenApi.Internal: instance forall k (cs :: k) a (as :: [*]). (Servant.OpenApi.Internal.IsSwaggerResponse cs a, GHC.Internal.TypeNats.KnownNat (Servant.OpenApi.Internal.MultiVerbStatus a), Servant.OpenApi.Internal.IsSwaggerResponseList cs as) => Servant.OpenApi.Internal.IsSwaggerResponseList cs (a : as)
+ Servant.OpenApi.Internal: instance forall k (cs :: k). Servant.OpenApi.Internal.IsSwaggerResponseList cs '[]
+ Servant.OpenApi.Internal: instance forall k (desc :: GHC.Types.Symbol) (cs :: k) (s :: GHC.Internal.TypeNats.Nat). GHC.Internal.TypeLits.KnownSymbol desc => Servant.OpenApi.Internal.IsSwaggerResponse cs (Servant.API.MultiVerb.RespondEmpty s desc)
+ Servant.OpenApi.Internal: instance forall k (desc :: GHC.Types.Symbol) a ct (cs :: k) (s :: GHC.Internal.TypeNats.Nat). (GHC.Internal.TypeLits.KnownSymbol desc, Data.OpenApi.Internal.Schema.ToSchema a, Servant.API.ContentTypes.Accept ct) => Servant.OpenApi.Internal.IsSwaggerResponse cs (Servant.API.MultiVerb.RespondAs ct s desc a)
+ Servant.OpenApi.Internal: instance forall k (hs :: [*]) (cs :: k) r a. (Servant.OpenApi.Internal.AllToResponseHeader hs, Servant.OpenApi.Internal.IsSwaggerResponse cs r) => Servant.OpenApi.Internal.IsSwaggerResponse cs (Servant.API.MultiVerb.WithHeaders hs a r)
+ Servant.OpenApi.Internal: instance forall k (method :: Network.HTTP.Types.Method.StdMethod) (cs :: k) (as :: [*]) r. (Servant.OpenApi.Internal.OpenApiMethod method, Servant.OpenApi.Internal.IsSwaggerResponseList cs as) => Servant.OpenApi.Internal.HasOpenApi (Servant.API.MultiVerb.MultiVerb method cs as r)
- Servant.OpenApi.Internal: class IsSwaggerResponse (a :: k)
+ Servant.OpenApi.Internal: class IsSwaggerResponse (cs :: k1) (a :: k)
- Servant.OpenApi.Internal: class IsSwaggerResponseList (as :: [Type])
+ Servant.OpenApi.Internal: class IsSwaggerResponseList (cs :: k) (as :: [Type])
- Servant.OpenApi.Internal: responseListSwagger :: IsSwaggerResponseList as => DeclareDefinition (InsOrdHashMap HttpStatusCode Response)
+ Servant.OpenApi.Internal: responseListSwagger :: IsSwaggerResponseList cs as => DeclareDefinition (InsOrdHashMap HttpStatusCode Response)
- Servant.OpenApi.Internal: responseSwagger :: IsSwaggerResponse a => DeclareDefinition Response
+ Servant.OpenApi.Internal: responseSwagger :: IsSwaggerResponse cs a => DeclareDefinition Response

Files

CHANGELOG.md view
@@ -1,3 +1,22 @@+5.1.0+-----++* Breaking: `MultiVerb` now documents each response alternative under its own+  content types. `RespondAs ct` keeps `ct`, `Respond` inherits the endpoint's+  media-type list, and `RespondEmpty` stays bodyless. Previously every+  alternative was rendered against a JSON default and then had the endpoint's+  content-type list reapplied wholesale, which discarded a `RespondAs`+  content type. Alternatives sharing a status code now retain every media type+  they declared instead of collapsing to one.+* Breaking: `Servant.OpenApi.Internal`'s `IsSwaggerResponse` and+  `IsSwaggerResponseList` take the enclosing `MultiVerb` content-type slot as an+  additional poly-kinded parameter, and the separate `'()` and `[Type]`+  `HasOpenApi (MultiVerb …)` instances are replaced by a single instance.+* Breaking: narrow the `openapi-hs` bound to `>=5.0 && <5.1`. Under PVP the+  breaking axis is `A.B`, so the previous `<6` promised compatibility with an+  `openapi-hs` 5.1 that the policy explicitly permits to break this package.+* Document the package's PVP versioning policy in the README.+ 5.0.0 ----- 
README.md view
@@ -119,6 +119,23 @@    nix run nixpkgs#vacuum-go -- lint -d openapi.json    ``` +## Versioning++This package follows the Haskell+[Package Versioning Policy (PVP)](https://pvp.haskell.org/). Versions have the+form `A.B.C`, optionally followed by further components, where **`A.B` together+is the major version**: it changes only when the public API breaks. A release+that only adds to the API bumps `C`, and one that does not touch the API bumps a+fourth component `D`. The PVP-recommended bound is therefore:++```cabal+build-depends: servant-openapi-hs >=5.0 && <5.1+```++Releases up to and including `5.0.0` numbered themselves like SemVer; `4.1.0` in+particular was additive-only and would be `4.0.1` under the policy above. From+`5.0.0` onward the policy holds.+ ## License  `servant-openapi-hs` retains the original **BSD-3-Clause** license of the upstream
servant-openapi-hs.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               servant-openapi-hs-version:            5.0.0+version:            5.1.0 synopsis:           Generate an OpenAPI 3.1 specification for your servant API. description:   [OpenAPI](https://spec.openapis.org/oas/v3.1.0) is a language-agnostic format@@ -65,7 +65,7 @@     , hspec                 >=2.6.0    && <2.12     , http-media            >=0.7.1.3  && <0.9     , lens                  >=5.3.3    && <5.4-    , openapi-hs            >=5.0      && <6+    , openapi-hs            >=5.0      && <5.1     , QuickCheck            >=2.9      && <2.17     , servant               >=0.17     && <0.21     , singleton-bool        >=0.1.4    && <0.2@@ -84,7 +84,7 @@     , base     , bytestring     , lens-    , openapi-hs          >=5.0 && <6+    , openapi-hs          >=5.0 && <5.1     , servant     , servant-openapi-hs     , text@@ -105,7 +105,7 @@     , hspec               >=2.6.0   && <2.12     , lens     , lens-aeson          >=1.0.2   && <1.3-    , openapi-hs          >=5.0     && <6+    , openapi-hs          >=5.0     && <5.1     , QuickCheck     , servant     , servant-openapi-hs
src/Servant/OpenApi/Internal.hs view
@@ -32,7 +32,6 @@ import Data.HashMap.Strict.InsOrd.Compat (InsOrdHashMap) import Data.HashMap.Strict.InsOrd.Compat qualified as InsOrdHashMap import Data.Kind (Type)-import Data.Maybe (listToMaybe) import Data.OpenApi hiding (Header, contentType) import Data.OpenApi qualified as OpenApi import Data.OpenApi.Declare@@ -575,17 +574,17 @@  -- | Produce the OpenAPI 'Response' object for one MultiVerb response -- alternative.-class IsSwaggerResponse a where+class IsSwaggerResponse (cs :: k) a where   responseSwagger :: DeclareDefinition Response  instance-  (AllToResponseHeader hs, IsSwaggerResponse r) =>-  IsSwaggerResponse (WithHeaders hs a r)+  (AllToResponseHeader hs, IsSwaggerResponse cs r) =>+  IsSwaggerResponse cs (WithHeaders hs a r)   where   responseSwagger =     fmap       (headers .~ fmap Inline (toAllResponseHeaders (Proxy @hs)))-      (responseSwagger @r)+      (responseSwagger @_ @cs @r)  -- | Build a 'Response' carrying a single schema, exposed under the given list -- of content types.@@ -606,23 +605,26 @@     cs = allMime (Proxy @cs)  instance+  (KnownSymbol desc, ToSchema a, AllMime cs) =>+  IsSwaggerResponse (cs :: [Type]) (Respond s desc a)+  where+  responseSwagger = simpleResponseSwagger @a @cs @desc++instance   (KnownSymbol desc, ToSchema a) =>-  IsSwaggerResponse (Respond s desc a)+  IsSwaggerResponse '() (Respond s desc a)   where-  -- Default the schema's content type to JSON: openapi needs *some* media type-  -- to hang the schema on, and the enclosing MultiVerb instance reapplies the-  -- real content-type list afterwards.   responseSwagger = simpleResponseSwagger @a @'[JSON] @desc  instance   (KnownSymbol desc, ToSchema a, Accept ct) =>-  IsSwaggerResponse (RespondAs (ct :: Type) s desc a)+  IsSwaggerResponse cs (RespondAs (ct :: Type) s desc a)   where   responseSwagger = simpleResponseSwagger @a @'[ct] @desc  instance   (KnownSymbol desc) =>-  IsSwaggerResponse (RespondEmpty s desc)+  IsSwaggerResponse cs (RespondEmpty s desc)   where   responseSwagger =     pure $@@ -631,25 +633,25 @@ -- | Fold a MultiVerb's list of response alternatives into a -- status-code-keyed map of 'Response' objects, merging alternatives that -- share a status code.-class IsSwaggerResponseList (as :: [Type]) where+class IsSwaggerResponseList (cs :: k) (as :: [Type]) where   responseListSwagger :: DeclareDefinition (InsOrdHashMap HttpStatusCode Response) -instance IsSwaggerResponseList '[] where+instance IsSwaggerResponseList cs '[] where   responseListSwagger = pure mempty  instance-  ( IsSwaggerResponse a+  ( IsSwaggerResponse cs a   , KnownNat (MultiVerbStatus a)-  , IsSwaggerResponseList as+  , IsSwaggerResponseList cs as   ) =>-  IsSwaggerResponseList (a ': as)+  IsSwaggerResponseList cs (a ': as)   where   responseListSwagger =     InsOrdHashMap.insertWith       combineResponseSwagger       (fromIntegral (natVal (Proxy @(MultiVerbStatus a))))-      <$> responseSwagger @a-      <*> responseListSwagger @as+      <$> responseSwagger @_ @cs @a+      <*> responseListSwagger @_ @cs @as  -- | Merge two responses that share a status code: concatenate descriptions and -- union their content maps.@@ -682,11 +684,9 @@           <>~ (s2 ^. properties . ix "label" . _Inline . enum_ . _Just)   | otherwise = s1 --- | MultiVerb whose request-mime-types slot is @'()@ (no content types given):--- the response schemas keep their JSON default. instance-  (OpenApiMethod method, IsSwaggerResponseList as) =>-  HasOpenApi (MultiVerb method '() as r)+  (OpenApiMethod method, IsSwaggerResponseList cs as) =>+  HasOpenApi (MultiVerb method cs as r)   where   toOpenApi _ =     mempty@@ -696,34 +696,6 @@              (mempty & responses . responses .~ refResps))     where       method = openApiMethod (Proxy @method)-      (schemaDefs, resps) = runDeclare (responseListSwagger @as) mempty+      (schemaDefs, resps) = runDeclare (responseListSwagger @_ @cs @as) mempty       refResps = Inline <$> resps---- | MultiVerb with an explicit content-type list @cs@: reapply @cs@ to every--- response's single schema.-instance-  (OpenApiMethod method, IsSwaggerResponseList as, AllMime cs) =>-  HasOpenApi (MultiVerb method (cs :: [Type]) as r)-  where-  toOpenApi _ =-    mempty-      & components . schemas <>~ schemaDefs-      & paths . at "/" ?~-          (mempty & method ?~-             (mempty & responses . responses .~ refResps))-    where-      method = openApiMethod (Proxy @method)-      cs = allMime (Proxy @cs)-      (schemaDefs, resps) = runDeclare (responseListSwagger @as) mempty-      -- Each alternative already holds a single (JSON-defaulted) schema; swap in-      -- the MultiVerb's own content types, reusing that one schema for each.-      addMime :: Response -> Response-      addMime resp =-        resp-          & content %~-              ( foldMap (\c -> InsOrdHashMap.fromList $ (,c) <$> cs)-                  . listToMaybe-                  . toList-              )-      refResps = Inline . addMime <$> resps #endif
test/Servant/OpenApiSpec.hs view
@@ -70,6 +70,26 @@       codes "201" `shouldSatisfy` has _Just       codes "202" `shouldSatisfy` has _Just       codes "204" `shouldSatisfy` has _Just+    it "MultiVerb documents each alternative under its own content type" $ do+      let doc = toJSON coverageOpenApi+          keys k = doc ^.. key "paths" . key "/cov" . key "get" . key "responses"+                         . key k . key "content" . members . asIndex+      keys "200" `shouldMatchList` ["application/json", "application/json;charset=utf-8"]+      keys "201" `shouldMatchList` ["text/plain;charset=utf-8"]+      keys "202" `shouldMatchList` ["application/json", "application/json;charset=utf-8"]+      keys "204" `shouldMatchList` []+      (doc ^? key "paths" . key "/cov" . key "get" . key "responses"+                . key "202" . key "headers" . key "X-Trace")+        `shouldSatisfy` has _Just+    it "MultiVerb preserves every media type when alternatives share a status" $ do+      let doc = toJSON coverageOpenApi+          keys = doc ^.. key "paths" . key "/shared" . key "get" . key "responses"+                          . key "200" . key "content" . members . asIndex+      keys `shouldMatchList`+        [ "application/json"+        , "application/json;charset=utf-8"+        , "text/plain;charset=utf-8"+        ] #endif    -- Layer 1: every generated document round-trips through openapi-hs's@@ -731,8 +751,14 @@    , WithHeaders '[Servant.API.Header "X-Trace" Text] Int (Respond 202 "Accepted" Int)    ] +type SharedContentResponses =+  '[ RespondAs PlainText 200 "Plain text form" String+   , RespondAs JSON 200 "JSON form" Int+   ]+ type CoverageAPI =-  "cov" :> MultiVerb 'GET '[JSON] CoverageResponses ()+  ("cov" :> MultiVerb 'GET '[JSON] CoverageResponses ())+    :<|> ("shared" :> MultiVerb 'GET '[JSON] SharedContentResponses ())  coverageOpenApi :: OpenApi coverageOpenApi = toOpenApi (Proxy :: Proxy CoverageAPI)