在管理错误的同时在管道中链接 REST 调用

Chaining REST calls in a pipeline while managing errors

来自 nodejs,在那里我可以使用 Promises and then operator 链接异步事件我正在尝试探索如何在惯用的 F# 中完成事情。

我尝试链接的调用是对某些实体的 HTTP 休息调用,从创建到更新到上传图像到发布。

函数组合表示一个函数的输出应该与要组合的第二个函数的输入相匹配,在我的例子中,公共输入和输出将是 string,即 JSON 序列化字符串为所有这些功能的输入和输出。

我了解到您可以使用 >> 运算符组合函数。理想情况下,函数不应抛出错误,但 IO 会发生错误,例如,在这种情况下,如果我要创建的实体的 ID 存在,该怎么办等等。

未知和问题是如果在链接序列期间发生错误会发生什么情况,调用者如何通过描述消息知道出了什么问题?操作可能会在链序列的中间或接近尾部或刚开始时失败。

我期望这些函数在出错时停止执行链,并且 return 将错误消息发送给调用者。错误消息也是 JSON string 所以你知道函数的输入和输出之间没有不兼容。

我也看了 Choice 但不确定那是否是我应该追求的方向。

代码不一定完整,我正在寻找的只是一个进一步研究的方向,以获得答案并可能改进这个问题。这是一些开始的代码。

let create schema =
    // POST request
    """{"id": 1, "title": "title 1"}""" // result output

let update schema =
    // PUT request, update title
    """{"id": 1, "title": "title 2"}""" // output

let upload schema = 
    // PUT request, upload image and add thumbnail to json
    """{"id": 1, "title": "title 2", "thumbnail": "image.jpg"}""" 

let publish schema =
    // PUT request, publish the entity, add url for the entity
    if response.StatusCode <> HttpStatusCode.OK then
        """{"code": "100", "message": "file size above limit"}"""
    else
        """{"id": 1, "title": "title 2", "thumbnail": "image.jpg", "url": "http://example.com/1"}"""

let chain = create >> update >> upload >> publish

编辑 - 尝试

正在尝试参数化上传部分的图片缩略图

let create (schema: string) : Result<string,string> =
    Ok """{"id": 1, "title": "title 1"}""" // result output

let update (schema: string) : Result<string,string>  =
    Ok """{"id": 1, "title": "title 2"}""" // output

let upload2 (img: string) (schema: string) : Result<string,string> =
    printf "upload image %s\n" img
    let statusCode = HttpStatusCode.OK
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg"}"""
    | x -> Error (sprintf "%A happened" x)

let publish (schema: string) =
    let statusCode = HttpStatusCode.InternalServerError
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg", "url": "http://example.com/1"}"""
    | _ -> Error """{"code": "100", "message": "couldn't publish, file size above limit"}"""

let chain = create >> Result.bind update >> Result.bind (upload2 "image.jpg") >> Result.bind publish

解决这个问题的一个很好的通用方法是将函数的 return 值包装在 Choice/Either-like 类型中,并使用 higher-order 函数来 bind 他们在一起,这样 失败 propagates/short-circuits 有一些有意义的数据。 F# 有一个 Result 类型和一个 bind 函数,可以像这样使用:

type MyResult = Result<string,string>
let f1 x : MyResult = printfn "%s" x; Ok "yep"
let f2 x : MyResult = printfn "%s" x; Ok "yep!"
let f3 x : MyResult = printfn "%s" x; Error "nope :("
let fAll = f1 >> Result.bind f2 >> Result.bind f3

> fAll "howdy";;
howdy
yep
yep!
[<Struct>]
val it : Result<string,string> = Error "nope :("

前两个函数成功,但第三个失败,因此您得到一个 Error 值。

另请参阅 Railway-oriented programming 上的这篇文章。

更新 以更具体地说明您的示例:

let create (schema: string) : Result<string,string> =
    Ok """{"id": 1, "title": "title 1"}""" // result output
let update (schema: string) : Result<string,string>  =
    Ok """{"id": 1, "title": "title 2"}""" // output
let upload (schema: string) = 
    let statusCode = HttpStatusCode.OK
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg"}"""
    | x -> Error (sprintf "%A happened" x)
let publish (schema: string) =
    let statusCode = HttpStatusCode.InternalServerError
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg", "url": "http://example.com/1"}"""
    | _ -> Error """{"code": "100", "message": "file size above limit"}"""
let chain =
  create >> Result.bind update >> Result.bind upload >> Result.bind publish