openapi-hs-4.0.0: examples/hackage.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Emit a representative API's OpenAPI 3.1 document as JSON to stdout.
--
-- Besides serving as a runnable example, this feeds Layer-3 (external,
-- authoritative) validation:
--
-- > cabal run example > openapi.json
-- > nix run nixpkgs#vacuum-go -- lint -d openapi.json
--
-- The document is deliberately a complete OpenAPI 3.1 contract — it carries
-- @info@ (title/version/description), a @server@, top-level @tags@, and a
-- unique @operationId@ per operation — so an external linter has a realistic
-- document to validate rather than a bare skeleton.
module Main where
import Control.Lens
import Data.Aeson
import Data.ByteString.Lazy.Char8 qualified as BL
import Data.OpenApi
import Data.OpenApi.Declare
import Data.OpenApi.Lens
import Data.OpenApi.Operation
import Data.Proxy
import Data.Text (Text)
import GHC.Generics
type Username = Text
data UserSummary = UserSummary
{ summaryUsername :: Username,
summaryUserid :: Int
}
deriving stock (Generic)
instance ToSchema UserSummary where
declareNamedSchema _ = do
usernameSchema <- declareSchemaRef (Proxy :: Proxy Username)
useridSchema <- declareSchemaRef (Proxy :: Proxy Int)
return $
NamedSchema (Just "UserSummary") $
mempty
& type_ ?~ OpenApiTypeSingle OpenApiObject
& properties
.~ [ ("summaryUsername", usernameSchema),
("summaryUserid", useridSchema)
]
& required
.~ [ "summaryUsername",
"summaryUserid"
]
type Group = Text
data UserDetailed = UserDetailed
{ username :: Username,
userid :: Int,
groups :: [Group]
}
deriving stock (Generic)
deriving anyclass (ToSchema)
newtype Package = Package {packageName :: Text}
deriving stock (Generic)
deriving anyclass (ToSchema)
hackageOpenApi :: OpenApi
hackageOpenApi = spec & components . schemas .~ defs
where
(defs, spec) = runDeclare declareHackageOpenApi mempty
declareHackageOpenApi :: Declare (Definitions Schema) OpenApi
declareHackageOpenApi = do
-- param schemas
let usernameParamSchema = toParamSchema (Proxy :: Proxy Username)
-- responses
userSummaryResponse <- declareResponse "application/json" (Proxy :: Proxy UserSummary)
userDetailedResponse <- declareResponse "application/json" (Proxy :: Proxy UserDetailed)
packagesResponse <- declareResponse "application/json" (Proxy :: Proxy [Package])
return $
mempty
& info . title .~ "Hackage API"
& info . version .~ "1.0.0"
& info . description ?~ "A small, representative Hackage-style read API."
& servers .~ ["https://hackage.haskell.org"]
& tags
.~ [ Tag "users" (Just "User lookup operations") Nothing,
Tag "packages" (Just "Package operations") Nothing
]
& paths
.~ [ ( "/users",
mempty
& get
?~ ( mempty
& operationId ?~ "getUsers"
& tags .~ ["users"]
& at 200 ?~ Inline userSummaryResponse
)
),
( "/user/{username}",
mempty
& get
?~ ( mempty
& operationId ?~ "getUser"
& tags .~ ["users"]
& parameters
.~ [ Inline $
mempty
& name .~ "username"
& required ?~ True
& in_ .~ ParamPath
& schema ?~ Inline usernameParamSchema
]
& at 200 ?~ Inline userDetailedResponse
)
),
( "/packages",
mempty
& get
?~ ( mempty
& operationId ?~ "getPackages"
& tags .~ ["packages"]
& at 200 ?~ Inline packagesResponse
)
)
]
main :: IO ()
main = BL.putStrLn (encode hackageOpenApi)