What is Endpoint
The foundation of Linnet library is Endpoint
data type:
data Endpoint (m :: * -> *) a =
Endpoint
{ runEndpoint :: Input -> EndpointResult m a
, toString :: Text
}
In fact, it's an abstraction over function that takes a request wrapped
in Input
and either matches with some output m a
or doesn't match the request at all:
data EndpointResult (m :: * -> *) a
= Matched
{ matchedReminder :: Input
, matchedTrace :: Trace
, matchedOutput :: m (Output a)
}
| NotMatched
Endpoint combinators
Linnet exposes two operators that allow to combine endpoints using AND/OR logic.
Product operator: (//)
product :: Endpoint IO (HList '[])
product = get(p'"hello" // p'"world")
Operator //
is used to create product of two or more endpoints that all must sequentially
match to produce a result.
The resulting type is an endpoint of HList
containing types of all combined
endpoints.
It's particularly useful for matching path of request against an endpoint, but not limited by that.
Coproduct (union) operator: (|+|)
coproduct :: Endpoint IO (Coproduct (HList '[]) (Coproduct (HList '[Int]) CNil))
coproduct = get(p'"users") |+| delete(p'"users" // path @Int)
Operator |+|
is used to create coproduct (or union) of two or more endpoints where at least one endpoint
must match the request to produce a result.
The resulting type is an endpoint of Coproduct
containing types of all combined
endpoints.
Example above shows how to combine together endpoints of different HTTP methods together.
Transformation
While Linnet employs interesting data types such as HList
and Coproduct
to ensure type-safety of each endpoint used,
it's a rare situation to interact with them directly.
There are multiple options available to transform endpoints, and usually this transformation itself is a business logic of application.
Endpoint as Applicative
Both Functor
and Applicative
type classes are defined for Endpoint
, natively exposing simple transformations:
lifted :: Endpoint IO Int
lifted = pure 42
multiplied = (* 2) <$> lifted
Map over HList
But often there is a need to handle multiple parameters (product of them). It's still possible
to pattern match an HList
, but also quite unpractical. That's why there is ~>>
operator available:
nameAndSurname :: Endpoint IO Text
nameAndSurname = (path @Text // path @Text) ~>> (\name surname -> return $ ok (name `append` surname))
Example above demonstrates how lambda function of two parameters is applied to endpoint of HList '[Text, Text]
.
Resulting type of this lambda always should be some m (Output a)
.
Single-type endpoint
If endpoint is just a single type like Endpoint m Int
, beside usual fmap
there is an operator ~>
that allows to change default Ok
output to something different:
nameEndpoint :: Endpoint IO Text
nameEndpoint = get(param @Text "name") ~> (\name -> return $ created ("Name: " `append` name))
Strictly speaking, ~>
operator is just an inverted alias of mapOutputM
function.