【问题标题】:graphql reason-apollo - recursive parsing of optionsgraphql reason-apollo - 选项的递归解析
【发布时间】:2018-07-31 22:51:30
【问题描述】:

我正在使用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 时,有什么方法可以减少选项冗长?

【问题讨论】:

    标签: django graphql graphene-python reason reasonml


    【解决方案1】:

    错误消息中的大型类型可能会让人很难看到发生了什么,因此将其归结为类型差异会很有帮助。它抱怨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 上完成。这是 Glenn 的另一个库,refetch

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

      【讨论】:

      • 非常酷,感谢您提供详细信息!我一直对 bs-json 很好奇,但还没有涉足。你在neilkistner.com 上使用原因吗?看起来你可能在那里使用 github API。你见过有人用bs-jsonreason-apollo吗?
      • 我目前正在重写我的个人网站以使用 Reason。好吧,它已经完成了,但是我正在完成我拥有的一些服务器的东西,我正在尝试在 Reason 中做这些,但是前端已经完成了。我还没有将这些更改推送到 GitHub。我个人还没有看到有人将这两个库结合起来,但是在 Reason discord 中有很多关于 reason-apollo 的讨论,所以在那里问可能会更好。
      • @P.MyerNore 我不确定你为什么需要 bs-jsonreason-apollographql_ppx 提供了一个解析函数来解析结果到 Js.t 所以这里不需要bs-json
      猜你喜欢
      • 2021-07-25
      • 2016-12-12
      • 2021-01-25
      • 2017-08-25
      • 2019-11-06
      • 2020-07-22
      • 2021-09-25
      • 2020-09-12
      • 2020-01-28
      相关资源
      最近更新 更多