Search code examples
resthaskellhtmxservantx-www-form-urlencoded

Htmx POST to haskell servant handling optional field in FormUrlEncoded request


I am using Htmx to POST a form to a Haskell Servant endpoint. The endpoint has a model with a FromForm instance. There is an optional field in the form. If the field is excluded in the POST, the client will receive a 400 with response "Unexpected end-of-input, expecting -, +, or a digit", indicating the parsing failed.

-- | Reading model.
data Reading = Reading
  { title :: String
  , authors :: Vector String
  , startDate :: Day
  , endDate :: Maybe Day
  }
  deriving (Eq, Show, Generic, ToJSON, FromJSON, FromRow)

-- | Allow form construction of a `Reading` instance.
instance FromForm Reading where
  fromForm :: Form -> Either Text Reading
  fromForm f =
    Reading
      <$> parseUnique "title" f
      <*> (fromList <$> parseAll "authors" f)
      <*> parseUnique "startDate" f
      <*> parseMaybe "endDate" f

The actual FormUrlEncoded bytestring that gets POSTed is title=asdf&authors=asdf&startDate=2024-03-01&endDate= . How can I work around this? I suspect there is a way to make servant handle this elegantly, but I am also considering client side changes (althought that seems less ideal).

I have seen this post but felt there must be an alternative to wrapping the Maybe. Also, maybe there is something I can do through Htmx for my specific use case.


Solution

  • I landed on a generic parser function with the signature suggested by @Ismor.

    It works as expected, I imagine I will always use this in place of parseMaybe when handling incoming form data.

    parseOptionalMaybe :: FromHttpApiData v => Text -> Form -> Either Text (Maybe v)
    parseOptionalMaybe fieldName form = do
      maybeField <- lookupMaybe fieldName form
      case maybeField of
        Just "" -> return Nothing
        Just _ -> parseMaybe fieldName form
        Nothing -> return Nothing