Reputation: 1328
I'm looking for a way to decorate a servant API definition: Whenever an endpoint returns a list of things, I'd like to add a query parameter.
So far, I have a type family that works on APIs that are defined with the traditional approach using :<|>
:
type family AddLimit a :: Type where
AddLimit ((sym :: Symbol) :> rest) = sym :> AddLimit rest
AddLimit (first :> rest) = first :> AddLimit rest
AddLimit (left :<|> right) = AddLimit left :<|> AddLimit right
AddLimit (Get contentTypes [res]) = QueryParam "limit" Int :> Get contentTypes [res]
AddLimit (Get contentTypes res) = Get contentTypes res
(this would require more instances to be complete, but it's enough to show the idea)
That will work if my API looks like this:
type API =
"foo" :> Get '[JSON] Int
:<|> "bar" :> Get '[JSON] [Int]
But it won't work when using NamedRoutes:
data MyNamedAPI mode = MyNamedAPI
{ foo :: mode :- "foo" :> Get '[JSON] Int,
bar :: mode :- "bar" :> Get '[JSON] [Int]
}
deriving stock (Generic)
I can add the following instance to the type family:
AddLimit (NamedRoutes nr) = AddLimit (ToServantApi nr)
but that will turn my API definition in the equivalent representation using :<|>
.
Alternatively, I could wrap AddLimit
around all fields manually:
data MyNamedAPI mode = MyNamedAPI
{ foo :: mode :- AddLimit ("foo" :> Get '[JSON] Int),
bar :: mode :- AddLimit ("bar" :> Get '[JSON] [Int])
}
deriving stock (Generic)
Is there a way to use the type family to do that with less duplication? There is ToServant in servant-generic, but the inverse is only supported on the value level using fromServant
Upvotes: 4
Views: 83