Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

graphql reason-apollo - recursive parsing of options

I'm using Reason-Apollo to parse a pretty nested GraphQL response from my server. I'm having trouble parsing the hairy tree of options returned from my GraphQL server (I'm using django-graphene).

Here is the GraphQL query and the Reason React module using Reason Apollo:

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>
  }
};

Here is the return data from the GraphQL query from GraphiQL:

{
  "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
              }
            }
          }, ...

I have a syntax error somewhere ...

  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

My immediate question: what is the error here?

On a deeper level, parsing all of these options to render the desired response seems like it would generally produce pretty unclear code. So what is the common paradigm around parsing options in JS when using ReasonML / OCaml? Is there an idiomatic way to get all of the options that will be there most of the time? Should I be creating an object type or a record type and parsing into those, and then rendering from the "known" object or record structures?

Or perhaps my graphql_schema.json and endpoint needs to have more required options?

Also, I'm using Relay's GraphQL convention of having edges { node { ... node fields ... } }, and it seems like if there are any edges then there should be at least one node. Is there any way to cut down on the option verbosity when using relay-style GraphQL?

like image 237
Myer Avatar asked Feb 21 '18 11:02

Myer


1 Answers

The large types in the error message can make it hard to see what's going on, so it's helpful to boil it down to just the type differences. It's complaining about the messages field that it says has the type:

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

while it's actually used as:

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

So edges is actually a non-optional array whereas you are using it as an option(Js.Array.t). You do not need to check if it is Some, perhaps just if it is an empty array []. Then you'll want to use Array.map to handle the non-empty case.

Try going through and fixing your usage so that the inferred type matches the the type you're getting from your query until it compiles successfully.

like image 73
Zach Ploskey Avatar answered Oct 07 '22 13:10

Zach Ploskey