6

我正在使用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和端点需要更多必需的选项?

另外,我正在使用 Relay 的 GraphQL 约定,edges { node { ... node fields ... } }如果有任何边,那么应该至少有一个节点。使用中继式 GraphQL 时,有什么方法可以减少选项的冗长性?

4

2 回答 2

3

错误消息中的大类型可能会让人很难看清发生了什么,因此将其归结为类型差异会很有帮助。它抱怨messages它所说的字段具有以下类型:

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

虽然它实际上用作:

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

Soedges实际上是一个非可选数组,而您将其用作option(Js.Array.t). 您不需要检查它是否是Some,也许只是它是一个空数组[]。然后你会想用它Array.map来处理非空的情况。

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

于 2018-02-26T21:58:33.690 回答
2

我能说的最好的是你正在解析option(Js.Array.t),但是当你去渲染时,你将它引用为array(option(Js.t)). 让您更接近解决的一种选择是将渲染函数中的Array.mapto更改为。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. 这是 Glenn 的另一个库,refetch

我将contributions以上projects内容作为道具传递到我的应用程序中。

于 2018-02-26T21:50:08.670 回答