servant icon indicating copy to clipboard operation
servant copied to clipboard

How to Capture with .ext suffix?

Open achea opened this issue 2 years ago • 4 comments

Hi, I have an API that I'm querying which in simplest form looks like:

/int.json

The type I tried is:

type publicAPI = Capture "index" Int :> ".json" :> Get '[JSON] PublicData

But it constructs a query with an additional slash:

/int/.json

Is there a way to remove that slash?

I'm not too good with higher haskell and tried to make a new combinator building off of the existing Capture but it doesn't compile:

data CaptureWithSuffix (sym1 :: Symbol) (a :: *) (sym2 :: Symbol)
  deriving (Typeable)
type publicAPI = CaptureWithSuffix "index" Int ".json" :> Get '[JSON] PublicData
suffixToPath :: Builder -> Request -> Request         -- TODO suffix
suffixToPath p req
  = req { requestPath = requestPath req <> "/" <> p }

instance (KnownSymbol capture, KnownSymbol suffix, ToHttpApiData a, HasClient m api)
      => HasClient m (CaptureWithSuffix capture a suffix :> api) where

  type Client m (CaptureWithSuffix capture a suffix :> api) =
    a -> Client m api

  clientWithRoute pm Proxy req val =  -- TODO pass in suffix
    clientWithRoute pm (Proxy :: Proxy api)
                    (suffixToPath p req)

    where p = toEncodedUrlPiece val

  hoistClientMonad pm _ f cl = \a ->
    hoistClientMonad pm (Proxy :: Proxy api) f (cl a)

The error looks like like somewhere is missing an argument, but I don't understand it:

src/ServantClient.hs:40:3: error:
    • Couldn't match type ‘Client m api0’ with ‘Client m api’
      Expected type: Client m (CaptureWithSuffix capture a suffix :> api)
        Actual type: a -> Client m api0
      NB: ‘Client’ is a non-injective type family
      The type variable ‘api0’ is ambiguous
    • The equation(s) for ‘clientWithRoute’ have four arguments,
      but its type ‘Proxy m
                    -> Proxy (CaptureWithSuffix capture a suffix :> api)
                    -> Request
                    -> Client m (CaptureWithSuffix capture a suffix :> api)’
      has only three
      In the instance declaration for
        ‘HasClient m (CaptureWithSuffix capture a suffix :> api)’
src/ServantClient.hs:46:32: error:
    • Couldn't match type ‘Client mon' api1’ with ‘Client mon' api’
      Expected type: Client
                       mon' (CaptureWithSuffix capture a suffix :> api)
        Actual type: a -> Client mon' api1
      NB: ‘Client’ is a non-injective type family
      The type variable ‘api1’ is ambiguous
    • The lambda expression ‘\ a
                               -> hoistClientMonad pm (Proxy :: Proxy api) f (cl a)’
      has one argument,
      but its type ‘Client
                      mon' (CaptureWithSuffix capture a suffix :> api)’
      has none
      In the expression:
        \ a -> hoistClientMonad pm (Proxy :: Proxy api) f (cl a)
      In an equation for ‘hoistClientMonad’:
          hoistClientMonad pm _ f cl
            = \ a -> hoistClientMonad pm (Proxy :: Proxy api) f (cl a)

How would I write this if this the way to go or is there a better way?

achea avatar Feb 02 '23 00:02 achea

Hi! :)

I have a preliminary question: Would this pattern (getting a identifier without the extension) be specific to an endpoint, or is it something that many endpoints would make use of?

tchoutri avatar Feb 02 '23 08:02 tchoutri

Many endpoints would be able to use it; in my usecase not only .json of various paths but .png is supported too, so it wouldn't be far to say other image types could be auto-generated or even some theoretical endpoint with .pdfs. This is why I was thinking of making it as general as possible, even though (I think) I can hard-code the <> ".json" in my suffixToPath function.

achea avatar Feb 02 '23 22:02 achea

I figured out the compile issue! ScopedTypeVariables was missing to compile the base Capture, though I didn't figure out how to pass in the extra symbol. Instead I went with pretty much Capture except using a period in req { requestPath = requestPath req <> "." <> p } instead of a forward slash in the suffixToPath function. Is this something that would be useful as a PR to include into base servant?

achea avatar Feb 05 '23 09:02 achea

@achea sorry for the late response! Sure, send us a PR. :)

tchoutri avatar Apr 25 '24 17:04 tchoutri