-
Notifications
You must be signed in to change notification settings - Fork 37
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
Description for QueryParam #62
Comments
@rikvdkleij you can't do it with type MyAPI
= QueryParam "resource_id" ResourceId :> Get '[JSON] Resource
:<|> QueryParam "resource_id" ResourceId :> Delete '[JSON] Resource And you might want to add different descriptions for
To attach description currently the best way would be to add new combinator like this: type MyAPI
= GetResource :<|> DeleteResource
type GetResource
= QueryParam "resource_id" ResourceId --^ "ID of a resource to fetch."
:> Get '[JSON] Resource
type DeleteResource
= QueryParam "resource_id" ResourceId --^ "ID of a resource to delete."
:> Delete '[JSON] Resource
data ParamDesc a (desc :: Symbol)
type (--^) = ParamDesc
instance ... => HasServer (ParamDesc str a :> api) where ...
instance ... => HasClient (ParamDesc str a :> api) where ...
instance ... => HasSwagger (ParamDesc str a :> api) where ... You could also attach descriptions on term-level using optics, but it doesn't seem to be particularly elegant: -- | Peak into 'Param' of an endpoint by its name.
paramByName :: Text -> Traversal' Operation Param
paramByName paramName = parameters.traverse._Inline.filtered byName
where
byName param = paramName == param^.name
-- to add a param description
mySwagger
& subOperations (Proxy @GetResource) (Proxy @MyAPI).paramByName "resource_id".description ?~ "ID of a resource to fetch."
& subOperations (Proxy @DeleteResource) (Proxy @MyAPI).paramByName "resource_id".description ?~ "ID of a resource to delete." |
@fizruk Thanks for your answer. Both solution feel rather "heavy" for adding a description to parameter. Are you on ZuriHac so we can discuss this further? |
@rikvdkleij type-level solution is the right way to go in my opinion. It makes code look nice and complete and it allows different interpretations to use this information. E.g. you could also generate JS client with comments in the generated code. We're hoping to adopt this at work first, see how it goes and then try push it into Servant's core libraries. In any case I should be able to share some code soon, so you could just copy-paste it to enable this feature. Unfortunately I'm not going to ZuriHac, but @phadej is and I think he'd be happy to discuss this! |
I surely am! |
I imagine writing API like type FooEndpoint =
"foo" :>
Capture "foo-id" FooId :? "The identifier" :>
QueryParam "canonical" Bool :? "Normalise the representation :>
Get '[JSON] Foo :? "Why not annotate the result too" I'm quite sure, that should work: -- | Server doesn't care about descriptions
instance HasServer (combinator :> api) => HasServer (combinator :? description :> api) where
... |
@phadej one problem you'll get is for interpretations where you care about descriptions. E.g. in instance ... => HasSwagger (QueryParam name a :? description :> api) where ...
instance ... => HasSwagger (QueryParam name a :> api) where ...
instance ... => HasSwagger (Capture name a :? description :> api) where ...
instance ... => HasSwagger (Capture name a :> api) where ... Another problem is that description is this just one kind of extra info we can add to parameters. Other kinds include leniency, required/optional, parameter name (which is embedded currently into every parameter, but might as well be external). If one day'd want to add more of those, imaging dealing with a parameter like this: type FooEndpoint
= "foo"
:> Required (Lenient (Name "foo-id" (QueryParam FooId))) :? "Foo ID"
:> Get '[JSON] Foo How many I think that maybe we'd have to converge to something more flexible, like this: type FooEndpoint
= "foo"
:> Param Query "foo-id" a
'[ "description" := "ID of a foo you'd like to GET."
, "required" := True
, "lenient" := False
...
]
:> Get '[JSON] Foo I imagine then you'd treat various parameter options (like P. S. I think all combinators should have prefix form like |
The type-level record is nice. Was it your (latest) idea for leniency/required too (you have said that you have an idea, but never mentioned it IIRC)? |
BTW, I also think that in some interpretations we could use instance (HasFooModifier mod, HasFoo api) => HasFoo (mod :> api) where where If nothing else, it would make generated haddocks much simpler. EDIT may also make GHC faster in resolving the instances, as there are less-flexible ones. |
@phadej sorry, I can't remember what I was referring to back then :) The problem with Compare this class HasSwagger api where
toSwagger :: proxy api -> Swagger
class HasSwaggerParam p where
toParam :: proxy p -> Param
instance (HasSwaggerParam param, HasSwagger api) => HasSwagger (param :> api) where ...
instance ... => HasSwaggerParam (QueryParam name a) where ...
-- impossible to write this instance!
instance ... => HasSwaggerParam (EndpointDescription desc) where ... and this class HasSwagger api where
toSwagger :: proxy api -> Swagger
-- uglier, but more generic modifiers
-- now modifier has to find what to modify in the entire Swagger spec!
class HasSwaggerModifier mod where
toModifier :: proxy mod -> Swagger -> Swagger
instance (HasSwaggerModifier mod, HasSwagger api) => HasSwagger (mod :> api) where ...
-- both instances are possible, but are less elegant and harder to write
instance ... => HasSwaggerModifier (QueryParam name a) where ...
instance ... => HasSwaggerModifier (EndpointDescription desc) where ... We could maybe have different modifiers for different kinds: class HasSwagger api where
toSwagger :: proxy api -> Swagger
class HasSwaggerModifier (mod :: k) where
type Mod (mod :: k) :: *
toModifier :: proxy mod -> Mod mod
-- we can now have different modifiers kinds!
-- for current servant parameters we can use * kind, to leave it open!
-- note how Symbol kind works out nicely for path segments!
-- we can add new modifiers with new kinds (but they'd be closed)
instance ... => HasSwagger ((mod :: *) :> api) where ...
instance ... => HasSwagger ((mod :: Symbol) :> api) where ...
instance ... => HasSwagger ((mod :: EndpointDescription) :> api) where ... |
@fizruk I'm lost with |
@phadej data EndpointDescription = Description Symbol
type FooAPI
= Description "Get foo by its ID."
:> Capture "foo-id" FooId
:> Get '[JSON] Foo
instance KnownSymbol desc => HasSwaggerModifier (Description desc)
type Mod EndpointDescription = String
toModifier _ = symbolVal (Proxy @ desc)
instance (HasSwaggerModifier mod, HasSwagger api)
=> HasSwagger ((mod :: EndpointDescription) :> api) wher
toSwagger = toSwagger (Proxy @ api)
& allOperations.description ?~ toModifier (Proxy @ mod) |
but won't that work equally well with class HasSwaggerModifier mod where
modifySwagger :: proxy mod -> Swagger -> Swagger
instance KnownSymbol => HasSwaggerModifier (Description desc) where
modifySwagger _ swagger = swagger
& allOperations.description ?~ symbolVal (Proxy :: Proxy desc) ? |
@phadej do you mean to restrict |
Seems that even GHC-7.8.4 can differentiate the instances based on the kind: {-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
{-# LANGUAGE PolyKinds, DataKinds, TypeOperators, ScopedTypeVariables #-}
import GHC.TypeLits
import Data.Proxy
infixr 5 :>
data a :> b
data Foo = Foo Int deriving Show
class HasFoo a where
foo :: Proxy a -> Foo
class ModFoo a where
modFoo :: Proxy a -> Foo -> Foo
instance (ModFoo a, HasFoo b) => HasFoo (a :> b) where
foo _ = modFoo (Proxy :: Proxy a) (foo (Proxy :: Proxy b))
instance HasFoo Bool where
foo _ = Foo 2
instance KnownSymbol a => ModFoo a where
modFoo p (Foo x) = Foo $ length (symbolVal p) + x
instance ModFoo () where
modFoo _ (Foo x) = Foo (x + 1)
{-
*Main> foo (Proxy :: Proxy ("foo" :> () :> Bool))
Foo 6
-} |
@phadej my point was that It also gets worse with associated type families or more complex methods: class HasServer api where
type ServerT api (m :: * -> *) :: *
route :: Proxy api -> Context context -> Delayed env (Server api) -> Router env
class ModServer m where
type ModServerT m :: ?
modRoute :: ? Instead, if we limit our ambitions per kind, we can make it simpler: -- | Parameters for server handlers.
-- Only works for kind *.
class ServerParam (p :: *) where
type ParamType p :: *
addParamCheck :: proxy p -> Delayed env (ParamType p -> b) -> Delayer env b
-- | General instance for parameters.
instance (ServerParam p, HasServer api) => HasServer (param :> api) context where
type ServerT (param :> api) m = ParamType param -> ServerT api m
route _ context subserver =
route (Proxy :: Proxy api) context (addParamCheck (Proxy :: Proxy param) subserver) And it's now easier to write individual instances for parameters: -- | Sample instance for 'QueryParam'.
instance ... => ServerParam (QueryParam sym a) where
type ParamType (QueryParam sym a) = Maybe a
addParamCheck _ subserver = subserver `addParameterCheck` withRequest parseParam
where parseParam req = ...
-- same approach would work for many other combinators
instance ... => ServerParam (QueryFlag sym) where ...
instance ... => ServerParam (Header sym a) where ...
instance ... => ServerParam (ReqBody cts a) where ...
instance ... => ServerParam (BasicAuth realm usr) where ...
instance ... => ServerParam HttpVersion where ...
-- only doesn't work for CaptureAll (for existing combinators),
-- but that's easily fixed by giving it its own kind and it's own HasServer instance
-- I guess it can also work with {-# OVERLAPPING #-},
-- but kind-dispatching has more appeal to me
data CaptureAll = CaptureAll Symbol Type
instance ... => HasServer (CaptureAll sym a :> api) where ... I also think that introducing |
@fizruk per kind makes sense indeed. |
On mobile so terse: there is Description modifier on QueryParam' (note the prime, servant-0.13) see example in servant-swagger-ui for an example
Cheers, Oleg
…Sent from my iPhone
On 11 Mar 2018, at 16.50, Sebastian Mendez ***@***.***> wrote:
Sorry to perform necromancy on this thread, but did anything come out of this discussion?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
…ding_members Notify team contacts on user update
@phadej is there a way of adding a |
I don't work on |
How can I add description for
QueryParam
withToParamSchema
? According to Swagger specification it is possible to add description to parameter but I do not see way how to add it.The text was updated successfully, but these errors were encountered: