question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Cannot use asyncResult computation expressions in resolvers

See original GitHub issue

Description

I am trying to use asyncResult computation expressions (from the excellent FsToolkit.ErrorHandling) in an AsyncField. This results in a run-time error when building the schema.

Repro steps

Here is a simplified script that shows the problem. You can run it with dotnet fsi ./Repro.fsx

#r "nuget: FSharp.Data.GraphQL.Server, 1.0.7"
#r "nuget: FsToolkit.ErrorHandling, 2.2.0"

open System
open FSharp.Data.GraphQL

type Product =
  {
    ID : Guid
    Name : string
    Weight : int
  }

module Schema =

  open FsToolkit.ErrorHandling
  open FSharp.Data.GraphQL.Types

  let private tryParseGuid (x : string) =
    match System.Guid.TryParse x with
    | (true, g) -> Ok g
    | _ -> Error (sprintf "Invalid GUID: %s" x)

  let productType =
    Define.Object
      (
        "Product",
        [
          Define.Field ("id", ID, fun ctx x -> x.ID)
          Define.Field ("name", String, fun ctx x -> x.Name)
          Define.Field ("weight", Int, fun ctx x -> x.Weight)
        ]
      )

  let queryType =
    Define.Object
      (
        name = "Query",
        fields =
          [
            Define.AsyncField
              (
                "product",
                Nullable productType,
                "Fetch a product by ID",
                [
                  Define.Input ("id", ID)
                ],
                (fun ctx (x : Map<Guid, Product>) ->
                  async {
                    let! result =
                      asyncResult {
                        let! id =
                          ctx.Args
                          |> Map.tryFind "id"
                          |> Result.requireSome "Missing id argument"
                          |> Result.bind (fun x ->
                            match x with
                            | :? System.Guid as g -> Ok g
                            | :? System.String as s -> tryParseGuid s
                            | x -> Error (sprintf "Invalid id: %A" x)
                          )

                        let product = Map.tryFind id x

                        return product
                      }

                    match result with
                    | Ok x ->
                      return x
                    | Error message ->
                      return failwith message
                  })
              )
          ]
      )

let schema = Schema (Schema.queryType)

let executor = Executor (schema)

let crateOfOranges =
  {
    ID = Guid.NewGuid ()
    Name = "Crate of Oranges"
    Weight = 123
  }

let data =
  Map.empty
  |> Map.add crateOfOranges.ID crateOfOranges

async {
  let plan =
    Parser.parse "query product($id : ID!) { product(id: $id) { id, name, weight } }"

  let variables =
    Map.empty
    |> Map.add "id" (crateOfOranges.ID |> string :> obj)

  let! result =
    executor.AsyncExecute (plan, data, variables)

  printfn "%A" result
}
|> Async.RunSynchronously

Expected behavior

Output like:

{ Content =
           Direct
             (seq
                [[documentId, 303956704];
                 [data, { product: { id: d63d5b1a-31ee-4c61-a68c-06c7b576afcc,
			name: "Crate of Oranges",
			weight: 123 } }]],
              [])
  Metadata = map [] }

Actual behavior

Run-time error related to quotations. I’m not sure what’s going on since my code doesn’t appear to use quotations!

System.InvalidOperationException: Could not bind function AsyncResultBuilder.Source in type FsToolkit.ErrorHandling.AsyncResultCEExtensions
   at Microsoft.FSharp.Quotations.PatternsModule.fail@1118[a](Type ty, String nm, Unit unitVar0) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1118
   at Microsoft.FSharp.Quotations.PatternsModule.bindModuleFunctionWithCallSiteArgs$cont@1110(Type ty, String nm, Type[] argTypes, Type[] tyArgs, Unit unitVar) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1169
   at Microsoft.FSharp.Quotations.PatternsModule.bindModuleFunctionWithCallSiteArgs(Type ty, String nm, FSharpList`1 argTypes, FSharpList`1 tyArgs) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1105
   at Microsoft.FSharp.Quotations.PatternsModule.u_ModuleDefn@1572-1.Invoke(FSharpList`1 argTypes, FSharpList`1 tyargs) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1576
   at Microsoft.FSharp.Quotations.PatternsModule.u_constSpec@1636.Invoke(FSharpList`1 argTypes, FSharpList`1 tyargs) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1636
   at Microsoft.FSharp.Quotations.PatternsModule.u_Expr@1492.Invoke(BindingEnv env) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1501
...

Known workarounds

Write in async computation expressions only. This can lead to less readable code in some situations.

                  async {
                    let id =
                      ctx.Args
                      |> Map.find "id"
                      |> (
                        function
                        | :? System.Guid as g -> g
                        | :? System.String as s -> System.Guid.Parse s
                        | x -> failwithf "Invalid id: %A" x
                      )

                    let product = Map.tryFind id x

                    return product
                  })

I would appreciate any work-arounds that allow the use of asyncResult.

Related information

  • Operating system: Ubuntu 20.04
  • Nuget packages: FSharp.Data.GraphQL.Server (1.0.7), FsToolkit.ErrorHandling (2.2)
  • .NET Runtime, CoreCLR or Mono Version
dotnet --version
5.0.103

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
jberzycommented, May 4, 2021

This will work:

#r "nuget: FSharp.Data.GraphQL.Server, 1.0.7"
#r "nuget: FsToolkit.ErrorHandling, 2.2.0"

open System
open FSharp.Data.GraphQL

type Product =
  {
    ID : Guid
    Name : string
    Weight : int
  }

module Schema =

  open FsToolkit.ErrorHandling
  open FSharp.Data.GraphQL.Types

  let private tryParseGuid (x : string) =
    match System.Guid.TryParse x with
    | (true, g) -> Ok g
    | _ -> Error (sprintf "Invalid GUID: %s" x)

  let productType =
    Define.Object
      (
        "Product",
        [
          Define.Field ("id", ID, fun ctx x -> x.ID)
          Define.Field ("name", String, fun ctx x -> x.Name)
          Define.Field ("weight", Int, fun ctx x -> x.Weight)
        ]
      )

  let getProduct (ctx: ResolveFieldContext) x =
    asyncResult {
        let! id =
          ctx.Args
          |> Map.tryFind "id"
          |> Result.requireSome "Missing id argument"
          |> Result.bind (fun x ->
            match x with
            | :? System.Guid as g -> Ok g
            | :? System.String as s -> tryParseGuid s
            | x -> Error (sprintf "Invalid id: %A" x)
          )
        let product = Map.tryFind id x
        return product
      }


  let queryType =
    Define.Object
      (
        name = "Query",
        fields =
          [
            Define.AsyncField
              (
                "product",
                Nullable productType,
                "Fetch a product by ID",
                [
                  Define.Input ("id", ID)
                ],
                (fun ctx (x : Map<Guid, Product>) ->
                  async {
                    let! result = getProduct ctx x
                    match result with
                    | Ok x ->
                      return x
                    | Error message ->
                      return failwith message
                  })
              )
          ]
      )

let schema = Schema (Schema.queryType)

let executor = Executor (schema)

let crateOfOranges =
  {
    ID = Guid.NewGuid ()
    Name = "Crate of Oranges"
    Weight = 123
  }

let data =
  Map.empty
  |> Map.add crateOfOranges.ID crateOfOranges

async {
  let plan =
    Parser.parse "query product($id : ID!) { product(id: $id) { id, name, weight } }"

  let variables =
    Map.empty
    |> Map.add "id" (crateOfOranges.ID |> string :> obj)

  let! result =
    executor.AsyncExecute (plan, data, variables)

  printfn "%A" result
}
|> Async.RunSynchronously
0reactions
xperiandricommented, May 5, 2021

Yes, they are. Allow to use directly

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using let! inside match statements causes compilation error
As already explained, the problem is that asynchronous workflows only allow certain kinds of nestings - you cannot use let! inside an ...
Read more >
Use binding in an asyncResult computation expression #2
I think I'm running into a bug where use-bound values in asyncResult computation expressions are disposed prematurely.
Read more >
Computation Expression - FsToolkit.ErrorHandling - GitBook
A login flow can be implemented as below using the asyncResult CE and a few helpers: type LoginError = | InvalidUser. | InvalidPwd....
Read more >
When should computational expressions be used? : r/fsharp
The pure logic is expressed in result{} CEs, the I/O has a lot of taskResult{}/asyncResult{} code. It's a killer feature.
Read more >
Computation expressions: Introduction
Computation expressions seem to have a reputation for being abstruse and difficult to understand. On one hand, they're easy enough to use.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found