-
-
Notifications
You must be signed in to change notification settings - Fork 413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cookbooks #1542
base: master
Are you sure you want to change the base?
Cookbooks #1542
Conversation
dfce55c
to
bf5b1fd
Compare
bf5b1fd
to
23edc45
Compare
23edc45
to
19e432c
Compare
structure. We only deal with non-nested APIs in which every endpoint is on the same | ||
level. | ||
|
||
If a you need nesting because you have different branches in your API tree, you |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"If a" => "If"
@@ -0,0 +1,383 @@ | |||
# Record-based APIs: the nested records case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nested API*
It does indeed result in "nested records", but this merely mirrors the structure of the API, which is the central element that should be in the title IMO
|
||
*Available in Servant 0.19 or higher* | ||
|
||
Servant offers a very natural way of constructing APIs with nested records, called `NamedRoutes`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it'd be good to clarify what "nested" means here? Describing the general pattern dividing up APIs in sub-APIs and being able to combine those easily, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic cookbook should have clarified this beforehand.
|
||
Finally, we can run the server and connect the API routes to the handlers as usual: | ||
|
||
``` haskell |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't we want to use genericServe
here? to stay in the "record world".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need as NamedRoutes
has a HasServer
instance. So no need to specialised functions that call toServant
and fromServant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't see the point of introducing genericServe
here — it makes the overall story less uniform.
We checked yesterday, and genericServe
produces the exact same error message as serve
if Generic
instances are missing (a relatively unhelpful message mentioning Generic
) so we are not gaining anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well that forces people to use NamedRoutes
at the top, which is completely unnecessary if your entire API is made of records. The most direct way to serve a record of handlers is genericServe
, hence my suggestion.
It'd be good to have some place that really explains those ":<|> separated" and "record" worlds and how we can go from one to the other, at all levels. But in the meantime, in the interest of giving the simplest code possible in the cookbook, without having to explain that named routes is an "adaptator" of sorts to plug records into old-style APIs, I'd recommend personally recommend genericServe
(but do as you wish, this is just my opinion).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think:
serveGeneric (Proxy @MyApi) myServer
isn't so much shorter than:
serve (Proxy @(NamedRoutes MyAPI)) myServer
as to warrant the introduction of another function in the cookbook. It gives off the feeling that record APIs still somewhat live in a different world — which they now don't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That function is already introduced in the introductory cookbook: https://docs.servant.dev/en/stable/cookbook/generic/Generic.html#server
(and even the ServerT
version)
And requires appending a few chars to serve
vs another pair of parens and the NamedRoutes
thing.
But I also suggested to @akhesaCaro at some point that another possibility could just be to pick one and show the equivalent with the other, in a commented-out (or not, could just be with a different name) line right below.
|
||
``` haskell | ||
movieCatalogClient :: API (AsClientT ClientM) | ||
movieCatalogClient = client api -- remember: api :: Proxy MovieCatalogAPI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
genericClient
?
, delete = deleteMovieHandler movieId | ||
} | ||
``` | ||
As you might have noticed, we build our handlers out of the same record types we used to define our API: `MoviesAPI` and `MovieAPI`. What kind of magic is this ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might fit well in the generic
cookbook? Where we introduce the whole record-API machinery.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two cookbooks should IMHO be merged into a single cookbook, the non-nested / flat API case being the first section, and the nested records one expanding upon the first. So we can move this section a bit up when we do so ; for now I think it is better if the NamedRoutes
cookbook remains standalone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is what I have advocated in favor of as well :-) And even putting the resulting thing in the tutorial. But in the interest of splitting that up into a bunch of much smaller steps, we're first consolidating the named routes one to make it stand as a nice follow up to the generic
cookbook. Then slowly but surely proceeding with the rest of the plan.
for now I think it is better if the NamedRoutes cookbook remains standalone
We'd end up with 2 cookbooks which half-explain the same basics, before covering simple APIs and nested APIs respectively. Might not be the best option for users, no?
|
||
If you are interested in further understanding the built-in Servant combinators, see [Structuring APIs](../structuring-apis/StructuringApis.html). | ||
|
||
Since `NamedRoutes` is based on the Generic mechanism, you might want to have a look at [Sandy Maguire’s _Thinking with Types_ book](https://doku.pub/download/sandy-maguire-thinking-with-typesz-liborgpdf-4lo5ne7kdj0x). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not nice to put a piracy link. Better link the official website: https://thinkingwithtypes.com/
Introducing NamedRoutes Cookbook (following #1538)
As requested by @alp,
We tried to tu put some consistency between the Generic Cookbook and this one.
The idea is to change a minimum of other cookbook as a first step.
The second step (in another PR) will be a thorough restructuration of the cookbooks.