Rewriting to Haskell–Parsing Query Params, Again
Riccardo Odone
Posted on May 4, 2020
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:
- Rewriting to Haskell–Intro
- Rewriting to Haskell–Project Setup
- Rewriting to Haskell–Deployment
- Rewriting to Haskell–Automatic Formatting
- Rewriting to Haskell–Configuration
- Rewriting to Haskell–Standing on the shoulders of Rails
- Rewriting to Haskell–Making GHC More Nitpicky
- Rewriting to Haskell–Testing
- Rewriting to Haskell–Linting
- Rewriting to Haskell–Parsing Query Params
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
Which translates to the following handler function
getSearchResults
:: Maybe Text
-> Maybe Int
-> Maybe Bool
-> Maybe Text
-> Maybe Int
-> Handler SearchResults
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
-- ...
By doing that, we can translate Maybe
s 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
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
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
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
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!
Posted on May 4, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.