packages feed

openapi-hs-4.1.0: src/Data/OpenApi.hs

-- |
-- Module:      Data.OpenApi
-- Maintainer:  Nadeem Bitar <nadeem@gmail.com>
-- Stability:   experimental
--
-- OpenAPI 3.1 data model.
--
-- This library supports OpenAPI Specification version 3.1.x (which adopts the
-- JSON Schema 2020-12 dialect), decoding and encoding 3.1 API specifications as
-- well as manipulating them. The specification is available at
-- <https://spec.openapis.org/oas/v3.1.0>.
--
-- To bring an existing OpenAPI 3.0 document forward, rewrite the parsed JSON with
-- the helpers in "Data.OpenApi.Migration" before decoding it into these 3.1 types
-- (see @MIGRATION_3.0_TO_3.1.md@).
module Data.OpenApi
  ( -- * How to use this library
    -- $howto

    -- ** @'Monoid'@ instances
    -- $monoids

    -- ** Lenses and prisms
    -- $lens

    -- ** Schema specification
    -- $schema

    -- ** Manipulation
    -- $manipulation

    -- ** Validation
    -- $validation

    -- * Re-exports
    module Data.OpenApi.Lens,
    module Data.OpenApi.Optics,
    module Data.OpenApi.Operation,
    module Data.OpenApi.ParamSchema,
    module Data.OpenApi.Schema,
    module Data.OpenApi.Schema.Validation,

    -- * OpenAPI specification
    OpenApi (..),
    OpenApiMajorVersion (..),
    detectVersion,
    Server (..),
    ServerVariable (..),
    Components (..),

    -- ** Info types
    Info (..),
    Contact (..),
    License (..),

    -- ** PathItem
    PathItem (..),

    -- ** Operations
    Operation (..),
    Tag (..),
    TagName,

    -- ** Types and formats
    OpenApiType (..),
    OpenApiTypeValue (..),
    singleType,
    Format,
    Definitions,
    Style (..),

    -- ** Parameters
    Param (..),
    ParamLocation (..),
    ParamName,
    Header (..),
    HeaderName,
    Example (..),
    RequestBody (..),
    MediaTypeObject (..),
    Encoding (..),

    -- ** Schemas
    Schema (..),
    NamedSchema (..),
    OpenApiItems (..),
    Xml (..),
    Pattern,
    AdditionalProperties (..),
    Discriminator (..),

    -- ** Responses
    Responses (..),
    Response (..),
    HttpStatusCode (..),
    StatusCodeRange (..),
    Link (..),
    Callback (..),

    -- ** Security
    SecurityScheme (..),
    SecuritySchemeType (..),
    HttpSchemeType (..),
    SecurityDefinitions (..),
    SecurityRequirement (..),

    -- *** API key
    ApiKeyParams (..),
    ApiKeyLocation (..),

    -- *** OAuth2
    OAuth2Flows (..),
    OAuth2Flow (..),
    OAuth2ImplicitFlow (..),
    OAuth2PasswordFlow (..),
    OAuth2ClientCredentialsFlow (..),
    OAuth2AuthorizationCodeFlow (..),
    AuthorizationURL,
    TokenURL,

    -- ** External documentation
    ExternalDocs (..),

    -- ** References
    Reference (..),
    Referenced (..),

    -- ** Miscellaneous
    MimeList (..),
    URL (..),
  )
where

import Data.OpenApi.Internal
import Data.OpenApi.Lens
import Data.OpenApi.Operation
import Data.OpenApi.Optics ()
import Data.OpenApi.ParamSchema
import Data.OpenApi.Schema
import Data.OpenApi.Schema.Validation

-- $setup
-- >>> import Control.Lens
-- >>> import Data.Aeson
-- >>> import Data.Monoid
-- >>> import Data.Proxy
-- >>> import GHC.Generics
-- >>> import qualified Data.ByteString.Lazy.Char8 as BSL
-- >>> import Data.OpenApi.Internal
-- >>> import Data.OpenApi.Internal.Schema
-- >>> import qualified Data.HashMap.Strict.InsOrd.Compat as IOHM
-- >>> import Data.OpenApi.Internal.Utils
-- >>> import Data.OpenApi.Lens
-- >>> :set -XDeriveGeneric
-- >>> :set -XOverloadedStrings
-- >>> :set -fno-warn-missing-methods

-- $howto
--
-- This section explains how to use this library to work with the OpenAPI specification.

-- $monoids
--
-- Virtually all types representing the OpenAPI specification have @'Monoid'@ instances.
-- The @'Monoid'@ type class provides two methods — @'mempty'@ and @'mappend'@.
--
-- In this library you can use @'mempty'@ for a default/empty value. For instance:
--
-- >>> BSL.putStrLn $ encodePretty (mempty :: OpenApi)
-- {
--     "components": {},
--     "info": {
--         "title": "",
--         "version": ""
--     },
--     "openapi": "3.1.0",
--     "paths": {}
-- }
--
-- As you can see some spec properties (e.g. @"version"@) are there even when the spec is empty.
-- That is because these properties are actually required ones.
--
-- You /should/ always override the default (empty) value for these properties,
-- although it is not strictly necessary:
--
-- >>> BSL.putStrLn $ encodePretty mempty { _infoTitle = "Todo API", _infoVersion = "1.0" }
-- {
--     "title": "Todo API",
--     "version": "1.0"
-- }
--
-- You can merge two values using @'mappend'@ or its infix version @('<>')@:
--
-- >>> BSL.putStrLn $ encodePretty $ mempty { _infoTitle = "Todo API" } <> mempty { _infoVersion = "1.0" }
-- {
--     "title": "Todo API",
--     "version": "1.0"
-- }

--
-- This can be useful for combining specifications of endpoints into a whole API specification:
--
-- @
-- \-\- /account subAPI specification
-- accountAPI :: OpenApi
--
-- \-\- /task subAPI specification
-- taskAPI :: OpenApi
--
-- \-\- while API specification is just a combination
-- \-\- of subAPIs' specifications
-- api :: OpenApi
-- api = accountAPI <> taskAPI
-- @

-- $lens
--
-- Note: if you're working with the <https://hackage.haskell.org/package/optics optics> library, take a look at "Data.OpenApi.Optics".
--
-- Since @'OpenApi'@ has a fairly complex structure, lenses and prisms are used
-- to work comfortably with it. In combination with @'Monoid'@ instances, lenses
-- make it fairly simple to construct/modify any part of the specification:
--
-- >>> :{
-- BSL.putStrLn $ encodePretty $ (mempty :: OpenApi)
--   & components . schemas .~ IOHM.fromList [ ("User", mempty & type_ ?~ OpenApiTypeSingle OpenApiString) ]
--   & paths .~
--     IOHM.fromList [ ("/user", mempty & get ?~ (mempty
--         & at 200 ?~ ("OK" & _Inline.content.at "application/json" ?~ (mempty & schema ?~ Ref (Reference "User")))
--         & at 404 ?~ "User info not found")) ]
-- :}
-- {
--     "components": {
--         "schemas": {
--             "User": {
--                 "type": "string"
--             }
--         }
--     },
--     "info": {
--         "title": "",
--         "version": ""
--     },
--     "openapi": "3.1.0",
--     "paths": {
--         "/user": {
--             "get": {
--                 "responses": {
--                     "200": {
--                         "content": {
--                             "application/json": {
--                                 "schema": {
--                                     "$ref": "#/components/schemas/User"
--                                 }
--                             }
--                         },
--                         "description": "OK"
--                     },
--                     "404": {
--                         "description": "User info not found"
--                     }
--                 }
--             }
--         }
--     }
-- }
--
-- In the snippet above we declare an API with a single path @/user@. This path provides method @GET@
-- which produces @application/json@ output. It should respond with code @200@ and body specified
-- by schema @User@ which is defined in the @'components'@ property of the OpenAPI specification.
-- Alternatively it may respond with code @404@ meaning that user info is not found.
--
-- For convenience, @openapi-hs@ uses /classy field lenses/. It means that
-- field accessor names can be overloaded for different types. One such
-- common field is @'description'@. Many components of an OpenAPI specification
-- can have descriptions, and you can use the same name for them:
--
-- >>> BSL.putStrLn $ encodePretty $ (mempty :: Response) & description .~ "No content"
-- {
--     "description": "No content"
-- }
-- >>> :{
-- BSL.putStrLn $ encodePretty $ (mempty :: Schema)
--   & type_       ?~ OpenApiTypeSingle OpenApiBoolean
--   & description ?~ "To be or not to be"
-- :}
-- {
--     "description": "To be or not to be",
--     "type": "boolean"
-- }
--
-- Additionally, to simplify working with @'Response'@, both @'Operation'@ and @'Responses'@
-- have direct access to it via @at code@. Example:
--
-- >>> :{
-- BSL.putStrLn $ encodePretty $ (mempty :: Operation)
--   & at 404 ?~ "Not found"
-- :}
-- {
--     "responses": {
--         "404": {
--             "description": "Not found"
--         }
--     }
-- }
--
-- You might've noticed that @'type_'@ has an extra underscore in its name
-- compared to, say, @'description'@ field accessor.
-- This is because @type@ is a keyword in Haskell.
-- A few other field accessors are modified in this way:
--
--    - @'in_'@, @'type_'@, @'default_'@ (as keywords);
--    - @'maximum_'@, @'minimum_'@, @'head_'@ (as conflicting with @Prelude@);
--    - @'enum_'@ (as conflicting with @Control.Lens@).

-- $schema
--
-- @'Schema'@ is the core type for data model specification: an OpenAPI 3.1
-- Schema Object, which is a JSON Schema 2020-12 document. Its @type@ and @items@
-- keywords are described by @'OpenApiType'@ and @'OpenApiItems'@.
--
-- The same @'Schema'@ type covers both primitive endpoint parameters and
-- request/response bodies; unlike OpenAPI 3.0 and earlier, 3.1 has no separate
-- parameter-schema type.
--
-- In most cases you will have a Haskell data type for which you would like to
-- define a corresponding schema. To facilitate this use case
-- @openapi-hs@ provides two classes for schema encoding.
-- Both these classes provide means to encode /types/ as OpenAPI /schemas/.
--
-- @'ToParamSchema'@ is intended to be used for primitive API endpoint parameters,
-- such as query parameters, headers and URL path pieces.
-- Its corresponding value-encoding class is @ToHttpApiData@ (from @http-api-data@ package).
--
-- @'ToSchema'@ is used for request and response bodies and mostly differ from
-- primitive parameters by allowing objects/mappings in addition to primitive types and arrays.
-- Its corresponding value-encoding class is @ToJSON@ (from @aeson@ package).
--
-- While lenses and prisms make it easy to define schemas, it might be that you don't need to:
-- @'ToSchema'@ and @'ToParamSchema'@ classes both have default @Generic@-based implementations!
--
-- @'ToSchema'@ default implementation is also aligned with @ToJSON@ default implementation with
-- the only difference being for sum encoding. @ToJSON@ defaults sum encoding to @defaultTaggedObject@,
-- while @'ToSchema'@ defaults to something which corresponds to @ObjectWithSingleField@. This is due to
-- @defaultTaggedObject@ behavior being hard to specify in OpenAPI.
--
-- Here's an example showing @ToJSON@–@'ToSchema'@ correspondance:
--
-- >>> data Person = Person { name :: String, age :: Integer } deriving Generic
-- >>> instance ToJSON Person
-- >>> instance ToSchema Person
-- >>> BSL.putStrLn $ encodePretty (Person "David" 28)
-- {
--     "age": 28,
--     "name": "David"
-- }
-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy Person)
-- {
--     "properties": {
--         "age": {
--             "type": "integer"
--         },
--         "name": {
--             "type": "string"
--         }
--     },
--     "required": [
--         "name",
--         "age"
--     ],
--     "type": "object"
-- }
--
-- This package implements the OpenAPI 3.1 spec, which supports @oneOf@ in schemas, allowing any sum types
-- to be faithfully represented. All sum encodings supported by @aeson@ are supported here as well, with
-- an exception of 'Data.Aeson.TwoElemArray', since OpenAPI spec does not support heterogeneous arrays.
--
-- An example with 'Data.Aeson.TaggedObject' encoding:
--
-- >>> data Error = ErrorNoUser { userId :: Int } | ErrorAccessDenied { requiredPermission :: String } deriving Generic
-- >>> instance ToJSON Error
-- >>> instance ToSchema Error
-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy Error)
-- {
--     "oneOf": [
--         {
--             "properties": {
--                 "tag": {
--                     "enum": [
--                         "ErrorNoUser"
--                     ],
--                     "type": "string"
--                 },
--                 "userId": {
--                     "maximum": 9223372036854775807,
--                     "minimum": -9223372036854775808,
--                     "type": "integer"
--                 }
--             },
--             "required": [
--                 "userId",
--                 "tag"
--             ],
--             "title": "ErrorNoUser",
--             "type": "object"
--         },
--         {
--             "properties": {
--                 "requiredPermission": {
--                     "type": "string"
--                 },
--                 "tag": {
--                     "enum": [
--                         "ErrorAccessDenied"
--                     ],
--                     "type": "string"
--                 }
--             },
--             "required": [
--                 "requiredPermission",
--                 "tag"
--             ],
--             "title": "ErrorAccessDenied",
--             "type": "object"
--         }
--     ]
-- }

-- $manipulation
-- Sometimes you have to work with an imported or generated @'OpenApi'@.
-- For instance, <https://hackage.haskell.org/package/servant-openapi3 servant-openapi3> generates a basic @'OpenApi'@
-- for a type-level servant API.
--
-- Lenses and prisms can be used to manipulate such specification to add additional information, tags, extra responses, etc.
-- To facilitate common needs, @"Data.OpenApi.Operation"@ module provides useful helpers.

-- $validation
-- While @'ToParamSchema'@ and @'ToSchema'@ provide means to easily obtain schemas for Haskell types,
-- there is no static mechanism to ensure those instances correspond to the @ToHttpApiData@ or @ToJSON@ instances.
--
-- @"Data.OpenApi.Schema.Validation"@ addresses @ToJSON@/@'ToSchema'@ validation.