Rewriting to Haskell–Parsing Query Params, Again

riccardoodone

Riccardo Odone

Posted on May 4, 2020

Rewriting to Haskell–Parsing Query Params, Again

You can keep reading here or jump to my blog to get the full experience, including the wonderful pink, blue and white palette.


This is part of a series:


In the previous post we covered how to have Servant parse URL query parameters to custom data types. In this post, we see a similar technique without the use of FromHttpApiData.

The search endpoint in Stream has the following type signature:

type SearchAPI =
  QueryParam "query" Text
    :> QueryParam "quantity" Int
    :> QueryParam "comments" Bool
    :> QueryParam "channel" Text
    :> QueryParam "last_id" Int
    :> Get '[JSON] SearchResults
Enter fullscreen mode Exit fullscreen mode

Which translates to the following handler function

getSearchResults
  :: Maybe Text
  -> Maybe Int
  -> Maybe Bool
  -> Maybe Text
  -> Maybe Int
  -> Handler SearchResults
Enter fullscreen mode Exit fullscreen mode

Contrarily to the previous post, in this case we chose to use primitive types (e.g. Text, Int) instead of defining our own. What we do instead is to parse all the values in the first few lines of the handler:

getSearchResults configuration connection mQuery mQuantity mComments mChannel mLastId = do
  let searchQuery = mkSearchQuery mQuery
  let searchQuantity = mkSearchQuantity mQuantity
  let searchComments = mkSearchComments searchQuery mComments
  let searchChannels = mkSearchChannels mChannel
  let searchLastId = mkSearchLastId mLastId
  -- ...
Enter fullscreen mode Exit fullscreen mode

By doing that, we can translate Maybes into something that makes sense in Stream. For example, when in the URL query is not present or is an empty string, we want to return all posts. Otherwise, we use the value to filter:

data SearchQuery
  = Query Text
  | NoQuery

mkSearchQuery :: Maybe Text -> SearchQuery
mkSearchQuery Nothing = NoQuery
mkSearchQuery (Just "") = NoQuery
mkSearchQuery (Just query) = Query query
Enter fullscreen mode Exit fullscreen mode

In the case of quantity (of posts returned) and (return posts older than) last_id:

data SearchQuantity
  = Limit Int
  | NoLimit

mkSearchQuantity :: Maybe Int -> SearchQuantity
mkSearchQuantity (Just quantity) = Limit quantity
mkSearchQuantity Nothing = NoLimit

data SearchLastId
  = LastId Int
  | NoLastId

mkSearchLastId :: Maybe Int -> SearchLastId
mkSearchLastId (Just lastId) = LastId lastId
mkSearchLastId Nothing = NoLastId
Enter fullscreen mode Exit fullscreen mode

When it comes to what channels to search posts in, we limit to one only if channel is specified in the URL:

data SearchChannels
  = Channel Text
  | All

mkSearchChannels :: Maybe Text -> SearchChannels
mkSearchChannels Nothing = All
mkSearchChannels (Just channel) = Channel channel
Enter fullscreen mode Exit fullscreen mode

More interesting is when the endpoint is instructed to also match against the comments belonging to the post:

data SearchComments
  = Enabled SearchQuery
  | Disabled

mkSearchComments :: SearchQuery -> Maybe Bool -> SearchComments
mkSearchComments searchQuery searchComments =
  case searchComments of
    Just True -> Enabled searchQuery
    _ -> Disabled
Enter fullscreen mode Exit fullscreen mode

When comments=true, the query used to match against comments is the same used for posts. Otherwise, the search in comments is Disabled.


Get the latest content via email from me personally. Reply with your thoughts. Let's learn from each other. Subscribe to my PinkLetter!

💖 💪 🙅 🚩
riccardoodone
Riccardo Odone

Posted on May 4, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Rewriting to Haskell–Errors
functional Rewriting to Haskell–Errors

May 11, 2020

Rewriting to Haskell–Linting
functional Rewriting to Haskell–Linting

April 20, 2020

Rewriting to Haskell–Testing
functional Rewriting to Haskell–Testing

April 13, 2020