Safe Haskell | None |
---|---|
Language | Haskell2010 |
This module lets you get API docs for free. It lets you generate
an API
from the type that represents your API using docs
:
docs ::HasDocs
api =>Proxy
api ->API
Alternatively, if you wish to add one or more introductions to your
documentation, use docsWithIntros
:
docsWithIntros
::HasDocs
api => [DocIntro] ->Proxy
api ->API
You can then call markdown
on the API
value:
markdown
::API
-> String
or define a custom pretty printer:
yourPrettyDocs :: API
-> String -- or blaze-html's HTML, or ...
The only thing you'll need to do will be to implement some classes for your captures, get parameters and request or response bodies.
See example/greet.hs for an example.
- class HasDocs layout where
- docsFor :: Proxy layout -> (Endpoint, Action) -> DocOptions -> API
- docs :: HasDocs layout => Proxy layout -> API
- markdown :: API -> String
- docsWith :: HasDocs layout => DocOptions -> [DocIntro] -> ExtraInfo layout -> Proxy layout -> API
- docsWithIntros :: HasDocs layout => [DocIntro] -> Proxy layout -> API
- docsWithOptions :: HasDocs layout => Proxy layout -> DocOptions -> API
- newtype ExtraInfo layout = ExtraInfo (HashMap Endpoint Action)
- extraInfo :: (IsIn endpoint layout, HasLink endpoint, HasDocs endpoint) => Proxy endpoint -> Action -> ExtraInfo layout
- data DocOptions = DocOptions {
- _maxSamples :: Int
- defaultDocOptions :: DocOptions
- maxSamples :: Iso' DocOptions Int
- class ToSample a where
- toSamples :: Proxy a -> [(Text, a)]
- toSample :: forall a. ToSample a => Proxy a -> Maybe a
- noSamples :: [(Text, a)]
- singleSample :: a -> [(Text, a)]
- samples :: [a] -> [(Text, a)]
- sampleByteString :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a) => Proxy ctypes -> Proxy a -> [(MediaType, ByteString)]
- sampleByteStrings :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a) => Proxy ctypes -> Proxy a -> [(Text, MediaType, ByteString)]
- class ToParam t where
- toParam :: Proxy t -> DocQueryParam
- class ToCapture c where
- toCapture :: Proxy c -> DocCapture
- data Method
- data Endpoint
- path :: Lens' Endpoint [String]
- method :: Lens' Endpoint Method
- defEndpoint :: Endpoint
- data API
- apiIntros :: Lens' API [DocIntro]
- apiEndpoints :: Lens' API (HashMap Endpoint Action)
- emptyAPI :: API
- data DocCapture = DocCapture {
- _capSymbol :: String
- _capDesc :: String
- capSymbol :: Lens' DocCapture String
- capDesc :: Lens' DocCapture String
- data DocQueryParam = DocQueryParam {
- _paramName :: String
- _paramValues :: [String]
- _paramDesc :: String
- _paramKind :: ParamKind
- data ParamKind
- paramName :: Lens' DocQueryParam String
- paramValues :: Lens' DocQueryParam [String]
- paramDesc :: Lens' DocQueryParam String
- paramKind :: Lens' DocQueryParam ParamKind
- data DocNote = DocNote {
- _noteTitle :: String
- _noteBody :: [String]
- noteTitle :: Lens' DocNote String
- noteBody :: Lens' DocNote [String]
- data DocIntro = DocIntro {
- _introTitle :: String
- _introBody :: [String]
- introTitle :: Lens' DocIntro String
- introBody :: Lens' DocIntro [String]
- data Response = Response {
- _respStatus :: Int
- _respTypes :: [MediaType]
- _respBody :: [(Text, MediaType, ByteString)]
- _respHeaders :: [Header]
- respStatus :: Lens' Response Int
- respTypes :: Lens' Response [MediaType]
- respBody :: Lens' Response [(Text, MediaType, ByteString)]
- defResponse :: Response
- data Action
- captures :: Lens' Action [DocCapture]
- headers :: Lens' Action [Text]
- notes :: Lens' Action [DocNote]
- params :: Lens' Action [DocQueryParam]
- rqtypes :: Lens' Action [MediaType]
- rqbody :: Lens' Action [(MediaType, ByteString)]
- response :: Lens' Action Response
- defAction :: Action
- single :: Endpoint -> Action -> API
HasDocs
class and key functions
class HasDocs layout where
The class that abstracts away the impact of API combinators on documentation generation.
docsFor :: Proxy layout -> (Endpoint, Action) -> DocOptions -> API
HasDocs * Raw | |
(HasDocs * layout1, HasDocs * layout2) => HasDocs * ((:<|>) layout1 layout2) | The generated docs for |
(ToSample a, IsNonEmpty cts, AllMimeRender cts a, AllHeaderSamples [*] ls, GetHeaders (HList ls)) => HasDocs * (Get cts (Headers ls a)) | |
(ToSample a, IsNonEmpty cts, AllMimeRender cts a) => HasDocs * (Get cts a) | |
(ToSample a, IsNonEmpty cts, AllMimeRender cts a, AllHeaderSamples [*] ls, GetHeaders (HList ls)) => HasDocs * (Post cts (Headers ls a)) | |
(ToSample a, IsNonEmpty cts, AllMimeRender cts a) => HasDocs * (Post cts a) | |
(ToSample a, IsNonEmpty cts, AllMimeRender cts a, AllHeaderSamples [*] ls, GetHeaders (HList ls)) => HasDocs * (Delete cts (Headers ls a)) | |
(ToSample a, IsNonEmpty cts, AllMimeRender cts a) => HasDocs * (Delete cts a) | |
(ToSample a, IsNonEmpty cts, AllMimeRender cts a, AllHeaderSamples [*] ls, GetHeaders (HList ls)) => HasDocs * (Put cts (Headers ls a)) | |
(ToSample a, IsNonEmpty cts, AllMimeRender cts a) => HasDocs * (Put cts a) | |
HasDocs k sublayout => HasDocs * ((:>) * k Vault sublayout) | |
HasDocs k sublayout => HasDocs * ((:>) * k HttpVersion sublayout) | |
HasDocs k sublayout => HasDocs * ((:>) * k IsSecure sublayout) | |
HasDocs k sublayout => HasDocs * ((:>) * k RemoteHost sublayout) | |
(ToSample a, IsNonEmpty cts, AllMimeRender cts a, HasDocs k sublayout) => HasDocs * ((:>) * k (ReqBody * cts a) sublayout) | |
(KnownSymbol sym, ToParam * (QueryFlag sym), HasDocs k sublayout) => HasDocs * ((:>) * k (QueryFlag sym) sublayout) | |
(KnownSymbol sym, ToParam * (QueryParams k1 sym a), HasDocs k sublayout) => HasDocs * ((:>) * k (QueryParams k sym a) sublayout) | |
(KnownSymbol sym, ToParam * (QueryParam k1 sym a), HasDocs k sublayout) => HasDocs * ((:>) * k (QueryParam k sym a) sublayout) | |
(KnownSymbol sym, HasDocs k sublayout) => HasDocs * ((:>) * k (Header sym a) sublayout) | |
(KnownSymbol sym, ToCapture * (Capture k1 sym a), HasDocs k sublayout) => HasDocs * ((:>) * k (Capture k sym a) sublayout) |
|
(KnownSymbol path, HasDocs k sublayout) => HasDocs * ((:>) Symbol k path sublayout) |
docs :: HasDocs layout => Proxy layout -> API
Generate the docs for a given API that implements HasDocs
. This is the
default way to create documentation.
docs == docsWithOptions defaultDocOptions
Generating docs with extra information
docsWith :: HasDocs layout => DocOptions -> [DocIntro] -> ExtraInfo layout -> Proxy layout -> API
Generate documentation given some extra introductions (in the form of
DocInfo
) and some extra endpoint documentation (in the form of
ExtraInfo
.
The extra introductions will be prepended to the top of the documentation, before the specific endpoint documentation. The extra endpoint documentation will be "unioned" with the automatically generated endpoint documentation.
You are expected to build up the ExtraInfo with the Monoid instance and
extraInfo
.
If you only want to add an introduction, use docsWithIntros
.
docsWithIntros :: HasDocs layout => [DocIntro] -> Proxy layout -> API
Generate the docs for a given API that implements HasDocs
with with any
number of introduction(s)
docsWithOptions :: HasDocs layout => Proxy layout -> DocOptions -> API
Generate the docs for a given API that implements HasDocs
.
newtype ExtraInfo layout
Type of extra information that a user may wish to "union" with their documentation.
These are intended to be built using extraInfo. Multiple ExtraInfo may be combined with the monoid instance.
Monoid (ExtraInfo k a) |
extraInfo :: (IsIn endpoint layout, HasLink endpoint, HasDocs endpoint) => Proxy endpoint -> Action -> ExtraInfo layout
Create an ExtraInfo
that is garunteed to be within the given API layout.
The safety here is to ensure that you only add custom documentation to an endpoint that actually exists within your API.
extra :: ExtraInfo TestApi extra = extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $ defAction & headers <>~ ["unicorns"] & notes <>~ [ DocNote "Title" ["This is some text"] , DocNote "Second secton" ["And some more"] ]
data DocOptions
Documentation options.
DocOptions | |
|
Show DocOptions |
defaultDocOptions :: DocOptions
Default documentation options.
maxSamples :: Iso' DocOptions Int
Classes you need to implement for your types
class ToSample a where
The class that lets us display a sample input or output in the supported content-types when generating documentation for endpoints that either:
- expect a request body, or
- return a non empty response body
Example of an instance:
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} import Data.Aeson import Data.Text import GHC.Generics data Greet = Greet { _msg :: Text } deriving (Generic, Show) instance FromJSON Greet instance ToJSON Greet instance ToSample Greet where toSamples _ = singleSample g where g = Greet "Hello, haskeller!"
You can also instantiate this class using toSamples
instead of
toSample
: it lets you specify different responses along with
some context (as Text
) that explains when you're supposed to
get the corresponding response.
Nothing
toSamples :: Proxy a -> [(Text, a)]
ToSample Bool | |
ToSample Ordering | |
ToSample () | |
ToSample All | |
ToSample Any | |
ToSample a => ToSample [a] | |
ToSample a => ToSample (ZipList a) | |
ToSample a => ToSample (Dual a) | |
ToSample a => ToSample (Sum a) | |
ToSample a => ToSample (Product a) | |
ToSample a => ToSample (First a) | |
ToSample a => ToSample (Last a) | |
ToSample a => ToSample (Maybe a) | |
(ToSample a, ToSample b) => ToSample (Either a b) | |
(ToSample a, ToSample b) => ToSample (a, b) | |
ToSample a => ToSample (Const a b) | |
(ToSample a, ToSample b, ToSample c) => ToSample (a, b, c) | |
(ToSample a, ToSample b, ToSample c, ToSample d) => ToSample (a, b, c, d) | |
(ToSample a, ToSample b, ToSample c, ToSample d, ToSample e) => ToSample (a, b, c, d, e) | |
(ToSample a, ToSample b, ToSample c, ToSample d, ToSample e, ToSample f) => ToSample (a, b, c, d, e, f) | |
(ToSample a, ToSample b, ToSample c, ToSample d, ToSample e, ToSample f, ToSample g) => ToSample (a, b, c, d, e, f, g) |
toSample :: forall a. ToSample a => Proxy a -> Maybe a
Sample input or output (if there is at least one).
noSamples :: [(Text, a)]
No samples.
singleSample :: a -> [(Text, a)]
Single sample without description.
samples :: [a] -> [(Text, a)]
Samples without documentation.
sampleByteString :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a) => Proxy ctypes -> Proxy a -> [(MediaType, ByteString)]
Synthesise a sample value of a type, encoded in the specified media types.
sampleByteStrings :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a) => Proxy ctypes -> Proxy a -> [(Text, MediaType, ByteString)]
Synthesise a list of sample values of a particular type, encoded in the specified media types.
class ToParam t where
The class that helps us automatically get documentation for GET parameters.
Example of an instance:
instance ToParam (QueryParam "capital" Bool) where toParam _ = DocQueryParam "capital" ["true", "false"] "Get the greeting message in uppercase (true) or not (false). Default is false."
toParam :: Proxy t -> DocQueryParam
class ToCapture c where
The class that helps us automatically get documentation for URL captures.
Example of an instance:
instance ToCapture (Capture "name" Text) where toCapture _ = DocCapture "name" "name of the person to greet"
toCapture :: Proxy c -> DocCapture
ADTs to represent an API
data Method
Supported HTTP request methods
data Endpoint
An Endpoint
type that holds the path
and the method
.
Gets used as the key in the API
hashmap. Modify defEndpoint
or any Endpoint
value you want using the path
and method
lenses to tweak.
λ>defEndpoint
GET / λ>defEndpoint
&path
<>~
["foo"] GET /foo λ>defEndpoint
&path
<>~
["foo"] &method
.~
DocPOST
POST /foo
An Endpoint
whose path is `"/"` and whose method is DocGET
Here's how you can modify it:
λ>defEndpoint
GET / λ>defEndpoint
&path
<>~
["foo"] GET /foo λ>defEndpoint
&path
<>~
["foo"] &method
.~
DocPOST
POST /foo
data API
apiEndpoints :: Lens' API (HashMap Endpoint Action)
data DocCapture
A type to represent captures. Holds the name of the capture and a description.
Write a ToCapture
instance for your captured types.
DocCapture | |
|
Eq DocCapture | |
Ord DocCapture | |
Show DocCapture |
capSymbol :: Lens' DocCapture String
capDesc :: Lens' DocCapture String
data DocQueryParam
A type to represent a GET parameter from the Query String. Holds its name, the possible values (leave empty if there isn't a finite number of them), and a description of how it influences the output or behavior.
Write a ToParam
instance for your GET parameter types
DocQueryParam | |
|
Eq DocQueryParam | |
Ord DocQueryParam | |
Show DocQueryParam |
data ParamKind
Type of GET parameter:
- Normal corresponds to
QueryParam
, i.e your usual GET parameter - List corresponds to
QueryParams
, i.e GET parameters with multiple values - Flag corresponds to
QueryFlag
, i.e a value-less GET parameter
paramName :: Lens' DocQueryParam String
paramValues :: Lens' DocQueryParam [String]
paramDesc :: Lens' DocQueryParam String
paramKind :: Lens' DocQueryParam ParamKind
data DocNote
A type to represent extra notes that may be attached to an Action
.
This is intended to be used when writing your own HasDocs instances to add extra sections to your endpoint's documentation.
DocNote | |
|
data DocIntro
An introductory paragraph for your documentation. You can pass these to
docsWithIntros
.
DocIntro | |
|
introTitle :: Lens' DocIntro String
data Response
A type to represent an HTTP response. Has an Int
status, a list of
possible MediaType
s, and a list of example ByteString
response bodies.
Tweak defResponse
using the respStatus
, respTypes
and respBody
lenses if you want.
If you want to respond with a non-empty response body, you'll most likely
want to write a ToSample
instance for the type that'll be represented
as encoded data in the response.
Can be tweaked with three lenses.
λ> defResponse Response {_respStatus = 200, _respTypes = [], _respBody = []} λ> defResponse & respStatus .~ 204 & respBody .~ [("If everything goes well", "{ \"status\": \"ok\" }")] Response {_respStatus = 204, _respTypes = [], _respBody = [("If everything goes well", "{ \"status\": \"ok\" }")]}
Response | |
|
respStatus :: Lens' Response Int
Default response: status code 200, no response body.
Can be tweaked with two lenses.
λ> defResponse Response {_respStatus = 200, _respBody = Nothing} λ> defResponse & respStatus .~ 204 & respBody .~ Just "[]" Response {_respStatus = 204, _respBody = Just "[]"}
data Action
A datatype that represents everything that can happen at an endpoint, with its lenses:
- List of captures (
captures
) - List of GET parameters (
params
) - What the request body should look like, if any is requested (
rqbody
) - What the response should be if everything goes well (
response
)
You can tweak an Action
(like the default defAction
) with these lenses
to transform an action and add some information to it.
captures :: Lens' Action [DocCapture]
params :: Lens' Action [DocQueryParam]