Search code examples
haskellhaskell-snap-frameworkservant

Haskell Servant: GET Requests with arbitrary request data


I'm serving an API using Servant, all managed by Snap. In Servant, it's easy to include an arbitrary data type as part of a POST request, assuming it has a FromJSON instance. For instance, I might have the following endpoint:

ReqBody '[JSON] RequestData :> Post '[JSON] [ResponseData]

How do I do the same for GET requests? From what I understand, I'd need to use the Query Parameters, but my request data consists of complex datatypes (lists, nested dictionaries) that don't seem to be readable easily, e.g. QueryParam "vals" [Int] :> Post '[JSON] [Int] results in the error No instance for (FromHttpApiData [Int])

A workaround would be to use POST requests, which have easily readable request bodies. However, this would clash with my caching scheme in Nginx, since responses to POST requests aren't that easily cachable. Even if I can cache them, I don't want to cache all post requests, so it'd be a messy approach.

Thanks for any help!


Solution

  • A simple solution if you want the same level of automatic derivation as for JSON post bodies is to just send the query params as JSON

    import Data.Aeson
    import Servant.API
    import qualified Data.Text as T
    import Data.Text.Encoding
    import qualified Data.ByteString.Lazy as LBS
    
    newtype JSONEncoded a = JSONEncoded { unJSONEncoded :: a }
      deriving (Eq, Show)
    
    instance (FromJSON a) => FromHttpApiData (JSONEncoded a) where
      parseQueryParam x = case eitherDecode $ LBS.fromStrict $ encodeUtf8 x of
        Left err -> Left (T.pack err)
        Right val -> Right (JSONEncoded val)
    
    instance (ToJSON a) => ToHttpApiData (JSONEncoded a) where
      toQueryParam (JSONEncoded x) = decodeUtf8 $ LBS.toStrict $ encode x