@hackage servant-hateoas0.3.2
HATEOAS extension for servant
Installation
Tested Compilers
Dependencies (9)
- aeson >=2.2.3 && <2.3
- base >=4.17.2 && <5
- constrained-some >=0.1.0 && <0.2
- http-media >=0.8.1 && <0.9
- http-types >=0.12.2 && <0.13
- servant >=0.20.2 && <0.21 Show all…
Dependents (0)
servant-hateoas
HATEOAS support for servant.
Infant state, highly experimental.
Find a motivating example further down this README.
What can we do already?
- Derive a layered HATEOAS-API and a server-implementation from an API, basically what has been touched on here.
- Derive a HATEOAS-API from an API by rewriting the API and its server-implementation
- Wrapping the response types of your API with Resource-Representations
- Automatically adding the self-link to every resource
- Adding custom links to resources via instances for type-class
ToResource
- Directly write a HATEOAS-API yourself
What can we do better?
Deriving the layered HATEOAS-API from your API does not require your API to be structured in a certain way.
However, for rewriting your API we need you to specify your server-implementation as an instance of class HasHandler (bad name, should be HasServer - exists already).
This currently makes it tricky for APIs which have shared path segments, e.g. "api" :> (UserApi :<|> AddressApi)
Therefore we currently need an instance on each flattened endpoint of the API, e.g. for "api :> UserApi" and "api :> AddressApi".
What's on the horizon?
A lot. There are plenty of opportunities.
- Merging the derived HATEOAS Layer-API with the rewritten HATEOAS API.
- Automatically adding links for servant-pagination
- Adding rich descriptions for Hypermedia-relations for content-types such as
application/prs.hal-forms+json - ...
Media-Types
application/hal+jsonapplication/vnd.collection+json: Work in progrressapplication/prs.hal-forms+json: Soon- Others: Maybe
Client usage with MimeUnrender is not yet supported.
Example
Suppose we have users and addresses, where each user has an address:
data User = User { usrId :: Int, addressId :: Int, income :: Double }
deriving stock (Generic, Show, Eq, Ord)
deriving anyclass (ToJSON)
data Address = Address { addrId :: Int, street :: String, city :: String }
deriving stock (Generic, Show, Eq, Ord)
deriving anyclass (ToJSON)
We need to define how their resource-representation looks like:
-- default just wrapps an address to a resource
instance ToResource res Address
-- add a link to the address-resource with the relation "address" for the user-resource
instance Resource res => ToResource res User where
toResource _ usr = addRel ("address", CompleteLink $ mkAddrLink $ addressId usr) $ wrap usr
where
mkAddrLink = safeLink (Proxy @AddressGetOne) (Proxy @AddressGetOne)
Further we define our API as usual:
type Api = UserApi :<|> AddressApi
type UserApi = UserGetOne :<|> UserGetAll :<|> UserGetAllCool
type UserGetOne = "api" :> "user" :> Capture "id" Int :> Get '[JSON] User
type UserGetAll = "api" :> "user" :> Get '[JSON] [User]
type AddressApi = AddressGetOne
type AddressGetOne = "api" :> "address" :> Capture "id" Int :> Get '[JSON] Address
Getting all the layers of the API in a HATEOAS way now is as simple as:
layerServer :: Server (Resourcify (MkLayers Api) (HAL JSON))
layerServer = getResourceServer (Proxy @Handler) (Proxy @(HAL JSON)) (Proxy @(MkLayers Api))
If we further want to rewrite our API to a HATEOAS-API, we need to define the server-implementation as an instance of HasHandler.
This is nothing but the usual servant-server implementation, just that the implementation is not floating around in the source code and instead is bound to a class instance.
instance HasHandler UserGetOne where
getHandler _ _ = \uId -> return $ User uId 1 1000
instance HasHandler UserGetAll where
getHandler _ _ = return [User 1 1 1000, User 2 2 2000, User 42 3 3000]
instance HasHandler AddressGetOne where
getHandler _ _ = \aId -> return $ Address aId "Foo St" "BarBaz"
Getting the rewritten HATEOAS-API and it's server-implementation is as simple as:
apiServer :: Server (Resourcify Api (HAL JSON))
apiServer = getResourceServer (Proxy @Handler) (Proxy @(HAL JSON)) (Proxy @Api)
For now apiServer and layerServer exist in isolation, but the goal is to merge them into one.
The complete example can be found here.
Contact information
Contributions, critics and bug reports are welcome!
Please feel free to contact me through GitHub.