Stash box client interface (#751)

* Add gql client generation files
* Update dependencies
* Add stash-box client generation to the makefile
* Move scraped scene object matchers to models
* Add stash-box to scrape with dropdown
* Add scrape scene from fingerprint in UI
This commit is contained in:
WithoutPants
2020-09-17 19:57:18 +10:00
committed by GitHub
parent b0b5621337
commit 7a45943e8e
324 changed files with 34978 additions and 17323 deletions

7
vendor/github.com/Yamashou/gqlgenc/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,7 @@
/.gqlgenc.yml
/models_gen.go
/**/.graphqlconfig
/schema.graphql
/client.go
/query/
/.idea/

65
vendor/github.com/Yamashou/gqlgenc/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,65 @@
# See https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
run:
linters-settings:
govet:
enable-all: true
disable:
- shadow
unused:
check-exported: true
unparam:
check-exported: true
varcheck:
exported-fields: true
structcheck:
exported-fields: true
nakedret:
max-func-lines: 1
linters:
enable-all: true
disable:
- testpackage
- nestif
- godot
- wsl
- lll
- dupl
- funlen
- gochecknoinits
- gochecknoglobals
- godox
- maligned
- gocognit
- gocyclo
- interfacer
- gomnd
- goerr113
fast: false
issues:
exclude-rules:
# Test
- path: _test\.go
text: "Using the variable on range scope `tt` in function literal"
linters:
- scopelint
- path: _test\.go
linters:
- unused
- structcheck
- path: introspection/type.go
linters:
- structcheck # These types fits IntrospectionQuery
- path: config/config.go
text: "`Query` is unused" # used in main.go
linters:
- structcheck
- path: graphqljson/graphql.go
text: "`Extensions` is unused" # used in line 48
linters:
- structcheck
- path: introspection/query.go
text: "`Introspection` is unused" # used in config/config.go
linters:
- varcheck

21
vendor/github.com/Yamashou/gqlgenc/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Yamashou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

10
vendor/github.com/Yamashou/gqlgenc/Makefile generated vendored Normal file
View File

@@ -0,0 +1,10 @@
MAKEFLAGS=--no-builtin-rules --no-builtin-variables --always-make
fmt:
gofumports -local github.com/Yamashou/gqlgenc -w .
lint:
golangci-lint run
test:
go test -v ./...

118
vendor/github.com/Yamashou/gqlgenc/README.md generated vendored Normal file
View File

@@ -0,0 +1,118 @@
# gqlgenc
## What is gqlgenc ?
This is Go library for building GraphQL client with [gqlgen](https://github.com/99designs/gqlgen)
## Motivation
Now, if you build GraphQL api client for Go, have choice:
- [github.com/shurcooL/graphql](https://github.com/shurcooL/graphql)
- [github.com/machinebox/graphql](https://github.com/machinebox/graphql)
These libraries are very simple and easy to handle.
However, as I work with [gqlgen](https://github.com/99designs/gqlgen) and [graphql-code-generator](https://graphql-code-generator.com/) every day, I find out the beauty of automatic generation.
So I want to automatically generate types.
## Installation
```shell script
go get -u github.com/Yamashou/gqlgenc
```
## How to use
### Client Codes Only
gqlgenc base is gqlgen with [plugins](https://gqlgen.com/reference/plugins/). So the setting is yaml in each format.
gqlgenc can be configured using a .gqlgenc.yml file,
```yaml
model:
package: generated
filename: ./models_gen.go # https://github.com/99designs/gqlgen/tree/master/plugin/modelgen
client:
package: generated
filename: ./client.go # Where should any generated client go?
models:
Int:
model: github.com/99designs/gqlgen/graphql.Int64
Date:
model: github.com/99designs/gqlgen/graphql.Time
endpoint:
url: https://api.annict.com/graphql # Where do you want to send your request?
headers: # If you need header for getting introspection query, set it
Authorization: "Bearer ${ANNICT_KEY}" # support environment variables
query:
- "./query/*.graphql" # Where are all the query files located?
```
Execute the following command on same directory for .gqlgenc.yaml
```shell script
gqlgenc
```
### With gqlgen
Do this when creating a server and client for Go.
You create your own entrypoint for gqlgen.
This use case is very useful for testing your server.
```go
package main
import (
"fmt"
"os"
"github.com/Yamashou/gqlgenc/clientgen"
"github.com/99designs/gqlgen/api"
"github.com/99designs/gqlgen/codegen/config"
)
func main() {
cfg, err := config.LoadConfigFromDefaultLocations()
if err != nil {
fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
os.Exit(2)
}
queries := []string{"client.query", "fragemt.query"}
clientPackage := config.PackageConfig{
Filename: "./client.go",
Package: "gen",
}
clientPlugin := clientgen.New(queries, clientPackage)
err = api.Generate(cfg,
api.AddPlugin(clientPlugin),
)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(3)
}
}
```
## Documents
- [How to configure gqlgen using gqlgen.yml](https://gqlgen.com/config/)
- [How to write plugins for gqlgen](https://gqlgen.com/reference/plugins/)
## Comments
### Japanese Comments
These codes have Japanese comments. Replace with English.
### Subscription
This client does not support subscription. If you need a subscription, please create an issue or pull request.
### Pre-conditions
[clientgen](https://github.com/Yamashou/gqlgenc/tree/master/clientgen) is created based on [modelgen](https://github.com/99designs/gqlgen/tree/master/plugin/modelgen). So if you don't have a modelgen, it may be a mysterious move.

88
vendor/github.com/Yamashou/gqlgenc/client/client.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
package client
import (
"bytes"
"context"
"encoding/json"
"net/http"
"github.com/Yamashou/gqlgenc/graphqljson"
"golang.org/x/xerrors"
)
type HTTPRequestOption func(req *http.Request)
type Client struct {
Client *http.Client
BaseURL string
HTTPRequestOptions []HTTPRequestOption
}
// Request represents an outgoing GraphQL request
type Request struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables,omitempty"`
OperationName string `json:"operationName,omitempty"`
}
func NewClient(client *http.Client, baseURL string, options ...HTTPRequestOption) *Client {
return &Client{
Client: client,
BaseURL: baseURL,
HTTPRequestOptions: options,
}
}
func (c *Client) newRequest(ctx context.Context, query string, vars map[string]interface{}, httpRequestOptions []HTTPRequestOption) (*http.Request, error) {
r := &Request{
Query: query,
Variables: vars,
OperationName: "",
}
requestBody, err := json.Marshal(r)
if err != nil {
return nil, xerrors.Errorf("encode: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL, bytes.NewBuffer(requestBody))
if err != nil {
return nil, xerrors.Errorf("create request struct failed: %w", err)
}
for _, httpRequestOption := range c.HTTPRequestOptions {
httpRequestOption(req)
}
for _, httpRequestOption := range httpRequestOptions {
httpRequestOption(req)
}
return req, nil
}
// Post sends a http POST request to the graphql endpoint with the given query then unpacks
// the response into the given object.
func (c *Client) Post(ctx context.Context, query string, respData interface{}, vars map[string]interface{}, httpRequestOptions ...HTTPRequestOption) error {
req, err := c.newRequest(ctx, query, vars, httpRequestOptions)
if err != nil {
return xerrors.Errorf("don't create request: %w", err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Accept", "application/json; charset=utf-8")
resp, err := c.Client.Do(req)
if err != nil {
return xerrors.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if err := graphqljson.Unmarshal(resp.Body, respData); err != nil {
return err
}
if resp.StatusCode < 200 || 299 < resp.StatusCode {
return xerrors.Errorf("http status code: %v", resp.StatusCode)
}
return nil
}

76
vendor/github.com/Yamashou/gqlgenc/clientgen/client.go generated vendored Normal file
View File

@@ -0,0 +1,76 @@
package clientgen
import (
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin"
"golang.org/x/xerrors"
)
var _ plugin.ConfigMutator = &Plugin{}
type Plugin struct {
queryFilePaths []string
Client config.PackageConfig
}
func New(queryFilePaths []string, client config.PackageConfig) *Plugin {
return &Plugin{
queryFilePaths: queryFilePaths,
Client: client,
}
}
func (p *Plugin) Name() string {
return "clientgen"
}
func (p *Plugin) MutateConfig(cfg *config.Config) error {
querySources, err := LoadQuerySources(p.queryFilePaths)
if err != nil {
return xerrors.Errorf("load query sources failed: %w", err)
}
// 1. 全体のqueryDocumentを1度にparse
// 1. Parse document from source of query
queryDocument, err := ParseQueryDocuments(cfg.Schema, querySources)
if err != nil {
return xerrors.Errorf(": %w", err)
}
// 2. OperationごとのqueryDocumentを作成
// 2. Separate documents for each operation
queryDocuments, err := QueryDocumentsByOperations(cfg.Schema, queryDocument.Operations)
if err != nil {
return xerrors.Errorf("parse query document failed: %w", err)
}
// 3. テンプレートと情報ソースを元にコード生成
// 3. Generate code from template and document source
sourceGenerator := NewSourceGenerator(cfg, p.Client)
source := NewSource(cfg.Schema, queryDocument, sourceGenerator)
query, err := source.Query()
if err != nil {
return xerrors.Errorf("generating query object: %w", err)
}
mutation, err := source.Mutation()
if err != nil {
return xerrors.Errorf("generating mutation object: %w", err)
}
fragments, err := source.Fragments()
if err != nil {
return xerrors.Errorf("generating fragment failed: %w", err)
}
operationResponses, err := source.OperationResponses()
if err != nil {
return xerrors.Errorf("generating operation response failed: %w", err)
}
if err := RenderTemplate(cfg, query, mutation, fragments, source.Operations(queryDocuments), operationResponses, p.Client); err != nil {
return xerrors.Errorf("template failed: %w", err)
}
return nil
}

93
vendor/github.com/Yamashou/gqlgenc/clientgen/query.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
package clientgen
import (
"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/parser"
"github.com/vektah/gqlparser/v2/validator"
"golang.org/x/xerrors"
)
func ParseQueryDocuments(schema *ast.Schema, querySources []*ast.Source) (*ast.QueryDocument, error) {
var queryDocument ast.QueryDocument
for _, querySource := range querySources {
query, gqlerr := parser.ParseQuery(querySource)
if gqlerr != nil {
return nil, xerrors.Errorf(": %w", gqlerr)
}
mergeQueryDocument(&queryDocument, query)
}
if errs := validator.Validate(schema, &queryDocument); errs != nil {
return nil, xerrors.Errorf(": %w", errs)
}
return &queryDocument, nil
}
func mergeQueryDocument(q, other *ast.QueryDocument) {
q.Operations = append(q.Operations, other.Operations...)
q.Fragments = append(q.Fragments, other.Fragments...)
}
func QueryDocumentsByOperations(schema *ast.Schema, operations ast.OperationList) ([]*ast.QueryDocument, error) {
queryDocuments := make([]*ast.QueryDocument, 0, len(operations))
for _, operation := range operations {
fragments := fragmentsInOperationDefinition(operation)
queryDocument := &ast.QueryDocument{
Operations: ast.OperationList{operation},
Fragments: fragments,
Position: nil,
}
if errs := validator.Validate(schema, queryDocument); errs != nil {
return nil, xerrors.Errorf(": %w", errs)
}
queryDocuments = append(queryDocuments, queryDocument)
}
return queryDocuments, nil
}
func fragmentsInOperationDefinition(operation *ast.OperationDefinition) ast.FragmentDefinitionList {
fragments := fragmentsInOperationWalker(operation.SelectionSet)
uniqueFragments := fragmentsUnique(fragments)
return uniqueFragments
}
func fragmentsUnique(fragments ast.FragmentDefinitionList) ast.FragmentDefinitionList {
uniqueMap := make(map[string]*ast.FragmentDefinition)
for _, fragment := range fragments {
uniqueMap[fragment.Name] = fragment
}
uniqueFragments := make(ast.FragmentDefinitionList, 0, len(uniqueMap))
for _, fragment := range uniqueMap {
uniqueFragments = append(uniqueFragments, fragment)
}
return uniqueFragments
}
func fragmentsInOperationWalker(selectionSet ast.SelectionSet) ast.FragmentDefinitionList {
var fragments ast.FragmentDefinitionList
for _, selection := range selectionSet {
var selectionSet ast.SelectionSet
switch selection := selection.(type) {
case *ast.Field:
selectionSet = selection.SelectionSet
case *ast.InlineFragment:
selectionSet = selection.SelectionSet
case *ast.FragmentSpread:
fragments = append(fragments, selection.Definition)
selectionSet = selection.Definition.SelectionSet
}
fragments = append(fragments, fragmentsInOperationWalker(selectionSet)...)
}
return fragments
}

View File

@@ -0,0 +1,106 @@
/*
Copyright (c) 2020 gqlgen authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package clientgen
import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/99designs/gqlgen/codegen/config"
"github.com/vektah/gqlparser/v2/ast"
"golang.org/x/xerrors"
)
var path2regex = strings.NewReplacer(
`.`, `\.`,
`*`, `.+`,
`\`, `[\\/]`,
`/`, `[\\/]`,
)
// LoadQuerySourceなどは、gqlgenがLoadConfigでSchemaを読み込む時の実装をコピーして一部修正している
// **/test/*.graphqlなどに対応している
func LoadQuerySources(queryFileNames []string) ([]*ast.Source, error) {
var noGlobQueryFileNames config.StringList
var err error
preGlobbing := queryFileNames
for _, f := range preGlobbing {
var matches []string
// for ** we want to override default globbing patterns and walk all
// subdirectories to match schema files.
if strings.Contains(f, "**") {
pathParts := strings.SplitN(f, "**", 2)
rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`)
// turn the rest of the glob into a regex, anchored only at the end because ** allows
// for any number of dirs in between and walk will let us match against the full path name
globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`)
if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) {
matches = append(matches, path)
}
return nil
}); err != nil {
return nil, xerrors.Errorf("failed to walk schema at root: %w", pathParts[0])
}
} else {
matches, err = filepath.Glob(f)
if err != nil {
return nil, xerrors.Errorf("failed to glob schema filename %v: %w", f, err)
}
}
for _, m := range matches {
if noGlobQueryFileNames.Has(m) {
continue
}
noGlobQueryFileNames = append(noGlobQueryFileNames, m)
}
}
querySources := make([]*ast.Source, 0, len(noGlobQueryFileNames))
for _, filename := range noGlobQueryFileNames {
filename = filepath.ToSlash(filename)
var err error
var schemaRaw []byte
schemaRaw, err = ioutil.ReadFile(filename)
if err != nil {
return nil, xerrors.Errorf("unable to open schema: %w", err)
}
querySources = append(querySources, &ast.Source{Name: filename, Input: string(schemaRaw)})
}
return querySources, nil
}

193
vendor/github.com/Yamashou/gqlgenc/clientgen/source.go generated vendored Normal file
View File

@@ -0,0 +1,193 @@
package clientgen
import (
"bytes"
"fmt"
"go/types"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/formatter"
"golang.org/x/xerrors"
)
type Source struct {
schema *ast.Schema
queryDocument *ast.QueryDocument
sourceGenerator *SourceGenerator
}
func NewSource(schema *ast.Schema, queryDocument *ast.QueryDocument, sourceGenerator *SourceGenerator) *Source {
return &Source{
schema: schema,
queryDocument: queryDocument,
sourceGenerator: sourceGenerator,
}
}
type Fragment struct {
Name string
Type types.Type
}
func (s *Source) Fragments() ([]*Fragment, error) {
fragments := make([]*Fragment, 0, len(s.queryDocument.Fragments))
for _, fragment := range s.queryDocument.Fragments {
responseFields := s.sourceGenerator.NewResponseFields(fragment.SelectionSet)
if s.sourceGenerator.cfg.Models.Exists(fragment.Name) {
return nil, xerrors.New(fmt.Sprintf("%s is duplicated", fragment.Name))
}
fragment := &Fragment{
Name: fragment.Name,
Type: responseFields.StructType(),
}
fragments = append(fragments, fragment)
}
for _, fragment := range fragments {
name := fragment.Name
s.sourceGenerator.cfg.Models.Add(
name,
fmt.Sprintf("%s.%s", s.sourceGenerator.client.Pkg(), templates.ToGo(name)),
)
}
return fragments, nil
}
type Operation struct {
Name string
ResponseStructName string
Operation string
Args []*Argument
VariableDefinitions ast.VariableDefinitionList
}
func NewOperation(operation *ast.OperationDefinition, queryDocument *ast.QueryDocument, args []*Argument) *Operation {
return &Operation{
Name: operation.Name,
ResponseStructName: getResponseStructName(operation),
Operation: queryString(queryDocument),
Args: args,
VariableDefinitions: operation.VariableDefinitions,
}
}
func (s *Source) Operations(queryDocuments []*ast.QueryDocument) []*Operation {
operations := make([]*Operation, 0, len(s.queryDocument.Operations))
queryDocumentsMap := queryDocumentMapByOperationName(queryDocuments)
operationArgsMap := s.operationArgsMapByOperationName()
for _, operation := range s.queryDocument.Operations {
queryDocument := queryDocumentsMap[operation.Name]
args := operationArgsMap[operation.Name]
operations = append(operations, NewOperation(
operation,
queryDocument,
args,
))
}
return operations
}
func (s *Source) operationArgsMapByOperationName() map[string][]*Argument {
operationArgsMap := make(map[string][]*Argument)
for _, operation := range s.queryDocument.Operations {
operationArgsMap[operation.Name] = s.sourceGenerator.OperationArguments(operation.VariableDefinitions)
}
return operationArgsMap
}
func queryDocumentMapByOperationName(queryDocuments []*ast.QueryDocument) map[string]*ast.QueryDocument {
queryDocumentMap := make(map[string]*ast.QueryDocument)
for _, queryDocument := range queryDocuments {
operation := queryDocument.Operations[0]
queryDocumentMap[operation.Name] = queryDocument
}
return queryDocumentMap
}
func queryString(queryDocument *ast.QueryDocument) string {
var buf bytes.Buffer
astFormatter := formatter.NewFormatter(&buf)
astFormatter.FormatQueryDocument(queryDocument)
return buf.String()
}
type OperationResponse struct {
Name string
Type types.Type
}
func (s *Source) OperationResponses() ([]*OperationResponse, error) {
operationResponse := make([]*OperationResponse, 0, len(s.queryDocument.Operations))
for _, operation := range s.queryDocument.Operations {
responseFields := s.sourceGenerator.NewResponseFields(operation.SelectionSet)
name := getResponseStructName(operation)
if s.sourceGenerator.cfg.Models.Exists(name) {
return nil, xerrors.New(fmt.Sprintf("%s is duplicated", name))
}
operationResponse = append(operationResponse, &OperationResponse{
Name: name,
Type: responseFields.StructType(),
})
}
for _, operationResponse := range operationResponse {
name := operationResponse.Name
s.sourceGenerator.cfg.Models.Add(
name,
fmt.Sprintf("%s.%s", s.sourceGenerator.client.Pkg(), templates.ToGo(name)),
)
}
return operationResponse, nil
}
type Query struct {
Name string
Type types.Type
}
func (s *Source) Query() (*Query, error) {
fields, err := s.sourceGenerator.NewResponseFieldsByDefinition(s.schema.Query)
if err != nil {
return nil, xerrors.Errorf("generate failed for query struct type : %w", err)
}
return &Query{
Name: s.schema.Query.Name,
Type: fields.StructType(),
}, nil
}
type Mutation struct {
Name string
Type types.Type
}
func (s *Source) Mutation() (*Mutation, error) {
fields, err := s.sourceGenerator.NewResponseFieldsByDefinition(s.schema.Mutation)
if err != nil {
return nil, xerrors.Errorf("generate failed for mutation struct type : %w", err)
}
return &Mutation{
Name: s.schema.Mutation.Name,
Type: fields.StructType(),
}, nil
}
func getResponseStructName(operation *ast.OperationDefinition) string {
if operation.Operation == ast.Mutation {
return fmt.Sprintf("%sPayload", operation.Name)
}
return operation.Name
}

View File

@@ -0,0 +1,204 @@
package clientgen
import (
"fmt"
"go/types"
"strings"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/vektah/gqlparser/v2/ast"
"golang.org/x/xerrors"
)
type Argument struct {
Variable string
Type types.Type
}
type ResponseField struct {
Name string
IsFragmentSpread bool
IsInlineFragment bool
Type types.Type
Tags []string
ResponseFields ResponseFieldList
}
type ResponseFieldList []*ResponseField
func (rs ResponseFieldList) StructType() *types.Struct {
vars := make([]*types.Var, 0)
structTags := make([]string, 0)
for _, filed := range rs {
// クエリーのフィールドの子階層がFragmentの場合、このフィールドにそのFragmentの型を追加する
if filed.IsFragmentSpread {
typ := filed.ResponseFields.StructType().Underlying().(*types.Struct)
for j := 0; j < typ.NumFields(); j++ {
vars = append(vars, typ.Field(j))
structTags = append(structTags, typ.Tag(j))
}
} else {
vars = append(vars, types.NewVar(0, nil, templates.ToGo(filed.Name), filed.Type))
structTags = append(structTags, strings.Join(filed.Tags, " "))
}
}
return types.NewStruct(vars, structTags)
}
func (rs ResponseFieldList) IsFragment() bool {
if len(rs) != 1 {
return false
}
return rs[0].IsInlineFragment || rs[0].IsFragmentSpread
}
func (rs ResponseFieldList) IsBasicType() bool {
return len(rs) == 0
}
func (rs ResponseFieldList) IsStructType() bool {
return len(rs) > 0 && !rs.IsFragment()
}
type SourceGenerator struct {
cfg *config.Config
binder *config.Binder
client config.PackageConfig
}
func NewSourceGenerator(cfg *config.Config, client config.PackageConfig) *SourceGenerator {
return &SourceGenerator{
cfg: cfg,
binder: cfg.NewBinder(),
client: client,
}
}
func (r *SourceGenerator) NewResponseFields(selectionSet ast.SelectionSet) ResponseFieldList {
responseFields := make(ResponseFieldList, 0, len(selectionSet))
for _, selection := range selectionSet {
responseFields = append(responseFields, r.NewResponseField(selection))
}
return responseFields
}
func (r *SourceGenerator) NewResponseFieldsByDefinition(definition *ast.Definition) (ResponseFieldList, error) {
fields := make(ResponseFieldList, 0, len(definition.Fields))
for _, field := range definition.Fields {
if field.Type.Name() == "__Schema" || field.Type.Name() == "__Type" {
continue
}
typ, err := r.binder.FindTypeFromName(r.cfg.Models[field.Type.Name()].Model[0])
if err != nil {
return nil, xerrors.Errorf("not found type: %w", err)
}
tags := []string{
fmt.Sprintf(`json:"%s"`, field.Name),
fmt.Sprintf(`graphql:"%s"`, field.Name),
}
fields = append(fields, &ResponseField{
Name: field.Name,
Type: r.binder.CopyModifiersFromAst(field.Type, typ),
Tags: tags,
})
}
return fields, nil
}
func (r *SourceGenerator) NewResponseField(selection ast.Selection) *ResponseField {
switch selection := selection.(type) {
case *ast.Field:
fieldsResponseFields := r.NewResponseFields(selection.SelectionSet)
var baseType types.Type
switch {
case fieldsResponseFields.IsBasicType():
baseType = r.Type(selection.Definition.Type.Name())
case fieldsResponseFields.IsFragment():
// 子フィールドがFragmentの場合はこのFragmentがフィールドの型になる
// if a child field is fragment, this field type became fragment.
baseType = fieldsResponseFields[0].Type
case fieldsResponseFields.IsStructType():
baseType = fieldsResponseFields.StructType()
default:
// ここにきたらバグ
// here is bug
panic("not match type")
}
// GraphQLの定義がオプショナルのはtypeのポインタ型が返り、配列の定義場合はポインタのスライスの型になって返ってきます
// return pointer type then optional type or slice pointer then slice type of definition in GraphQL.
typ := r.binder.CopyModifiersFromAst(selection.Definition.Type, baseType)
tags := []string{
fmt.Sprintf(`json:"%s"`, selection.Alias),
fmt.Sprintf(`graphql:"%s"`, selection.Alias),
}
return &ResponseField{
Name: selection.Alias,
Type: typ,
Tags: tags,
ResponseFields: fieldsResponseFields,
}
case *ast.FragmentSpread:
// この構造体はテンプレート側で使われることはなく、ast.FieldでFragment判定するために使用する
fieldsResponseFields := r.NewResponseFields(selection.Definition.SelectionSet)
typ := types.NewNamed(
types.NewTypeName(0, r.client.Pkg(), templates.ToGo(selection.Name), nil),
fieldsResponseFields.StructType(),
nil,
)
return &ResponseField{
Name: selection.Name,
Type: typ,
IsFragmentSpread: true,
ResponseFields: fieldsResponseFields,
}
case *ast.InlineFragment:
// InlineFragmentは子要素をそのままstructとしてもつので、ここで、構造体の型を作成します
fieldsResponseFields := r.NewResponseFields(selection.SelectionSet)
return &ResponseField{
Name: selection.TypeCondition,
Type: fieldsResponseFields.StructType(),
IsInlineFragment: true,
Tags: []string{fmt.Sprintf(`graphql:"... on %s"`, selection.TypeCondition)},
ResponseFields: fieldsResponseFields,
}
}
panic("unexpected selection type")
}
func (r *SourceGenerator) OperationArguments(variableDefinitions ast.VariableDefinitionList) []*Argument {
argumentTypes := make([]*Argument, 0, len(variableDefinitions))
for _, v := range variableDefinitions {
argumentTypes = append(argumentTypes, &Argument{
Variable: v.Variable,
Type: r.binder.CopyModifiersFromAst(v.Type, r.Type(v.Type.Name())),
})
}
return argumentTypes
}
// Typeの引数に渡すtypeNameは解析した結果からselectionなどから求めた型の名前を渡さなければいけない
func (r *SourceGenerator) Type(typeName string) types.Type {
goType, err := r.binder.FindTypeFromName(r.cfg.Models[typeName].Model[0])
if err != nil {
// 実装として正しいtypeNameを渡していれば必ず見つかるはずなのでpanic
panic(fmt.Sprintf("%+v", err))
}
return goType
}

View File

@@ -0,0 +1,27 @@
package clientgen
import (
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"golang.org/x/xerrors"
)
func RenderTemplate(cfg *config.Config, query *Query, mutation *Mutation, fragments []*Fragment, operations []*Operation, operationResponses []*OperationResponse, client config.PackageConfig) error {
if err := templates.Render(templates.Options{
PackageName: client.Package,
Filename: client.Filename,
Data: map[string]interface{}{
"Query": query,
"Mutation": mutation,
"Fragment": fragments,
"Operation": operations,
"OperationResponse": operationResponses,
},
Packages: cfg.Packages,
PackageDoc: "// Code generated by github.com/Yamashou/gqlgenc, DO NOT EDIT.\n",
}); err != nil {
return xerrors.Errorf("%s generating failed: %w", client.Filename, err)
}
return nil
}

View File

@@ -0,0 +1,54 @@
{{ reserveImport "bytes" }}
{{ reserveImport "context" }}
{{ reserveImport "encoding/json" }}
{{ reserveImport "fmt" }}
{{ reserveImport "io" }}
{{ reserveImport "io/ioutil" }}
{{ reserveImport "net/http" }}
{{ reserveImport "net/url" }}
{{ reserveImport "path" }}
{{ reserveImport "time" }}
{{ reserveImport "golang.org/x/xerrors" }}
{{ reserveImport "github.com/Yamashou/gqlgenc/graphqljson" }}
{{ reserveImport "github.com/Yamashou/gqlgenc/client" }}
type Client struct {
Client *client.Client
}
func NewClient(cli *http.Client, baseURL string, options ...client.HTTPRequestOption) *Client {
return &Client{Client: client.NewClient(cli, baseURL, options...)}
}
type {{ .Query.Name | go }} {{ .Query.Type | ref }}
type {{ .Mutation.Name | go }} {{ .Mutation.Type | ref }}
{{- range $name, $element := .Fragment }}
type {{ .Name | go }} {{ .Type | ref }}
{{- end }}
{{- range $name, $element := .OperationResponse }}
type {{ .Name | go }} {{ .Type | ref }}
{{- end }}
{{- range $model := .Operation}}
const {{ $model.Name|go }}Query = `{{ $model.Operation }}`
func (c *Client) {{ $model.Name|go }} (ctx context.Context{{- range $arg := .Args }}, {{ $arg.Variable | goPrivate }} {{ $arg.Type | ref }} {{- end }}, httpRequestOptions ...client.HTTPRequestOption) (*{{ $model.ResponseStructName | go }}, error) {
vars := map[string]interface{}{
{{- range $args := .VariableDefinitions}}
"{{ $args.Variable }}": {{ $args.Variable | goPrivate }},
{{- end }}
}
var res {{ $model.ResponseStructName | go }}
if err := c.Client.Post(ctx, {{ $model.Name|go }}Query, &res, vars, httpRequestOptions...); err != nil {
return nil, err
}
return &res, nil
}
{{- end}}

123
vendor/github.com/Yamashou/gqlgenc/config/config.go generated vendored Normal file
View File

@@ -0,0 +1,123 @@
package config
import (
"context"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"github.com/99designs/gqlgen/codegen/config"
"github.com/Yamashou/gqlgenc/client"
"github.com/Yamashou/gqlgenc/introspection"
"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/validator"
"golang.org/x/xerrors"
"gopkg.in/yaml.v2"
)
type Config struct {
Model config.PackageConfig `yaml:"model,omitempty"`
Client config.PackageConfig `yaml:"client,omitempty"`
Models config.TypeMap `yaml:"models,omitempty"`
Endpoint EndPointConfig `yaml:"endpoint"`
Query []string `yaml:"query"`
// gqlgen config struct
GQLConfig *config.Config `yaml:"-"`
}
type EndPointConfig struct {
URL string `yaml:"url"`
Headers map[string]string `yaml:"headers,omitempty"`
}
func findCfg(fileName string) (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", xerrors.Errorf("unable to get working dir to findCfg: %w", err)
}
cfg := findCfgInDir(dir, fileName)
if cfg == "" {
return "", os.ErrNotExist
}
return cfg, nil
}
func findCfgInDir(dir, fileName string) string {
path := filepath.Join(dir, fileName)
return path
}
func LoadConfig(filename string) (*Config, error) {
var cfg Config
file, err := findCfg(filename)
if err != nil {
return nil, xerrors.Errorf("unable to get file path: %w", err)
}
b, err := ioutil.ReadFile(file)
if err != nil {
return nil, xerrors.Errorf("unable to read config: %w", err)
}
confContent := []byte(os.ExpandEnv(string(b)))
if err := yaml.UnmarshalStrict(confContent, &cfg); err != nil {
return nil, xerrors.Errorf("unable to parse config: %w", err)
}
cfg.GQLConfig = &config.Config{
Model: cfg.Model,
Models: cfg.Models,
// TODO: gqlgen must be set exec but client not used
Exec: config.PackageConfig{Filename: "generated.go"},
Directives: map[string]config.DirectiveConfig{},
}
if err := cfg.Client.Check(); err != nil {
return nil, xerrors.Errorf("config.exec: %w", err)
}
return &cfg, nil
}
func (c *Config) LoadSchema(ctx context.Context) error {
addHeader := func(req *http.Request) {
for key, value := range c.Endpoint.Headers {
req.Header.Set(key, value)
}
}
gqlclient := client.NewClient(http.DefaultClient, c.Endpoint.URL, addHeader)
schema, err := LoadRemoteSchema(ctx, gqlclient)
if err != nil {
return xerrors.Errorf("load remote schema failed: %w", err)
}
if schema.Query == nil {
schema.Query = &ast.Definition{
Kind: ast.Object,
Name: "Query",
}
schema.Types["Query"] = schema.Query
}
c.GQLConfig.Schema = schema
return nil
}
func LoadRemoteSchema(ctx context.Context, gqlclient *client.Client) (*ast.Schema, error) {
var res introspection.Query
if err := gqlclient.Post(ctx, introspection.Introspection, &res, nil); err != nil {
return nil, xerrors.Errorf("introspection query failed: %w", err)
}
schema, err := validator.ValidateSchemaDocument(introspection.ParseIntrospectionQuery(res))
if err != nil {
return nil, xerrors.Errorf("validation error: %w", err)
}
return schema, nil
}

View File

@@ -0,0 +1,40 @@
package generator
import (
"context"
"github.com/99designs/gqlgen/api"
"github.com/99designs/gqlgen/plugin"
"github.com/99designs/gqlgen/plugin/modelgen"
"github.com/Yamashou/gqlgenc/config"
"golang.org/x/xerrors"
)
func Generate(ctx context.Context, cfg *config.Config, option ...api.Option) error {
var plugins []plugin.Plugin
if cfg.Model.IsDefined() {
plugins = append(plugins, modelgen.New())
}
for _, o := range option {
o(cfg.GQLConfig, &plugins)
}
if err := cfg.LoadSchema(ctx); err != nil {
return xerrors.Errorf("failed to load schema: %w\n", err)
}
if err := cfg.GQLConfig.Init(); err != nil {
return xerrors.Errorf("generating core failed: %w\n", err)
}
for _, p := range plugins {
if mut, ok := p.(plugin.ConfigMutator); ok {
err := mut.MutateConfig(cfg.GQLConfig)
if err != nil {
return xerrors.Errorf("%s failed: %w\n", p.Name(), err)
}
}
}
return nil
}

14
vendor/github.com/Yamashou/gqlgenc/go.mod generated vendored Normal file
View File

@@ -0,0 +1,14 @@
module github.com/Yamashou/gqlgenc
go 1.14
require (
github.com/99designs/gqlgen v0.12.2
github.com/agnivade/levenshtein v1.1.0 // indirect
github.com/google/go-cmp v0.5.2
github.com/pkg/errors v0.9.1 // indirect
github.com/vektah/gqlparser/v2 v2.0.1
golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
gopkg.in/yaml.v2 v2.3.0
)

122
vendor/github.com/Yamashou/gqlgenc/go.sum generated vendored Normal file
View File

@@ -0,0 +1,122 @@
github.com/99designs/gqlgen v0.12.2 h1:aOdpsiCycFtCnAv8CAI1exnKrIDHMqtMzQoXeTziY4o=
github.com/99designs/gqlgen v0.12.2/go.mod h1:7zdGo6ry9u1YBp/qlb2uxSU5Mt2jQKLcBETQiKk+Bxo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3 h1:OjYQxZBKJFs+sJbHkvSGIKNMkZXDJQ9JsMpebGhkafI=
golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=

View File

@@ -0,0 +1,416 @@
/*
MIT License
Copyright (c) 2017 Dmitri Shuralyov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
// Package jsonutil provides a function for decoding JSON
// into a GraphQL query data structure.
package graphqljson
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
"golang.org/x/xerrors"
)
// Reference: https://blog.gopheracademy.com/advent-2017/custom-json-unmarshaler-for-graphql-client/
// RawJSONError is a json formatted error from a GraphQL server.
type RawJSONError struct {
Response
}
func (r RawJSONError) Error() string {
return fmt.Sprintf("data: %s, error: %s, extensions: %v", r.Data, r.Errors, r.Extensions)
}
// Response is a GraphQL layer response from a handler.
type Response struct {
Data json.RawMessage
Errors Errors
Extensions map[string]interface{}
}
func Unmarshal(r io.Reader, data interface{}) error {
resp := Response{}
decoder := json.NewDecoder(r)
if err := decoder.Decode(&resp); err != nil {
var buf bytes.Buffer
if _, e := io.Copy(&buf, decoder.Buffered()); e != nil {
return xerrors.Errorf(": %w", err)
}
return xerrors.Errorf("%s", buf.String())
}
if len(resp.Errors) > 0 {
return xerrors.Errorf("response error: %w", resp.Errors)
}
if err := UnmarshalData(resp.Data, data); err != nil {
return xerrors.Errorf("response mapping failed: %w", err)
}
if resp.Errors != nil {
return RawJSONError{resp}
}
return nil
}
// UnmarshalGraphQL parses the JSON-encoded GraphQL response data and stores
// the result in the GraphQL query data structure pointed to by v.
//
// The implementation is created on top of the JSON tokenizer available
// in "encoding/json".Decoder.
func UnmarshalData(data json.RawMessage, v interface{}) error {
d := NewDecoder(bytes.NewBuffer(data))
if err := d.Decode(v); err != nil {
return xerrors.Errorf(": %w", err)
}
// TODO: この処理が本当に必要かは今後検討
tok, err := d.jsonDecoder.Token()
switch err {
case io.EOF:
// Expect to get io.EOF. There shouldn't be any more
// tokens left after we've decoded v successfully.
return nil
case nil:
return xerrors.Errorf("invalid token '%v' after top-level value", tok)
}
return xerrors.Errorf("invalid token '%v' after top-level value", tok)
}
// decoder is a JSON decoder that performs custom unmarshaling behavior
// for GraphQL query data structures. It's implemented on top of a JSON tokenizer.
type Decoder struct {
jsonDecoder *json.Decoder
// Stack of what part of input JSON we're in the middle of - objects, arrays.
parseState []json.Delim
// Stacks of values where to unmarshal.
// The top of each stack is the reflect.Value where to unmarshal next JSON value.
//
// The reason there's more than one stack is because we might be unmarshaling
// a single JSON value into multiple GraphQL fragments or embedded structs, so
// we keep track of them all.
vs [][]reflect.Value
}
func NewDecoder(r io.Reader) *Decoder {
jsonDecoder := json.NewDecoder(r)
jsonDecoder.UseNumber()
return &Decoder{
jsonDecoder: jsonDecoder,
}
}
// Decode decodes a single JSON value from d.tokenizer into v.
func (d *Decoder) Decode(v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
return xerrors.Errorf("cannot decode into non-pointer %T", v)
}
d.vs = [][]reflect.Value{{rv.Elem()}}
if err := d.decode(); err != nil {
return xerrors.Errorf(": %w", err)
}
return nil
}
// decode decodes a single JSON value from d.tokenizer into d.vs.
func (d *Decoder) decode() error {
// The loop invariant is that the top of each d.vs stack
// is where we try to unmarshal the next JSON value we see.
for len(d.vs) > 0 {
tok, err := d.jsonDecoder.Token()
if err == io.EOF {
return xerrors.New("unexpected end of JSON input")
} else if err != nil {
return xerrors.Errorf(": %w", err)
}
switch {
// Are we inside an object and seeing next key (rather than end of object)?
case d.state() == '{' && tok != json.Delim('}'):
key, ok := tok.(string)
if !ok {
return xerrors.New("unexpected non-key in JSON input")
}
someFieldExist := false
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
var f reflect.Value
if v.Kind() == reflect.Struct {
f = fieldByGraphQLName(v, key)
if f.IsValid() {
someFieldExist = true
}
}
d.vs[i] = append(d.vs[i], f)
}
if !someFieldExist {
return xerrors.Errorf("struct field for %q doesn't exist in any of %v places to unmarshal", key, len(d.vs))
}
// We've just consumed the current token, which was the key.
// Read the next token, which should be the value, and let the rest of code process it.
tok, err = d.jsonDecoder.Token()
if err == io.EOF {
return xerrors.New("unexpected end of JSON input")
} else if err != nil {
return xerrors.Errorf(": %w", err)
}
// Are we inside an array and seeing next value (rather than end of array)?
case d.state() == '[' && tok != json.Delim(']'):
someSliceExist := false
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
var f reflect.Value
if v.Kind() == reflect.Slice {
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) // v = append(v, T).
f = v.Index(v.Len() - 1)
someSliceExist = true
}
d.vs[i] = append(d.vs[i], f)
}
if !someSliceExist {
return xerrors.Errorf("slice doesn't exist in any of %v places to unmarshal", len(d.vs))
}
}
switch tok := tok.(type) {
case string, json.Number, bool, nil:
// Value.
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
if !v.IsValid() {
continue
}
err := unmarshalValue(tok, v)
if err != nil {
return xerrors.Errorf(": %w", err)
}
}
d.popAllVs()
case json.Delim:
switch tok {
case '{':
// Start of object.
d.pushState(tok)
frontier := make([]reflect.Value, len(d.vs)) // Places to look for GraphQL fragments/embedded structs.
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
frontier[i] = v
// TODO: Do this recursively or not? Add a test case if needed.
if v.Kind() == reflect.Ptr && v.IsNil() {
v.Set(reflect.New(v.Type().Elem())) // v = new(T).
}
}
// Find GraphQL fragments/embedded structs recursively, adding to frontier
// as new ones are discovered and exploring them further.
for len(frontier) > 0 {
v := frontier[0]
frontier = frontier[1:]
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
continue
}
for i := 0; i < v.NumField(); i++ {
if isGraphQLFragment(v.Type().Field(i)) || v.Type().Field(i).Anonymous {
// Add GraphQL fragment or embedded struct.
d.vs = append(d.vs, []reflect.Value{v.Field(i)})
frontier = append(frontier, v.Field(i))
}
}
}
case '[':
// Start of array.
d.pushState(tok)
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
// TODO: Confirm this is needed, write a test case.
// if v.Kind() == reflect.Ptr && v.IsNil() {
// v.Set(reflect.New(v.Type().Elem())) // v = new(T).
//}
// Reset slice to empty (in case it had non-zero initial value).
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Slice {
continue
}
v.Set(reflect.MakeSlice(v.Type(), 0, 0)) // v = make(T, 0, 0).
}
case '}', ']':
// End of object or array.
d.popAllVs()
d.popState()
default:
return xerrors.New("unexpected delimiter in JSON input")
}
default:
return xerrors.New("unexpected token in JSON input")
}
}
return nil
}
// pushState pushes a new parse state s onto the stack.
func (d *Decoder) pushState(s json.Delim) {
d.parseState = append(d.parseState, s)
}
// popState pops a parse state (already obtained) off the stack.
// The stack must be non-empty.
func (d *Decoder) popState() {
d.parseState = d.parseState[:len(d.parseState)-1]
}
// state reports the parse state on top of stack, or 0 if empty.
func (d *Decoder) state() json.Delim {
if len(d.parseState) == 0 {
return 0
}
return d.parseState[len(d.parseState)-1]
}
// popAllVs pops from all d.vs stacks, keeping only non-empty ones.
func (d *Decoder) popAllVs() {
var nonEmpty [][]reflect.Value
for i := range d.vs {
d.vs[i] = d.vs[i][:len(d.vs[i])-1]
if len(d.vs[i]) > 0 {
nonEmpty = append(nonEmpty, d.vs[i])
}
}
d.vs = nonEmpty
}
// fieldByGraphQLName returns an exported struct field of struct v
// that matches GraphQL name, or invalid reflect.Value if none found.
func fieldByGraphQLName(v reflect.Value, name string) reflect.Value {
for i := 0; i < v.NumField(); i++ {
if v.Type().Field(i).PkgPath != "" {
// Skip unexported field.
continue
}
if hasGraphQLName(v.Type().Field(i), name) {
return v.Field(i)
}
}
return reflect.Value{}
}
// hasGraphQLName reports whether struct field f has GraphQL name.
func hasGraphQLName(f reflect.StructField, name string) bool {
value, ok := f.Tag.Lookup("graphql")
if !ok {
// TODO: caseconv package is relatively slow. Optimize it, then consider using it here.
// return caseconv.MixedCapsToLowerCamelCase(f.Name) == name
return strings.EqualFold(f.Name, name)
}
value = strings.TrimSpace(value) // TODO: Parse better.
if strings.HasPrefix(value, "...") {
// GraphQL fragment. It doesn't have a name.
return false
}
if i := strings.Index(value, "("); i != -1 {
value = value[:i]
}
if i := strings.Index(value, ":"); i != -1 {
value = value[:i]
}
return strings.TrimSpace(value) == name
}
// isGraphQLFragment reports whether struct field f is a GraphQL fragment.
func isGraphQLFragment(f reflect.StructField) bool {
value, ok := f.Tag.Lookup("graphql")
if !ok {
return false
}
value = strings.TrimSpace(value) // TODO: Parse better.
return strings.HasPrefix(value, "...")
}
// unmarshalValue unmarshals JSON value into v.
// v must be addressable and not obtained by the use of unexported
// struct fields, otherwise unmarshalValue will panic.
func unmarshalValue(value json.Token, v reflect.Value) error {
b, err := json.Marshal(value) // TODO: Short-circuit (if profiling says it's worth it).
if err != nil {
return xerrors.Errorf(": %w", err)
}
return json.Unmarshal(b, v.Addr().Interface())
}
// Errors represents the "Errors" array in a response from a GraphQL server.
// If returned via error interface, the slice is expected to contain at least 1 element.
//
// Specification: https://facebook.github.io/graphql/#sec-Errors.
type Errors []struct {
Message string
Locations []struct {
Line int
Column int
}
}
// Error implements error interface.
func (e Errors) Error() string {
return e[0].Message
}

View File

@@ -0,0 +1,289 @@
package introspection
import (
"fmt"
"github.com/vektah/gqlparser/v2/ast"
)
func ParseIntrospectionQuery(query Query) *ast.SchemaDocument {
var doc ast.SchemaDocument
typeMap := query.Schema.Types.NameMap()
doc.Schema = append(doc.Schema, parseSchemaDefinition(query, typeMap))
for _, typeVale := range typeMap {
doc.Definitions = append(doc.Definitions, parseTypeSystemDefinition(typeVale))
}
for _, directiveValue := range query.Schema.Directives {
doc.Directives = append(doc.Directives, parseDirectiveDefinition(directiveValue))
}
return &doc
}
func parseSchemaDefinition(query Query, typeMap map[string]*FullType) *ast.SchemaDefinition {
def := ast.SchemaDefinition{}
def.OperationTypes = append(def.OperationTypes,
parseOperationTypeDefinitionForQuery(typeMap[*query.Schema.QueryType.Name]),
parseOperationTypeDefinitionForMutation(typeMap[*query.Schema.MutationType.Name]),
)
return &def
}
func parseOperationTypeDefinitionForQuery(fullType *FullType) *ast.OperationTypeDefinition {
var op ast.OperationTypeDefinition
op.Operation = ast.Query
op.Type = *fullType.Name
return &op
}
func parseOperationTypeDefinitionForMutation(fullType *FullType) *ast.OperationTypeDefinition {
var op ast.OperationTypeDefinition
op.Operation = ast.Mutation
op.Type = *fullType.Name
return &op
}
func parseDirectiveDefinition(directiveValue *DirectiveType) *ast.DirectiveDefinition {
args := make(ast.ArgumentDefinitionList, 0, len(directiveValue.Args))
for _, arg := range directiveValue.Args {
argumentDefinition := buildInputValue(arg)
args = append(args, argumentDefinition)
}
locations := make([]ast.DirectiveLocation, 0, len(directiveValue.Locations))
for _, locationValue := range directiveValue.Locations {
locations = append(locations, ast.DirectiveLocation(locationValue))
}
return &ast.DirectiveDefinition{
Description: pointerString(directiveValue.Description),
Name: directiveValue.Name,
Arguments: args,
Locations: locations,
}
}
func parseObjectFields(typeVale *FullType) ast.FieldList {
fieldList := make(ast.FieldList, 0, len(typeVale.Fields))
for _, field := range typeVale.Fields {
typ := getType(&field.Type)
args := make(ast.ArgumentDefinitionList, 0, len(field.Args))
for _, arg := range field.Args {
argumentDefinition := buildInputValue(arg)
args = append(args, argumentDefinition)
}
fieldDefinition := &ast.FieldDefinition{
Description: pointerString(field.Description),
Name: field.Name,
Arguments: args,
Type: typ,
}
fieldList = append(fieldList, fieldDefinition)
}
return fieldList
}
func parseInputObjectFields(typeVale *FullType) ast.FieldList {
fieldList := make(ast.FieldList, 0, len(typeVale.InputFields))
for _, field := range typeVale.InputFields {
typ := getType(&field.Type)
fieldDefinition := &ast.FieldDefinition{
Description: pointerString(field.Description),
Name: field.Name,
Type: typ,
}
fieldList = append(fieldList, fieldDefinition)
}
return fieldList
}
func parseObjectTypeDefinition(typeVale *FullType) *ast.Definition {
fieldList := parseObjectFields(typeVale)
interfaces := make([]string, 0, len(typeVale.Interfaces))
for _, intf := range typeVale.Interfaces {
interfaces = append(interfaces, pointerString(intf.Name))
}
enums := make(ast.EnumValueList, 0, len(typeVale.EnumValues))
for _, enum := range typeVale.EnumValues {
enumValue := &ast.EnumValueDefinition{
Description: pointerString(enum.Description),
Name: enum.Name,
}
enums = append(enums, enumValue)
}
return &ast.Definition{
Kind: ast.Object,
Description: pointerString(typeVale.Description),
Name: pointerString(typeVale.Name),
Interfaces: interfaces,
Fields: fieldList,
EnumValues: enums,
Position: nil,
BuiltIn: true,
}
}
func parseInterfaceTypeDefinition(typeVale *FullType) *ast.Definition {
fieldList := parseObjectFields(typeVale)
interfaces := make([]string, 0, len(typeVale.Interfaces))
for _, intf := range typeVale.Interfaces {
interfaces = append(interfaces, pointerString(intf.Name))
}
return &ast.Definition{
Kind: ast.Interface,
Description: pointerString(typeVale.Description),
Name: pointerString(typeVale.Name),
Interfaces: interfaces,
Fields: fieldList,
Position: nil,
BuiltIn: true,
}
}
func parseInputObjectTypeDefinition(typeVale *FullType) *ast.Definition {
fieldList := parseInputObjectFields(typeVale)
interfaces := make([]string, 0, len(typeVale.Interfaces))
for _, intf := range typeVale.Interfaces {
interfaces = append(interfaces, pointerString(intf.Name))
}
return &ast.Definition{
Kind: ast.InputObject,
Description: pointerString(typeVale.Description),
Name: pointerString(typeVale.Name),
Interfaces: interfaces,
Fields: fieldList,
Position: nil,
BuiltIn: true,
}
}
func parseUnionTypeDefinition(typeVale *FullType) *ast.Definition {
unions := make([]string, 0, len(typeVale.PossibleTypes))
for _, unionValue := range typeVale.PossibleTypes {
unions = append(unions, *unionValue.Name)
}
return &ast.Definition{
Kind: ast.Union,
Description: pointerString(typeVale.Description),
Name: pointerString(typeVale.Name),
Types: unions,
Position: nil,
BuiltIn: true,
}
}
func parseEnumTypeDefinition(typeVale *FullType) *ast.Definition {
enums := make(ast.EnumValueList, 0, len(typeVale.EnumValues))
for _, enum := range typeVale.EnumValues {
enumValue := &ast.EnumValueDefinition{
Description: pointerString(enum.Description),
Name: enum.Name,
}
enums = append(enums, enumValue)
}
return &ast.Definition{
Kind: ast.Enum,
Description: pointerString(typeVale.Description),
Name: pointerString(typeVale.Name),
EnumValues: enums,
Position: nil,
BuiltIn: true,
}
}
func parseScalarTypeExtension(typeVale *FullType) *ast.Definition {
return &ast.Definition{
Kind: ast.Scalar,
Description: pointerString(typeVale.Description),
Name: pointerString(typeVale.Name),
Position: nil,
BuiltIn: true,
}
}
func parseTypeSystemDefinition(typeVale *FullType) *ast.Definition {
switch typeVale.Kind {
case TypeKindScalar:
return parseScalarTypeExtension(typeVale)
case TypeKindInterface:
return parseInterfaceTypeDefinition(typeVale)
case TypeKindEnum:
return parseEnumTypeDefinition(typeVale)
case TypeKindUnion:
return parseUnionTypeDefinition(typeVale)
case TypeKindObject:
return parseObjectTypeDefinition(typeVale)
case TypeKindInputObject:
return parseInputObjectTypeDefinition(typeVale)
case TypeKindList, TypeKindNonNull:
panic(fmt.Sprintf("not match Kind: %s", typeVale.Kind))
}
panic(fmt.Sprintf("not match Kind: %s", typeVale.Kind))
}
func pointerString(s *string) string {
if s == nil {
return ""
}
return *s
}
func buildInputValue(input *InputValue) *ast.ArgumentDefinition {
typ := getType(&input.Type)
var defaultValue *ast.Value
if input.DefaultValue != nil {
defaultValue = &ast.Value{
Raw: pointerString(input.DefaultValue),
Kind: ast.Variable,
}
}
return &ast.ArgumentDefinition{
Description: pointerString(input.Description),
Name: input.Name,
DefaultValue: defaultValue,
Type: typ,
}
}
func getType(typeRef *TypeRef) *ast.Type {
if typeRef.Kind == TypeKindList {
itemRef := typeRef.OfType
if itemRef == nil {
panic("Decorated type deeper than introspection query.")
}
return ast.ListType(getType(itemRef), nil)
}
if typeRef.Kind == TypeKindNonNull {
nullableRef := typeRef.OfType
if nullableRef == nil {
panic("Decorated type deeper than introspection query.")
}
nullableType := getType(nullableRef)
nullableType.NonNull = true
return nullableType
}
return ast.NamedType(pointerString(typeRef.Name), nil)
}

View File

@@ -0,0 +1,93 @@
package introspection
const Introspection = `query Query {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}`

View File

@@ -0,0 +1,80 @@
package introspection
type TypeKind string
const (
TypeKindScalar TypeKind = "SCALAR"
TypeKindObject TypeKind = "OBJECT"
TypeKindInterface TypeKind = "INTERFACE"
TypeKindUnion TypeKind = "UNION"
TypeKindEnum TypeKind = "ENUM"
TypeKindInputObject TypeKind = "INPUT_OBJECT"
TypeKindList TypeKind = "LIST"
TypeKindNonNull TypeKind = "NON_NULL"
)
type FullTypes []*FullType
func (fs FullTypes) NameMap() map[string]*FullType {
typeMap := make(map[string]*FullType)
for _, typ := range fs {
typeMap[*typ.Name] = typ
}
return typeMap
}
type FullType struct {
Kind TypeKind
Name *string
Description *string
Fields []*FieldValue
InputFields []*InputValue
Interfaces []*TypeRef
EnumValues []*struct {
Name string
Description *string
IsDeprecated bool
DeprecationReason *string
}
PossibleTypes []*TypeRef
}
type FieldValue struct {
Name string
Description *string
Args []*InputValue
Type TypeRef
IsDeprecated bool
DeprecationReason *string
}
type InputValue struct {
Name string
Description *string
Type TypeRef
DefaultValue *string
}
type TypeRef struct {
Kind TypeKind
Name *string
OfType *TypeRef
}
type Query struct {
Schema struct {
QueryType struct{ Name *string }
MutationType *struct{ Name *string }
SubscriptionType *struct{ Name *string }
Types FullTypes
Directives []*DirectiveType
} `graphql:"__schema"`
}
type DirectiveType struct {
Name string
Description *string
Locations []string
Args []*InputValue
}

27
vendor/github.com/Yamashou/gqlgenc/main.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
package main
import (
"context"
"fmt"
"os"
"github.com/99designs/gqlgen/api"
"github.com/Yamashou/gqlgenc/clientgen"
"github.com/Yamashou/gqlgenc/config"
"github.com/Yamashou/gqlgenc/generator"
)
func main() {
ctx := context.Background()
cfg, err := config.LoadConfig(".gqlgenc.yml")
if err != nil {
fmt.Fprintf(os.Stderr, "%+v", err.Error())
os.Exit(2)
}
clientPlugin := clientgen.New(cfg.Query, cfg.Client)
if err := generator.Generate(ctx, cfg, api.AddPlugin(clientPlugin)); err != nil {
fmt.Fprintf(os.Stderr, "%+v", err.Error())
os.Exit(4)
}
}