Abstracting similar APIs (APIFor/serverFor)

18 views
Skip to first unread message

Samuel Feitosa

unread,
Nov 21, 2022, 3:31:13 PM11/21/22
to haskell-servant
Hi people!

I'm trying to code an example from the servant documentation, where the APIs for user and products are abstracted using a generic approach.


It says:

-- API for values of type 'a'
-- indexed by values of type 'i'
type APIFor a i = ...

I'm having difficult to get the types right when implementing the serverFor function.

Does anyone have a complete/running example using this approach to share?

I'd apreciate any help! Thanks!

Samuel

Julian Arni

unread,
Nov 23, 2022, 9:31:19 AM11/23/22
to Samuel Feitosa, haskell-servant
Hi Samuel,

I haven't tried compiling this, but I think it would go roughly like this. Note two things: first, we're just capturing the *structure* of productServer and userServer, so we get all the *actual* handlers as an argument, and "arrange" them in the right shape. (Second), that shape is analogous to the type -- :<|> at the type leves becomes :<|> at the value level, "Capture x a :> blah" becomes a function from "a" to whatever the value-level of "blah" is, and things associate in a similar way.

-- API for values of type 'a'
-- indexed by values of type 'i'
type APIFor a i =
       Get '[JSON] [a] -- list 'a's
  :<|> ReqBody '[JSON] a :> PostNoContent -- add an 'a'
  :<|> Capture "id" i :>
         ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
      :<|> ReqBody '[JSON] a :> PutNoContent -- update an 'a'
      :<|> DeleteNoContent -- delete an 'a'
         )

-- Build the appropriate 'Server'
-- given the handlers of the right type.
serverFor :: Handler [a] -- handler for listing of 'a's
          -> (a -> Handler NoContent) -- handler for adding an 'a'
          -> (i -> Handler a) -- handler for viewing an 'a' given its identifier of type 'i'
          -> (i -> a -> Handler NoContent) -- updating an 'a' with given id
          -> (i -> Handler NoContent) -- deleting an 'a' given its id
          -> Server (APIFor a i)
serverFor listAll new view update delete = listAll :<|> new :<|> operations
   where
     -- All of these take a *common* argument. You can see this in the type above:
     -- the Capture "id" i *distributes* over all the endpoints to its right.
     -- So we have to produce something that takes one `i` and returns the three
     -- endpoints, which we can do by giving them all the argument :
     operations i = view i :<|> update i :<|> delete i


Let me know if that doesn't work or doesn't help!

--
You received this message because you are subscribed to the Google Groups "haskell-servant" group.
To unsubscribe from this group and stop receiving emails from it, send an email to haskell-serva...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/haskell-servant/e0206d92-ff22-4406-897f-b1898a0d4f9bn%40googlegroups.com.

Samuel Feitosa

unread,
Nov 30, 2022, 2:43:46 PM11/30/22
to Julian Arni, haskell-servant
Hi Julian! Thank you and sorry for the delay!! Indeed, your code helped me a lot.

I was able to create a simple example using the approach (https://github.com/sfeitosa/servant-abstracting-similar-apis). In this example I was only trying to compile a minimal app using this generic way to organize the routes/handlers.

In the next repos I show my initial idea, which was to create a generic way to provide REST endpoints for CRUD apps. So, I managed to integrate the reffered approach with the Persistent library. The code is here (https://github.com/sfeitosa/generic-crud-servant-persistent) if anyone is interested. There, one only needs to define the Routes and the Model, and all the rest is done automatically. Feel free to comment/suggest better approaches for this problem.

Thanks again.

[]'s

Samuel

--
Samuel da Silva Feitosa
Professor Instituto Federal de Santa Catarina
E-mail: samuel....@ifsc.edu.br
Reply all
Reply to author
Forward
0 new messages