Elixir: graphql-elixir を試す

# 概要

Elixir 用の GraphQL パーサーは [awesome-graphql](https://github.com/chentsulin/awesome-graphql) によると３つあります。

1. [asonge/graphql](https://github.com/asonge/graphql)
2. [joshprice/graphql-elixir](https://github.com/joshprice/graphql-elixir)
3. [peburrows/plot](https://github.com/peburrows/plot)

この中で開発が一番活発な [graphql-elixir](https://github.com/joshprice/graphql-elixir) を調べました。


# クエリーのパース

GraphQL のパースは次のように行います。

```ex:hello
GraphQL.parse "{ hello }"
```

まずは [以前の記事](http://qiita.com/yasuhiro-okada-aktsk/items/2e4d3878ed0c62688913) で試した簡単なクエリーをパースしてみます。

```GraphQL
{game {
  id
}}
```

次のように [仕様に定義されている要素](http://facebook.github.io/graphql/) に分解されます。

```ex
[kind: :Document, loc: [start: 0],
 definitions: [[kind: :OperationDefinition, loc: [start: 0], operation: :query,
   selectionSet: [kind: :SelectionSet, loc: [start: 0],
    selections: [[kind: :Field, loc: [start: 0], name: 'game',
      selectionSet: [kind: :SelectionSet, loc: [start: 0],
       selections: [[kind: :Field, loc: [start: 0], name: 'id']]]]]]]]]
```

```mutation``` も正しくパースできます。

```GraphQL
mutation Mutation($input:CheckHidingSpotForTreasureInput!) {
  checkHidingSpotForTreasure(input:$input) {
    clientMutationId
  }
}
```

```ex
[kind: :Document, loc: [start: 0],
 definitions: [[kind: :OperationDefinition, loc: [start: 0],
   operation: :mutation, name: 'Mutation',
   variableDefinitions: [[kind: :VariableDefinition, loc: [start: 0],
     variable: [kind: :Variable, loc: [start: 0], name: 'input'],
     type: [kind: :NonNullType, loc: [start: 0],
      type: [kind: :NamedType, loc: [start: 0],
       name: 'CheckHidingSpotForTreasureInput']]]],
   selectionSet: [kind: :SelectionSet, loc: [start: 0],
    selections: [[kind: :Field, loc: [start: 0],
      name: 'checkHidingSpotForTreasure',
      arguments: [[kind: :Argument, loc: [start: 0], name: 'input',
        value: [kind: :Variable, loc: [start: 0], name: 'input']]],
      selectionSet: [kind: :SelectionSet, loc: [start: 0],
       selections: [[kind: :Field, loc: [start: 0],
         name: 'clientMutationId']]]]]]]]]
```

変数 ```$input``` も ```kind: :Variable``` としてパースできています。

変数への代入は後述の executor で実装されそうです。

他のパターンは ```graphql-elixir``` の [```test/graphql_parser_test.exs```](https://github.com/joshprice/graphql-elixir/blob/master/test/graphql_parser_test.exs)　で確認できます。

# schema のパース

schema のパースにも対応しており、こちらの [サンプル](https://github.com/yasuhiro-okada-aktsk/sample_relay) で ```npm run update-schema``` したときに生成される ```data/schema.graphql``` では次のようになります。

```GraphQL:data/schema.graphql
input CheckHidingSpotForTreasureInput {
  id: ID!
  clientMutationId: String!
}

type CheckHidingSpotForTreasurePayload {
  hidingSpot: HidingSpot
  game: Game
  clientMutationId: String!
}

＜中略＞

type Query {
  node(id: ID!): Node
  game: Game
}
```

```ex
[kind: :Document, loc: [start: 0],
 definitions: [[kind: :InputObjectTypeDefinition, loc: [start: 0],
   name: 'CheckHidingSpotForTreasureInput',
   fields: [[kind: :InputValueDefinition, loc: [start: 0], name: 'id',
     type: [kind: :NonNullType, loc: [start: 0],
      type: [kind: :NamedType, loc: [start: 0], name: 'ID']]],
    [kind: :InputValueDefinition, loc: [start: 0], name: 'clientMutationId',
     type: [kind: :NonNullType, loc: [start: 0],
      type: [kind: :NamedType, loc: [start: 0], name: 'String']]]]],
  [kind: :ObjectTypeDefinition, loc: [start: 0],
   name: 'CheckHidingSpotForTreasurePayload',
   fields: [[kind: :FieldDefinition, loc: [start: 0], name: 'hidingSpot',
     type: [kind: :NamedType, loc: [start: 0], name: 'HidingSpot']],
    [kind: :FieldDefinition, loc: [start: 0], name: 'game',
     type: [kind: :NamedType, loc: [start: 0], name: 'Game']],
    [kind: :FieldDefinition, loc: [start: 0], name: 'clientMutationId',
     type: [kind: :NonNullType, loc: [start: 0],
      type: [kind: :NamedType, loc: [start: 0], name: 'String']]]]],

＜中略＞

  [kind: :ObjectTypeDefinition, loc: [start: 0], name: 'Query',
   fields: [[kind: :FieldDefinition, loc: [start: 0], name: 'node',
     arguments: [[kind: :InputValueDefinition, loc: [start: 0], name: 'id',
       type: [kind: :NonNullType, loc: [start: 0],
        type: [kind: :NamedType, loc: [start: 0], name: 'ID']]]],
     type: [kind: :NamedType, loc: [start: 0], name: 'Node']],
    [kind: :FieldDefinition, loc: [start: 0], name: 'game',
     type: [kind: :NamedType, loc: [start: 0], name: 'Game']]]]]]
```

クエリーと schema の整合性チェックの機能は今のところ無いようです。

# executor

schema に elixir の関数を設定しておくことで、クエリーので指定した変数を関数に渡すことできるようになるようです。
※ 最新の Version 0.0.2 と master ブランチでは仕様が変わっているので、まだ大きく変わるかもしれません。

```ex
defmodule TestSchema do
    def schema do
      %GraphQL.Schema{
        query: %GraphQL.ObjectType{
          name: "RootQueryType",
          fields: [
            %GraphQL.FieldDefinition{
              name: "greeting",
              type: "String",
              resolve: &greeting/1,
            }
          ]
        }
      }
    end
    
    def greeting(name: name), do: "Hello, #{name}!"
    def greeting(_), do: greeting(name: "world")
  end
      
  test "query arguments" do
    query = "{ greeting }"
    assert GraphQL.execute(TestSchema.schema, query) == [data: [greeting: "Hello, world!"]]

    query = "{ greeting(name: \"Elixir\") }"
    assert GraphQL.execute(TestSchema.schema, query) == [data: [greeting: "Hello, Elixir!"]]
  end
```

# 最後に

他の２つに比べてコミット数も多く最近も開発されていますが、簡単に使えるようになるにはもう少し時間がかかりそうです。

[Plot](https://github.com/peburrows/plot) も軽くさわってみましたが、次のような問題がありました。

* variables があるとエラーになる
* schema のパースでエラー
* パラメータにオブジェクトを渡すとエラー (文字と数字はok)

こちらはさらに時間がかかりそうです。

サンプルコードはこちらです。

* [sample_elixir_graphql-elixir](https://github.com/yasuhiro-okada-aktsk/sample_elixir_graphql-elixir)
* [sample_elixir_plot](https://github.com/yasuhiro-okada-aktsk/sample_elixir_plot)