クエリーに合わせた構造体を書くだけでGithub GraphQL API を扱えるGoのライブラリ[githubql]

golangとGraphQLの勉強中。

今回はGoでGithub APIのGraphQLのクライアント githubql を使ってみました。

githubqlの使い方

基本的にはGraphQLのqueryやmutationに合わせたGoの構造体を定義するだけです。

まずgithubqlのページに載っているサンプルを見てみましょう。 以下のようなシンプルなqueryの場合、

query {
    viewer {
        login
        createdAt
    }
}

このように構造体を定義し、

var query struct {
    Viewer struct {
        Login     githubql.String
        CreatedAt githubql.DateTime
    }
}

client.Queryを実行すると戻り値にGithubのGraphQL APIの結果が入っています。

err := client.Query(context.Background(), &query, nil)
if err != nil {
    // Handle error.
}
fmt.Println("    Login:", query.Viewer.Login)
fmt.Println("CreatedAt:", query.Viewer.CreatedAt)

今回試すクエリー(GraphQL Explorerの場合)

GithubのGraphQL APIはこちらで簡単に実行結果を確認することができます。 https://developer.github.com/v4/explorer/

今回はGithubにあるGraphQL関連のRepositoryとその言語を5件抽出するクエリーを書きました。

query {
    search(query: "GraphQL", first: 5, type: REPOSITORY) {
      edges {
        node {
          ... on Repository {
            nameWithOwner,
            url,
            languages(first:5) {
              edges {
                node {
                  ... on Language {
                    name,
                    color
                  }
                }
              }
            }
          }
        }
      }
    }
  }

結果

{
  "data": {
    "search": {
      "edges": [
        {
          "node": {
            "nameWithOwner": "facebook/graphql",
            "url": "https://github.com/facebook/graphql",
            "languages": {
              "edges": [
                {
                  "node": {
                    "name": "Shell",
                    "color": "#89e051"
                  }
                }
              ]
            }
          }
        },
        {
          "node": {
            "nameWithOwner": "graphql-go/graphql",
            "url": "https://github.com/graphql-go/graphql",
            "languages": {
              "edges": [
                {
                  "node": {
                    "name": "Go",
                    "color": "#375eab"
                  }
                }
              ]
            }
          }
        },

・・・以下省略

githubqlで書く場合

上記のクエリーをgithubqlを使ってGoで書くと以下のようになります。

Githubトークンを使ってclientを初期化します。

src := oauth2.StaticTokenSource(
    &oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
)
httpClient := oauth2.NewClient(context.Background(), src)

client := githubql.NewClient(httpClient)

続いてRepositoryとLanguageの構造体を定義します。

type Language struct {
    Name  githubql.String
    Color githubql.String
}

type Repository struct {
    NameWithOwner   githubql.String
    Url     githubql.String
    Languages struct {
        Nodes []struct {
            Language `graphql:"... on Language"`
        }
    } `graphql:"languages(first: 5)"`
}

GraphQLの引数を指定したいときは構造体にgraphql:"... on Language"のようにタグをつけます。

Searchも構造体で定義します。最初はここが分からなくて少しハマりました。先程のRepositoryの定義もSearchの中に入れ子で書けますが、今回は分けて書いてます。

var query struct {
    Search struct {
        Nodes []struct {
            Repository `graphql:"... on Repository"`
        }
    } `graphql:"search(first: 5, query: $q, type: $searchType)"`
}

Searchに渡すクエリパラメータを利用する時は、mapを作って渡します。

variables := map[string]interface{}{
    "q": githubql.String("GraphQL"),
    "searchType":  githubql.SearchTypeRepository,
}

err := client.Query(context.Background(), &query, variables)

以下が全体のソースです。

package main

import (
    "golang.org/x/oauth2"
    "os"
    "fmt"
    "context"
    "github.com/shurcooL/githubql"
)

func main() {
    src := oauth2.StaticTokenSource(
        &oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
    )
    httpClient := oauth2.NewClient(context.Background(), src)

    client := githubql.NewClient(httpClient)

    type Language struct {
        Name  githubql.String
        Color githubql.String
    }

    type Repository struct {
        NameWithOwner   githubql.String
        Url     githubql.String
        Languages struct {
            Nodes []struct {
                Language `graphql:"... on Language"`
            }
        } `graphql:"languages(first: 5)"`
    }

    var query struct {
        Search struct {
            Nodes []struct {
                Repository `graphql:"... on Repository"`
            }
        } `graphql:"search(first: 5, query: $q, type: $searchType)"`
    }

    variables := map[string]interface{}{
        "q": githubql.String("GraphQL"),
        "searchType":  githubql.SearchTypeRepository,
    }

    err := client.Query(context.Background(), &query, variables)
    if err != nil {
        fmt.Println(err)
    }

    for _, repo := range query.Search.Nodes {
        fmt.Println("---------")
        fmt.Println(repo.NameWithOwner)
        fmt.Println(repo.Url)
        for _, lang := range repo.Languages.Nodes {
            fmt.Println(lang.Name)
            fmt.Println(lang.Color)
        }
    }
}

実行結果

クエリーで定義した構造体に、GraphQL APIからのレスポンスのJSONマッピングされています。

---------
facebook/graphql
https://github.com/facebook/graphql
Shell
#89e051
---------
graphql-go/graphql
https://github.com/graphql-go/graphql
Go
#375eab
---------
Youshido/GraphQL
https://github.com/Youshido/GraphQL
PHP
#4F5D95
---------
graphql-elixir/graphql
https://github.com/graphql-elixir/graphql
Elixir
#6e4a7e
Erlang
#B83998
---------
GraphQLSwift/GraphQL
https://github.com/GraphQLSwift/GraphQL
Swift
#ffac45

以上です。

参考