Construct a dynamic QueryParam

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Construct a dynamic QueryParam

Dieter Vekeman
Hi list

I am currently building a client for an existing (non-Servant) REST API. The api is very close to the database schema and data is returned in a very generic way.
Among the table and column metadata it also returns links to other entities as an URL that can be used as is.

What I want to achieve is to define a route for this special case where I already have a complete URL. I'm not sure how to do it:
- either break down the URL and fit it into an API call
- or somehow just pass the url directly but reuse things like BasicAuth, Headers, ...

For example

Let's say I start with a normal call to query some data through the api: 

http://<server>:<port>/<captured_type>/advanced/
body: {"criteria": ...}

type BasicAPI =
       
BasicAuth "realm" User :> Capture "type" T.Text :> "/advanced" :> ReqBody '[JSON] SC.Criteria :> Header "X-SLIMS-REQUESTED-FOR" T.Text :> Get '[JSON] SE.Records

:<|> etc...


This returns
{
 
"entities": [
   
{
     
...
     
"columns": [...],
     
"links": [ { "rel": "-ctrl_fk_contentType", "href": "http://localhost:9999/rest/ContentTypeRole?ctrl_fk_contentType=1" } ]
     
...
   
}
 
]
}


I want to call this URL from the result (see link > href) directly in order to follow the link to the other entity and then parse it using the JSON mappings that I already defined.

Initially I started of by splitting up the url in the BaseUrl and get the parameter name and value myself.
And there are some assumptions I can take:
* there will be just one QueryParam
* the type of the parameter will be Int

However the name of the parameter is unknown...

-- :<|> BasicAuth "realm" User :> QueryParam ??? Int :> Header "X-SLIMS-REQUESTED-FOR" T.Text :> Get '[JSON] SE.Records

I'm a bit stuck here on how to construct the QueryParam with a dynamic name...

Thanks!
D.

--
Reply | Threaded
Open this post in threaded view
|

Re: Construct a dynamic QueryParam

Julian Arni-2
Interesting problem!

For the dynamic name bit of it, I think the way to go is to declare a new dataype (data DynQueryParam a), and instances for it. The servant-client HasClient instance for this new type will look a look like QueryParam, but will take an extra Text argument, which corresponds to the query parameter name.

I imagine not *all* params are valid, so you might want to consider a variation of this that instead of a Text argument takes another type whose inhabitants correspond only to valid params.

Let me know if this doesn't work or doesn't make sense!

Cheers,

--
  Julian Arni
  Turing Jump, https://turingjump.com

--
Reply | Threaded
Open this post in threaded view
|

Re: Construct a dynamic QueryParam

Dieter Vekeman
Hi

Thanks a lot for the suggestion! I think I got it working (somehow because it's still a lot of magic to me). I started with the suggestion to allow any Text, I might reduce that a bit further later on as you suggested.

Here is what I ended up with, any improvements welcome :-)

{- ... -}
module Servant.API.Extended 
  ( module Servant.API
  , DynQueryParam
  , DynCapture
  ) where
...
data
DynQueryParam a b deriving (Typeable)
data
DynCapture a b deriving (Typeable)

And instances for HasClient

{- ... -}
module Servant.Client.Extended 
  ( module Servant.Client
  ) where
...
instance (ToHttpApiData a, ToHttpApiData b, HasClient api)
     
=> HasClient (DynQueryParam a b :> api) where

  type
Client (DynQueryParam a b :> api) =
    T
.Text -> Maybe a -> Client api

  clientWithRoute
Proxy req name value =
    clientWithRoute
(Proxy :: Proxy api)
                   
(maybe req
                           
(flip (appendToQueryString pname) req . Just)
                           mparamText
                   
)

   
where pname = name
          mparamText
= fmap toQueryParam value

instance
(ToHttpApiData a, ToHttpApiData b, HasClient api)
     
=> HasClient (DynCapture a b :> api) where

  type
Client (DynCapture a b :> api) =
    T
.Text -> b -> Client api

  clientWithRoute
Proxy req name value =
    clientWithRoute
(Proxy :: Proxy api)
                   
(appendToPath p req)

   
where p = T.unpack (toUrlPiece value)

Usage in the api:
  :<|> BasicAuth "realm" User :> DynCapture T.Text T.Text :> Header "X-..." T.Text :> Get '[JSON] SE.Records


On Tuesday, July 12, 2016 at 4:38:15 PM UTC+2, Julian Arni wrote:
Interesting problem!

For the dynamic name bit of it, I think the way to go is to declare a new dataype (data DynQueryParam a), and instances for it. The servant-client HasClient instance for this new type will look a look like QueryParam, but will take an extra Text argument, which corresponds to the query parameter name.

I imagine not *all* params are valid, so you might want to consider a variation of this that instead of a Text argument takes another type whose inhabitants correspond only to valid params.

Let me know if this doesn't work or doesn't make sense!

Cheers,

--
  Julian Arni
  Turing Jump, <a href="https://turingjump.com" target="_blank" rel="nofollow" onmousedown="this.href=&#39;https://www.google.com/url?q\x3dhttps%3A%2F%2Fturingjump.com\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNH6sHpinvVIkDnpIrD3HV41Odn4ng&#39;;return true;" onclick="this.href=&#39;https://www.google.com/url?q\x3dhttps%3A%2F%2Fturingjump.com\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNH6sHpinvVIkDnpIrD3HV41Odn4ng&#39;;return true;">https://turingjump.com

--