graphql reason-apollo - 递归解析选项

问题描述 投票:5回答:2

我正在使用Reason-Apollo从我的服务器解析一个非常嵌套的GraphQL响应。我无法解析从GraphQL服务器返回的毛茸茸树的选项(我正在使用django-graphene)。

这是使用Reason Apollo的GraphQL查询和Reason React模块:

module GroupQuery = [%graphql {|
query GetChatGroup($chatGroupId: ID!){
  chatGroup(id: $chatGroupId) {
    id
    users {
      edges {
        node {
          id
          name
          isCurrentUser
        }
      }
    }
    messages {
      edges {
        node {
          id
          text
          author {
            name
            abbreviation
            photoUrl
            isCurrentUser
          }
        }
      }
    }
  }
}
|}];

/*eventually will be a reducerComponent*/
let component = ReasonReact.statelessComponent("RechatWindow");

module Query = RechatApollo.Instance.Query;

let parseMessages = chatGroup =>
  switch chatGroup {
  | Some(chatGroup) =>
    switch chatGroup##messages {
    | Some(messages) =>
      let edges = messages##edges;
      switch edges {
      | Some(edges) =>
        let parsedNodes =
          Js.Array.map(
            node =>
              switch node {
              | Some(node) =>
                let id = node##id;
                let text = node##text;
                let author = node##author;
                switch (id, text, author) {
                | (Some(id), Some(text), Some(author)) =>
                  let name = author##name;
                  let abbrev = author##abbreviation;
                  let isCurrentUser = author##isCurrentUser;
                  switch (name, abbrev, isCurrentUser) {
                  | (Some(name), Some(abbrev), Some(isCurrentUser)) =>
                    id ++ " - " ++ text ++ " - " ++ name ++ " - " ++ abbrev ++ " - "
                  | _ => "Error retrieving message 3"
                  };
                | _ => "Error retrieving message 2"
                };
              | _ => "Error retrieving message 1"
              },
            edges
          );
        parsedNodes;
      | None => [||]
      };
    | None => [||]
    };
  | None => [||]
  };

let make = (_children) => {
  ...component,
  render: (_) => {
    let unexpectedError = <div> (ReasonReact.stringToElement("There was an internal error")) </div>;
      let groupQuery = GroupQuery.make(~chatGroupId="Q2hhdEdyb3VwVHlwZTox", ());
      <Query query=groupQuery>
      ...((response, parse) => {
        switch response {
           | Loading => <div> (ReasonReact.stringToElement("Loading")) </div>
           | Failed(error) => <div> (ReasonReact.stringToElement(error)) </div>
           | Loaded(result) => {
              let chatGroup = parse(result)##chatGroup;
              let parsedMessages = parseMessages(chatGroup);
               <ul>
                 (
                   ReasonReact.arrayToElement(
                     Array.map(message => <li> (ste(message)) </li>, parsedMessages)
                   )
                 )
               </ul>;
           }
        }
       })
    </Query>
  }
};

以下是来自GraphiQL的GraphQL查询的返回数据:

{
  "data": {
    "chatGroup": {
      "id": "Q2hhdEdyb3VwVHlwZTox",
      "users": {
        "edges": [
          {
            "node": {
              "id": "VXNlclR5cGU6MzQ=",
              "name": "User 1",
              "isCurrentUser": false
            }
          },
          {
            "node": {
              "id": "VXNlclR5cGU6MQ==",
              "name": "User 2",
              "isCurrentUser": true
            }
          }
        ]
      },
      "messages": {
        "edges": [
          {
            "node": {
              "id": "Q2hhdE1lc3NhZ2VUeXBlOjE=",
              "text": "my first message",
              "author": {
                "name": "User 1",
                "abbreviation": "U1",
                "photoUrl": "",
                "isCurrentUser": true
              }
            }
          }, ...

我某处有语法错误......

  137 ┆ | Loaded(result) => {
  138 ┆    let chatGroup = parse(result)##chatGroup;
  139 ┆    let parsedMessages = parseMessages(chatGroup);
  140 ┆     <ul>
  141 ┆       (

  This has type:
    option(Js.t({. id : string,
                  messages : option(Js.t({. edges : array(option(Js.t(
                                                                 {. node : 
                                                                   option(
                                                                   Js.t(
                                                                   {. author : 
                                                                    Js.t(
                                                                    {. abbreviation : 
                                                                    option(
                                                                    string),
                                                                    isCurrentUser : 
                                                                    option(
                                                                    Js.boolean),
                                                                    name : 
                                                                    option(
                                                                    string),
                                                                    photoUrl : 
                                                                    option(
                                                                    string) }),
                                                                    id : 
                                                                    string,
                                                                    text : 
                                                                    string })) }))) })),
                  users : option(Js.t({. edges : array(option(Js.t({. node : 
                                                                    option(
                                                                    Js.t(
                                                                    {. id : 
                                                                    string,
                                                                    isCurrentUser : 
                                                                    option(
                                                                    Js.boolean),
                                                                    name : 
                                                                    option(
                                                                    string) })) }))) })) }))
  But somewhere wanted:
    option(Js.t({.. messages : option(Js.t({.. edges : option(Js.Array.t(
                                                              option(
                                                              Js.t({.. author : 
                                                                    option(
                                                                    Js.t(
                                                                    {.. abbreviation : 
                                                                    option(
                                                                    string),
                                                                    isCurrentUser : 
                                                                    option('a),
                                                                    name : 
                                                                    option(
                                                                    string) })),
                                                                    id : 
                                                                    option(
                                                                    string),
                                                                    text : 
                                                                    option(
                                                                    string) })))) })) }))
  Types for method edges are incompatible

我的直接问题是:这里的错误是什么?

在更深层次上,解析所有这些选项以呈现所需的响应似乎通常会产生非常不清楚的代码。那么在使用ReasonML / OCaml时,在JS中解析选项的常见范例是什么?是否有一种惯用的方法来获得大多数时间都会出现的所有选项?我应该创建一个对象类型或记录类型并解析它们,然后从“已知”对象或记录结构进行渲染?

或许我的graphql_schema.json和端点需要更多必需的选项?

另外,我正在使用具有edges { node { ... node fields ... } }的Relay的GraphQL约定,看起来如果有任何边缘则应该至少有一个节点。使用中继式GraphQL时,有没有办法减少选项的详细程度?

django graphql graphene-python reason reasonml
2个回答
3
投票

错误消息中的大类型可能使得很难看到正在发生的事情,因此将其归结为类型差异是有帮助的。它抱怨messages字段,它说有类型:

option(Js.t({. edges : array(option(Js.t(...

虽然它实际上用作:

option(Js.t({.. edges : option(Js.Array.t(Js.t(...

所以edges实际上是一个非可选数组,而你用它作为option(Js.Array.t)。你不需要检查它是否是Some,也许只是它是一个空数组[]。那么你将要使用Array.map来处理非空案例。

尝试浏览并修复您的用法,以便推断的类型与您从查询中获得的类型匹配,直到它成功编译为止。


2
投票

我能说的最好是你正在解析option(Js.Array.t),但是当你去渲染时,你将它称为array(option(Js.t))。让你更接近解决的一个选择是将渲染函数中的Array.map更改为Js.Array.map

既然你提到了替代方案,我将在下面分享我正在做的事情:


我正在使用bs-json来解析来自GitHub API的GraphQL响应。

这是查询:

let query = {|
  query {
    viewer {
      projects: repositories ( orderBy: { direction: DESC, field: STARGAZERS }, affiliations: [ OWNER ], first: 100, isFork: false ) {
        nodes {
          ...RepoFields
        }
      }
      contributions1: pullRequests( first: 100, states: [ MERGED ] ) {
        nodes {
          repository {
            ...RepoFields
          }
        }
      },
      contributions2: pullRequests( last: 100, states: [ MERGED ] ) {
        nodes {
          repository {
            ...RepoFields
          }
        }
      }
    }
  }

  fragment RepoFields on Repository {
    name
    nameWithOwner
    shortDescriptionHTML( limit: 100 )
    stargazers {
      totalCount
    }
    url
  }
|};

然后我构建了一个小解码器模块:

module Decode = {
  open Json.Decode;

  let repo = ( ~nameField="name", json ) => {
    name: json |> field(nameField, string),
    stars: json |> at([ "stargazers", "totalCount" ], int),
    description: json |> field("shortDescriptionHTML", string),
    url: json |> field("url", string),
  };

  let repo2 = json =>
    json |> field("repository", repo(~nameField="nameWithOwner"));

  let rec uniq = ( free, lst ) =>
    switch lst {
    | [] => free
    | [ hd, ...tl ] =>
      switch ( List.mem(hd, tl) ) {
      | true => uniq(free, tl)
      | false => uniq([ hd, ...free ], tl)
      }
    };

  let all = json => {
    contributions: (
        (json |> at([ "data", "viewer", "contributions1", "nodes" ], list(repo2))) @
        (json |> at([ "data", "viewer", "contributions2", "nodes" ], list(repo2)))
      )
        |> uniq([])
        |> List.sort(( left, right ) => right.stars - left.stars),
    projects: json |> at([ "data", "viewer", "projects", "nodes" ], list(repo)),
  };
};

哪个解析成记录类型:

type github = {
  description: string,
  name: string,
  stars: int,
  url: string,
};

type gh = {
  contributions: list(github),
  projects: list(github),
};

这是我的抓取器:

let get =
  Resync.(Refetch.(
    request(`POST, "https://api.github.com/graphql",
      ~headers=[
        `Authorization(`Bearer("******")),
        `ContentType("application/graphql")
      ],
      ~body=`Json(body))
    |> fetch
      |> Future.flatMap(
          fun | Response.Ok(_, response) => Response.json(response)
              | Response.Error({ reason }, _) => raise(FetchError(reason)))
      |> Future.map(Decode.all)
  ));

^解码是在Future.map那里完成的。这是格伦,refetch的另一个图书馆。

而且我将contributionsprojects作为道具传递到我的应用程序中。

© www.soinside.com 2019 - 2024. All rights reserved.