如何通过浏览器正确减少 GET 请求的仆人 API 路径 (:>) 组合子树?
How to properly reduce servant API path (:>) combinator trees for GET request via browser?
Haskell Servant docs 提供了几个示例,用于编写 API 来提供具有类型级 DSL 的某些内容,例如
type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
我想做的是编写一个类似的 API, 可以 将多个输入作为 GET 请求,也可以在浏览器中使用。 (以下代码经过删节和简化)。
type QuestionAPI = "question"
:> QueryParam "question1" Question
:> QueryParam "question2" Question
:> QueryParam "question3" Question
...
...
:> QueryParam "questionn" Question
:> Get '[JSON] [Answer]
这工作得很好,但是使用这个端点的函数接收 n
个参数
processQuestionAPI :: Maybe Question -> Maybe Question -> ... -> Handler [Answer]
processQuestionAPI param1 param2 param3 ... paramN = ...
这让一切都更难阅读和推理。
我想到的第一个解决方法是使用记录!
data LotsOfQuestions = LotsOfQuestions
{ question1 :: Maybe Question
, question2 :: Maybe Question
, question3 :: Maybe Question
...
...
, questionn :: Maybe Question
}
并像这样重写端点
type QuestionAPI = "question"
:> ReqBody '[FromUrlEncoded] LotsOfQuestions
:> Get '[JSON] [Answer]
编译这段代码时 GHC 抛出了这个错误
• No instance for (Web.Internal.FormUrlEncoded.FromForm
LotsOfQuestion)
arising from a use of ‘serve’
所以我只是为 LotsOfQuestions
.
编写了一个自定义 FromForm
实例
instance FromForm LotsOfQuestion where
fromForm aForm = LotsOfQuestions
<$> parseMaybe "q1" aForm
<*> parseMaybe "q2" aForm
...
<*> parseMaybe "qN" aForm
一切都已编译,服务器已启动,运行但我无法使用浏览器连接到它。
我用的URL是这个
localhost:8081/questions?q1=what&q2=where&q3=when
奇怪的是 cURL
确实有效!
这个
curl -d "q1=what&q2=where&q3=when" -X GET "localhost:8081/questions"
生产出我想要的。
环顾四周,我发现了这个 this issue,这让我相信用 GET
请求发送请求主体不是推荐的做事方式。
所以我必须用 GET
请求的等效内容替换 ReqBody
,但我不确定那可能是什么。
这更像是一份进度报告,而不是最终答案。
这个主要问题是像这样的端点
type QuestionAPI = "question"
:> QueryParam "question1" Question
:> QueryParam "question2" Question
:> QueryParam "question3" Question
...
...
:> QueryParam "questionn" Question
:> Get '[JSON] [Answer]
确实有效,但使用它们的函数通常不那么容易使用,例如
processQuestionAPI :: Maybe Question -> Maybe Question -> ... -> Handler [Answer]
processQuestionAPI param1 param2 param3 ... paramN = ...
我的解决方案是使用记录语法
data LotsOfQuestions = LotsOfQuestions
{ question1 :: Maybe Question
, question2 :: Maybe Question
, question3 :: Maybe Question
...
...
, questionn :: Maybe Question
}
但我不知道如何将该记录映射到服务 DSL。
Mark 的评论给了我一些启发。
我需要做的是实施 FromHttpApiData
class,特别是 parseQueryParam。
因为其中一些问题是可选的,所以实施有些迂回。
instance FromHttpApiData LotsOfQuestions where
parseQueryParam = parseQuestions
tailMaybe :: [a] -> Maybe [a]
tailMaybe [] = Nothing
tailMaybe str = Just $ tail str
splitOnEqual :: String -> Maybe (String, Maybe String)
splitOnEqual xs = second tailMaybe . flip splitAt xs <$> elemIndex '=' xs
parseQuestions :: Text -> Either Text LotsOfQuestions
parseQuestions txt =
LotsOfQuestions
<$> sequence (fmap fromOrder =<< lookup "q1" txtMap)
<*> sequence (fmap fromOrder =<< lookup "q2" txtMap)
<*> sequence (fmap fromOrder =<< lookup "q3" txtMap)
...
...
<*> sequence (fmap fromOrder =<< lookup "qN" txtMap)
where txtMap = mapMaybe splitOnEqual (splitOn "&" $ unpack txt)
这里 fromOrder
是类型为 Text -> Either Text Question
的内部函数,而 splitOn
来自 Data.List.Split
.
这些是我对 QuestionAPI
所做的更改
type QuestionAPI = "questions"
:> QueryParam "are" LotsOfQuestions
:> Get '[ JSON] [Answer]
与 API 交互的方式是通过这样的 link
http://localhost:8081/questions?are=q1=what&q2=where&q3=when
Haskell Servant docs 提供了几个示例,用于编写 API 来提供具有类型级 DSL 的某些内容,例如
type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
我想做的是编写一个类似的 API, 可以 将多个输入作为 GET 请求,也可以在浏览器中使用。 (以下代码经过删节和简化)。
type QuestionAPI = "question"
:> QueryParam "question1" Question
:> QueryParam "question2" Question
:> QueryParam "question3" Question
...
...
:> QueryParam "questionn" Question
:> Get '[JSON] [Answer]
这工作得很好,但是使用这个端点的函数接收 n
个参数
processQuestionAPI :: Maybe Question -> Maybe Question -> ... -> Handler [Answer]
processQuestionAPI param1 param2 param3 ... paramN = ...
这让一切都更难阅读和推理。
我想到的第一个解决方法是使用记录!
data LotsOfQuestions = LotsOfQuestions
{ question1 :: Maybe Question
, question2 :: Maybe Question
, question3 :: Maybe Question
...
...
, questionn :: Maybe Question
}
并像这样重写端点
type QuestionAPI = "question"
:> ReqBody '[FromUrlEncoded] LotsOfQuestions
:> Get '[JSON] [Answer]
编译这段代码时 GHC 抛出了这个错误
• No instance for (Web.Internal.FormUrlEncoded.FromForm
LotsOfQuestion)
arising from a use of ‘serve’
所以我只是为 LotsOfQuestions
.
FromForm
实例
instance FromForm LotsOfQuestion where
fromForm aForm = LotsOfQuestions
<$> parseMaybe "q1" aForm
<*> parseMaybe "q2" aForm
...
<*> parseMaybe "qN" aForm
一切都已编译,服务器已启动,运行但我无法使用浏览器连接到它。
我用的URL是这个
localhost:8081/questions?q1=what&q2=where&q3=when
奇怪的是 cURL
确实有效!
这个
curl -d "q1=what&q2=where&q3=when" -X GET "localhost:8081/questions"
生产出我想要的。
环顾四周,我发现了这个 this issue,这让我相信用 GET
请求发送请求主体不是推荐的做事方式。
所以我必须用 GET
请求的等效内容替换 ReqBody
,但我不确定那可能是什么。
这更像是一份进度报告,而不是最终答案。
这个主要问题是像这样的端点
type QuestionAPI = "question"
:> QueryParam "question1" Question
:> QueryParam "question2" Question
:> QueryParam "question3" Question
...
...
:> QueryParam "questionn" Question
:> Get '[JSON] [Answer]
确实有效,但使用它们的函数通常不那么容易使用,例如
processQuestionAPI :: Maybe Question -> Maybe Question -> ... -> Handler [Answer]
processQuestionAPI param1 param2 param3 ... paramN = ...
我的解决方案是使用记录语法
data LotsOfQuestions = LotsOfQuestions
{ question1 :: Maybe Question
, question2 :: Maybe Question
, question3 :: Maybe Question
...
...
, questionn :: Maybe Question
}
但我不知道如何将该记录映射到服务 DSL。
Mark 的评论给了我一些启发。
我需要做的是实施 FromHttpApiData
class,特别是 parseQueryParam。
因为其中一些问题是可选的,所以实施有些迂回。
instance FromHttpApiData LotsOfQuestions where
parseQueryParam = parseQuestions
tailMaybe :: [a] -> Maybe [a]
tailMaybe [] = Nothing
tailMaybe str = Just $ tail str
splitOnEqual :: String -> Maybe (String, Maybe String)
splitOnEqual xs = second tailMaybe . flip splitAt xs <$> elemIndex '=' xs
parseQuestions :: Text -> Either Text LotsOfQuestions
parseQuestions txt =
LotsOfQuestions
<$> sequence (fmap fromOrder =<< lookup "q1" txtMap)
<*> sequence (fmap fromOrder =<< lookup "q2" txtMap)
<*> sequence (fmap fromOrder =<< lookup "q3" txtMap)
...
...
<*> sequence (fmap fromOrder =<< lookup "qN" txtMap)
where txtMap = mapMaybe splitOnEqual (splitOn "&" $ unpack txt)
这里 fromOrder
是类型为 Text -> Either Text Question
的内部函数,而 splitOn
来自 Data.List.Split
.
这些是我对 QuestionAPI
type QuestionAPI = "questions"
:> QueryParam "are" LotsOfQuestions
:> Get '[ JSON] [Answer]
与 API 交互的方式是通过这样的 link
http://localhost:8081/questions?are=q1=what&q2=where&q3=when