Htmx POST至Haskell Servant:处理表单可选字段解析异常
解决Servant中FromForm可选日期字段空值解析失败问题
问题分析
当表单提交空的endDate字段(如endDate=)时,parseMaybe会尝试将空字符串解析为Day类型,直接抛出解析错误并返回400响应。这是因为parseMaybe的逻辑是:字段不存在时返回Nothing,但字段存在且值无法解析为目标类型时,会返回错误而非Nothing。
服务端解决方案
自定义解析逻辑,先获取字段的原始文本值,判断是否为空,为空则返回Nothing;非空时再尝试解析为Day类型,同时保留无效格式的错误提示。
修改后的完整代码
import Data.Text (Text, isEmpty) import Data.Time (Day) import Data.Time.Format (parseTimeM, defaultTimeLocale) import Servant (FromForm(..), Form, parseMaybe, parseUnique, parseAll) import Data.Vector (Vector, fromList) -- | Reading model. data Reading = Reading { title :: String , authors :: Vector String , startDate :: Day , endDate :: Maybe Day } deriving (Eq, Show, Generic, ToJSON, FromJSON, FromRow) -- 解析日期:空字符串返回Nothing,无效格式返回错误 parseDayAllowEmpty :: Text -> Either Text (Maybe Day) parseDayAllowEmpty t | isEmpty t = Right Nothing | otherwise = case parseTimeM True defaultTimeLocale "%Y-%m-%d" t of Nothing -> Left $ "无效的日期格式: " <> t Just day -> Right $ Just day -- 处理可选日期字段:兼容字段不存在、空值、有效/无效格式 parseOptionalDay :: Text -> Form -> Either Text (Maybe Day) parseOptionalDay field f = do mTextVal <- parseMaybe field f case mTextVal of Nothing -> pure Nothing -- 字段不存在 Just val -> parseDayAllowEmpty val -- 字段存在,判断空值并解析 -- | 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 <*> parseOptionalDay "endDate" f
方案效果
endDate字段省略时:返回NothingendDate提交空字符串时:返回NothingendDate提交有效日期(如2024-03-01)时:返回Just DayendDate提交无效格式字符串(如abc)时:返回错误提示,保留参数校验逻辑
可选前端调整(非推荐)
在Htmx提交表单前,用JavaScript移除值为空的endDate字段,让服务端收到的请求不包含该字段,parseMaybe即可正常返回Nothing。但这种方式需要前端配合,不如服务端处理通用优雅。
内容的提问来源于stack exchange,提问作者Razumov
相关产品推荐
相关产品推荐

