What is Endpoint

The foundation of Linnet library is Endpoint data type:

data Endpoint (m :: * -> *) a =
    { 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.


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.

Related topics