GitHub GraphQL client whose answers are kept in a raw store
3.3 kB · 77 lines
Markdown
at main

Errors of one selection of a batch #

GitHub answers a query whose aliased selections fail one by one with the selections it could resolve under data and an errors member, each error naming the alias it is about as the first step of its path (GraphQL, section 7.1.2). A repository GitHub does not know is null with an error of type NOT_FOUND; one it refuses to show, such as one behind an organisation's SAML enforcement, is null with an error of type FORBIDDEN. The two need different handling: the first is gone, the second should be asked again.

A stub transport answers four aliases: one resolved, one not found, one forbidden, and one whose nested field failed.

# let with_client f =
    let answer =
      {|{"data":{"a0":{"id":"R_1","nameWithOwner":"ocaml/dune"},
                 "a1":null,"a2":null,"a3":{"id":"R_3","parent":null}},
         "errors":[
           {"type":"NOT_FOUND","path":["a1"],
            "message":"Could not resolve to a Repository with the name 'ocaml/gone'."},
           {"type":"FORBIDDEN","path":["a2"],
            "message":"Resource protected by organization SAML enforcement."},
           {"path":["a3","parent"],
            "message":"Something went wrong while executing your query."}]}|}
    in
    Eio_mock.Backend.run @@ fun () ->
    let clock = Eio_mock.Clock.make () in
    let store = Cas_store.in_memory ~clock () in
    let transport ~headers:_ _ =
      Ok (Github_query.response ~status:200 answer)
    in
    f (Github_query.v ~clock ~store ~token:"t" transport);;
val with_client : (Github_query.t -> 'a) -> 'a = <fun>

Each repository gets its own answer: a value, Not_found, or the errors about its alias alone.

# let show = function
    | Ok j -> "ok " ^ Json.Value.to_string j
    | Error Github_query.Not_found -> "not found"
    | Error (Github_query.Graphql es) -> "errors " ^ Json.Value.to_canonical es
    | Error e -> Fmt.str "%a" Github_query.pp_error e;;
val show : (Json.t, Github_query.error) result -> string = <fun>
# with_client (fun gq ->
    Github_query.batch gq ~policy:(Cas_store.Policy.Snapshot "d")
      (fun n -> Fmt.str "repository(owner:\"ocaml\" name:%S){id nameWithOwner}" n)
      [ "dune"; "gone"; "saml"; "opam" ]
    |> List.map (fun (n, r) -> (n, show r)));;
- : (string * string) list =
[("dune", "ok {\"id\":\"R_1\",\"nameWithOwner\":\"ocaml/dune\"}");
 ("gone", "not found");
 ("saml",
  "errors [{\"message\":\"Resource protected by organization SAML enforcement.\",\"path\":[\"a2\"],\"type\":\"FORBIDDEN\"}]");
 ("opam",
  "errors [{\"message\":\"Something went wrong while executing your query.\",\"path\":[\"a3\",\"parent\"]}]")]

A probe tells a repository GitHub does not know, Ok None, from one whose probe failed, an error.

# with_client (fun gq ->
    Github_query.Probe.repos gq ~policy:(Cas_store.Policy.Snapshot "d")
      [ ("ocaml", "dune"); ("ocaml", "gone"); ("ocaml", "saml") ]
    |> List.map (fun ((_, n), r) ->
           ( n,
             match r with
             | Ok None -> "gone"
             | Ok (Some _) -> "seen"
             | Error _ -> "unknown" )));;
- : (string * string) list =
[("dune", "seen"); ("gone", "gone"); ("saml", "unknown")]