diff --git a/go.mod b/go.mod index 6e6b1f45a46cb..b84185a6af495 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.16 require ( github.com/99designs/gqlgen v0.13.0 + github.com/Code-Hex/gqlparser/v2 v2.1.1-0.20210404043438-758ac252308a + github.com/agnivade/levenshtein v1.1.0 // indirect github.com/google/go-cmp v0.5.5 github.com/machinebox/graphql v0.2.2 github.com/matryer/is v1.4.0 // indirect diff --git a/go.sum b/go.sum index b0521817590e2..4ebaec08dfde3 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,12 @@ github.com/99designs/gqlgen v0.13.0 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA= github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Code-Hex/gqlparser/v2 v2.1.1-0.20210404043438-758ac252308a h1:IzMV22Hitzxqb71um5i64jEJTyeUVwemK8JmcQdgpA4= +github.com/Code-Hex/gqlparser/v2 v2.1.1-0.20210404043438-758ac252308a/go.mod h1:0sYgRh/Er/ZhHrcKw516TOX4K0ul94vKcBSXAPhLWDc= 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= @@ -13,8 +16,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma 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.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= diff --git a/internal/gqlgen/gqlgen.go b/internal/gqlgen/gqlgen.go index c067ff6fc5f51..db6bd0209d8ef 100644 --- a/internal/gqlgen/gqlgen.go +++ b/internal/gqlgen/gqlgen.go @@ -9,13 +9,14 @@ import ( "strconv" "sync" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/introspection" - gqlparser "github.com/vektah/gqlparser/v2" - "github.com/vektah/gqlparser/v2/ast" + gql "github.com/99designs/gqlgen/graphql" + "github.com/Code-Hex/gqldoc/internal/graphql" + "github.com/Code-Hex/gqldoc/internal/wrapper" + gqlparser "github.com/Code-Hex/gqlparser/v2" + "github.com/Code-Hex/gqlparser/v2/ast" ) -func NewExecutableSchema(filenames ...string) (graphql.ExecutableSchema, error) { +func NewExecutableSchema(filenames ...string) (*ExecutableSchema, error) { sources := make([]*ast.Source, len(filenames)) for i, filename := range filenames { body, err := ioutil.ReadFile(filename) @@ -31,48 +32,34 @@ func NewExecutableSchema(filenames ...string) (graphql.ExecutableSchema, error) if err != nil { return nil, err } - return &executableSchema{ - parsedSchema: AST, + return &ExecutableSchema{ + ParsedSchema: AST, }, nil } -type executableSchema struct { - parsedSchema *ast.Schema +type ExecutableSchema struct { + ParsedSchema *ast.Schema } -func (e *executableSchema) Schema() *ast.Schema { - return e.parsedSchema -} - -func (e *executableSchema) Complexity(typeName, fieldName string, childComplexity int, args map[string]interface{}) (int, bool) { - return 0, false -} - -func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { - rc := graphql.GetOperationContext(ctx) - ec := executionContext{rc, e} - return func(ctx context.Context) *graphql.Response { - data := ec._Query(ctx, rc.Operation.SelectionSet) - var buf bytes.Buffer - data.MarshalGQL(&buf) - - return &graphql.Response{ - Data: buf.Bytes(), - } - } +func (e *ExecutableSchema) Exec(oc *graphql.OperationContext) *bytes.Buffer { + ec := executionContext{oc, e} + data := ec._Query(context.Background(), oc.Operation.SelectionSet) + var buf bytes.Buffer + data.MarshalGQL(&buf) + return &buf } type executionContext struct { *graphql.OperationContext - *executableSchema + *ExecutableSchema } -func (ec *executionContext) introspectSchema() *introspection.Schema { - return introspection.WrapSchema(ec.parsedSchema) +func (ec *executionContext) introspectSchema() *wrapper.Schema { + return wrapper.WrapSchema(ec.ParsedSchema) } -func (ec *executionContext) introspectType(name string) *introspection.Type { - return introspection.WrapTypeFromDef(ec.parsedSchema, ec.parsedSchema.Types[name]) +func (ec *executionContext) introspectType(name string) *wrapper.Type { + return wrapper.WrapTypeFromDef(ec.ParsedSchema, ec.ParsedSchema.Types[name]) } func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { @@ -80,7 +67,7 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs args := map[string]interface{}{} var arg0 string if tmp, ok := rawArgs["name"]; ok { - arg0, err = graphql.UnmarshalString(tmp) + arg0, err = gql.UnmarshalString(tmp) if err != nil { return nil, err } @@ -94,7 +81,7 @@ func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, ra args := map[string]interface{}{} var arg0 bool if tmp, ok := rawArgs["includeDeprecated"]; ok { - arg0, err = graphql.UnmarshalBoolean(tmp) + arg0, err = gql.UnmarshalBoolean(tmp) if err != nil { return nil, err } @@ -108,7 +95,7 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg args := map[string]interface{}{} var arg0 bool if tmp, ok := rawArgs["includeDeprecated"]; ok { - arg0, err = graphql.UnmarshalBoolean(tmp) + arg0, err = gql.UnmarshalBoolean(tmp) if err != nil { return nil, err } @@ -117,219 +104,216 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg return args, nil } -func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret gql.Marshaler) { rawArgs := field.ArgumentMap(ec.Variables) args, err := ec.field_Query___type_args(ctx, rawArgs) if err != nil { - ec.Error(ctx, err) - return graphql.Null + return gql.Null } res := ec.introspectType(args["name"].(string)) return ec.marshalType(ctx, field.Selections, res) } -func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) (ret gql.Marshaler) { res := ec.introspectSchema() return ec.___Schema(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { +func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *wrapper.Directive) (ret gql.Marshaler) { res := obj.Name - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___Directive_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { +func (ec *executionContext) ___Directive_description(ctx context.Context, field graphql.CollectedField, obj *wrapper.Directive) (ret gql.Marshaler) { res := obj.Description - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___Directive_locations(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { +func (ec *executionContext) ___Directive_locations(ctx context.Context, field graphql.CollectedField, obj *wrapper.Directive) (ret gql.Marshaler) { res := obj.Locations return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { +func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql.CollectedField, obj *wrapper.Directive) (ret gql.Marshaler) { res := obj.Args return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { +func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql.CollectedField, obj *wrapper.EnumValue) (ret gql.Marshaler) { res := obj.Name - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___EnumValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { +func (ec *executionContext) ___EnumValue_description(ctx context.Context, field graphql.CollectedField, obj *wrapper.EnumValue) (ret gql.Marshaler) { res := obj.Description - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { +func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *wrapper.EnumValue) (ret gql.Marshaler) { res := obj.IsDeprecated() - return graphql.MarshalBoolean(res) + return gql.MarshalBoolean(res) } -func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { +func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *wrapper.EnumValue) (ret gql.Marshaler) { res := obj.DeprecationReason() if res == nil { - return graphql.Null + return gql.Null } - return graphql.MarshalString(*res) + return gql.MarshalString(*res) } -func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { +func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.CollectedField, obj *wrapper.Field) (ret gql.Marshaler) { res := obj.Name - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___Field_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { +func (ec *executionContext) ___Field_description(ctx context.Context, field graphql.CollectedField, obj *wrapper.Field) (ret gql.Marshaler) { res := obj.Description - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___Field_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { +func (ec *executionContext) ___Field_args(ctx context.Context, field graphql.CollectedField, obj *wrapper.Field) (ret gql.Marshaler) { res := obj.Args return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___Field_type(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { +func (ec *executionContext) ___Field_type(ctx context.Context, field graphql.CollectedField, obj *wrapper.Field) (ret gql.Marshaler) { res := obj.Type if res == nil { - return graphql.Null + return gql.Null } return ec.___Type(ctx, field.Selections, res) } -func (ec *executionContext) ___Field_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { +func (ec *executionContext) ___Field_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *wrapper.Field) (ret gql.Marshaler) { res := obj.IsDeprecated() - return graphql.MarshalBoolean(res) + return gql.MarshalBoolean(res) } -func (ec *executionContext) ___Field_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { +func (ec *executionContext) ___Field_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *wrapper.Field) (ret gql.Marshaler) { res := obj.DeprecationReason() if res == nil { - return graphql.Null + return gql.Null } - return graphql.MarshalString(*res) + return gql.MarshalString(*res) } -func (ec *executionContext) ___InputValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { +func (ec *executionContext) ___InputValue_name(ctx context.Context, field graphql.CollectedField, obj *wrapper.InputValue) (ret gql.Marshaler) { res := obj.Name - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___InputValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { +func (ec *executionContext) ___InputValue_description(ctx context.Context, field graphql.CollectedField, obj *wrapper.InputValue) (ret gql.Marshaler) { res := obj.Description - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___InputValue_type(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { +func (ec *executionContext) ___InputValue_type(ctx context.Context, field graphql.CollectedField, obj *wrapper.InputValue) (ret gql.Marshaler) { res := obj.Type if res == nil { - return graphql.Null + return gql.Null } return ec.___Type(ctx, field.Selections, res) } -func (ec *executionContext) ___InputValue_defaultValue(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { +func (ec *executionContext) ___InputValue_defaultValue(ctx context.Context, field graphql.CollectedField, obj *wrapper.InputValue) (ret gql.Marshaler) { res := obj.DefaultValue if res == nil { - return graphql.Null + return gql.Null } - return graphql.MarshalString(*res) + return gql.MarshalString(*res) } -func (ec *executionContext) ___Schema_types(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { +func (ec *executionContext) ___Schema_types(ctx context.Context, field graphql.CollectedField, obj *wrapper.Schema) (ret gql.Marshaler) { res := obj.Types() return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___Schema_queryType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { +func (ec *executionContext) ___Schema_queryType(ctx context.Context, field graphql.CollectedField, obj *wrapper.Schema) (ret gql.Marshaler) { res := obj.QueryType() if res == nil { - return graphql.Null + return gql.Null } return ec.___Type(ctx, field.Selections, res) } -func (ec *executionContext) ___Schema_mutationType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { +func (ec *executionContext) ___Schema_mutationType(ctx context.Context, field graphql.CollectedField, obj *wrapper.Schema) (ret gql.Marshaler) { res := obj.MutationType() return ec.marshalType(ctx, field.Selections, res) } -func (ec *executionContext) ___Schema_subscriptionType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { +func (ec *executionContext) ___Schema_subscriptionType(ctx context.Context, field graphql.CollectedField, obj *wrapper.Schema) (ret gql.Marshaler) { res := obj.SubscriptionType() return ec.marshalType(ctx, field.Selections, res) } -func (ec *executionContext) ___Schema_directives(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { +func (ec *executionContext) ___Schema_directives(ctx context.Context, field graphql.CollectedField, obj *wrapper.Schema) (ret gql.Marshaler) { res := obj.Directives() return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_kind(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { +func (ec *executionContext) ___Type_kind(ctx context.Context, field graphql.CollectedField, obj *wrapper.Type) (ret gql.Marshaler) { res := obj.Kind() - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___Type_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { +func (ec *executionContext) ___Type_name(ctx context.Context, field graphql.CollectedField, obj *wrapper.Type) (ret gql.Marshaler) { res := obj.Name() if res == nil { - return graphql.Null + return gql.Null } - return graphql.MarshalString(*res) + return gql.MarshalString(*res) } -func (ec *executionContext) ___Type_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { +func (ec *executionContext) ___Type_description(ctx context.Context, field graphql.CollectedField, obj *wrapper.Type) (ret gql.Marshaler) { res := obj.Description() - return graphql.MarshalString(res) + return gql.MarshalString(res) } -func (ec *executionContext) ___Type_fields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { +func (ec *executionContext) ___Type_fields(ctx context.Context, field graphql.CollectedField, obj *wrapper.Type) (ret gql.Marshaler) { rawArgs := field.ArgumentMap(ec.Variables) args, err := ec.field___Type_fields_args(ctx, rawArgs) if err != nil { - ec.Error(ctx, err) - return graphql.Null + return gql.Null } res := obj.Fields(args["includeDeprecated"].(bool)) return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_interfaces(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { +func (ec *executionContext) ___Type_interfaces(ctx context.Context, field graphql.CollectedField, obj *wrapper.Type) (ret gql.Marshaler) { res := obj.Interfaces() return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_possibleTypes(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { +func (ec *executionContext) ___Type_possibleTypes(ctx context.Context, field graphql.CollectedField, obj *wrapper.Type) (ret gql.Marshaler) { res := obj.PossibleTypes() return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_enumValues(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { +func (ec *executionContext) ___Type_enumValues(ctx context.Context, field graphql.CollectedField, obj *wrapper.Type) (ret gql.Marshaler) { rawArgs := field.ArgumentMap(ec.Variables) args, err := ec.field___Type_enumValues_args(ctx, rawArgs) if err != nil { - ec.Error(ctx, err) - return graphql.Null + return gql.Null } res := obj.EnumValues(args["includeDeprecated"].(bool)) return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_inputFields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { +func (ec *executionContext) ___Type_inputFields(ctx context.Context, field graphql.CollectedField, obj *wrapper.Type) (ret gql.Marshaler) { res := obj.InputFields() return ec.marshalArray(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { +func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.CollectedField, obj *wrapper.Type) (ret gql.Marshaler) { res := obj.OfType() return ec.marshalType(ctx, field.Selections, res) } -func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { +func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, queryImplementors) out := graphql.NewFieldSet(fields) @@ -337,7 +321,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("Query") + out.Values[i] = gql.MarshalString("Query") case "__type": out.Values[i] = ec._Query___type(ctx, field) case "__schema": @@ -348,12 +332,12 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } out.Dispatch() if invalids > 0 { - return graphql.Null + return gql.Null } return out } -func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler { +func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *wrapper.Directive) gql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, __DirectiveImplementors) out := graphql.NewFieldSet(fields) @@ -361,22 +345,22 @@ func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionS for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("__Directive") + out.Values[i] = gql.MarshalString("__Directive") case "name": out.Values[i] = ec.___Directive_name(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "description": out.Values[i] = ec.___Directive_description(ctx, field, obj) case "locations": out.Values[i] = ec.___Directive_locations(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "args": out.Values[i] = ec.___Directive_args(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } default: @@ -385,12 +369,12 @@ func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionS } out.Dispatch() if invalids > 0 { - return graphql.Null + return gql.Null } return out } -func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionSet, obj *introspection.EnumValue) graphql.Marshaler { +func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionSet, obj *wrapper.EnumValue) gql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, __EnumValueImplementors) out := graphql.NewFieldSet(fields) @@ -398,17 +382,17 @@ func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionS for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("__EnumValue") + out.Values[i] = gql.MarshalString("__EnumValue") case "name": out.Values[i] = ec.___EnumValue_name(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "description": out.Values[i] = ec.___EnumValue_description(ctx, field, obj) case "isDeprecated": out.Values[i] = ec.___EnumValue_isDeprecated(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "deprecationReason": @@ -419,12 +403,12 @@ func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionS } out.Dispatch() if invalids > 0 { - return graphql.Null + return gql.Null } return out } -func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, obj *introspection.Field) graphql.Marshaler { +func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, obj *wrapper.Field) gql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, __FieldImplementors) out := graphql.NewFieldSet(fields) @@ -432,27 +416,27 @@ func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("__Field") + out.Values[i] = gql.MarshalString("__Field") case "name": out.Values[i] = ec.___Field_name(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "description": out.Values[i] = ec.___Field_description(ctx, field, obj) case "args": out.Values[i] = ec.___Field_args(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "type": out.Values[i] = ec.___Field_type(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "isDeprecated": out.Values[i] = ec.___Field_isDeprecated(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "deprecationReason": @@ -463,12 +447,12 @@ func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, } out.Dispatch() if invalids > 0 { - return graphql.Null + return gql.Null } return out } -func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.SelectionSet, obj *introspection.InputValue) graphql.Marshaler { +func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.SelectionSet, obj *wrapper.InputValue) gql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, __InputValueImplementors) out := graphql.NewFieldSet(fields) @@ -476,17 +460,17 @@ func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.Selection for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("__InputValue") + out.Values[i] = gql.MarshalString("__InputValue") case "name": out.Values[i] = ec.___InputValue_name(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "description": out.Values[i] = ec.___InputValue_description(ctx, field, obj) case "type": out.Values[i] = ec.___InputValue_type(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "defaultValue": @@ -497,12 +481,12 @@ func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.Selection } out.Dispatch() if invalids > 0 { - return graphql.Null + return gql.Null } return out } -func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, obj *introspection.Schema) graphql.Marshaler { +func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, obj *wrapper.Schema) gql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, __SchemaImplementors) out := graphql.NewFieldSet(fields) @@ -510,15 +494,15 @@ func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("__Schema") + out.Values[i] = gql.MarshalString("__Schema") case "types": out.Values[i] = ec.___Schema_types(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "queryType": out.Values[i] = ec.___Schema_queryType(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "mutationType": @@ -527,7 +511,7 @@ func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, out.Values[i] = ec.___Schema_subscriptionType(ctx, field, obj) case "directives": out.Values[i] = ec.___Schema_directives(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } default: @@ -536,12 +520,12 @@ func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, } out.Dispatch() if invalids > 0 { - return graphql.Null + return gql.Null } return out } -func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, obj *wrapper.Type) gql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, __TypeImplementors) out := graphql.NewFieldSet(fields) @@ -549,10 +533,10 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("__Type") + out.Values[i] = gql.MarshalString("__Type") case "kind": out.Values[i] = ec.___Type_kind(ctx, field, obj) - if out.Values[i] == graphql.Null { + if out.Values[i] == gql.Null { invalids++ } case "name": @@ -577,12 +561,12 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o } out.Dispatch() if invalids > 0 { - return graphql.Null + return gql.Null } return out } -func (ec *executionContext) marshalArray(ctx context.Context, sel ast.SelectionSet, v interface{}) graphql.Marshaler { +func (ec *executionContext) marshalArray(ctx context.Context, sel ast.SelectionSet, v interface{}) gql.Marshaler { if reflect.TypeOf(v).Kind() != reflect.Slice { panic("expected slice type") } @@ -590,10 +574,10 @@ func (ec *executionContext) marshalArray(ctx context.Context, sel ast.SelectionS slice := reflect.ValueOf(v) len := slice.Len() if len == 0 { - return graphql.Array{} + return gql.Array{} } - ret := make(graphql.Array, len) + ret := make(gql.Array, len) var wg sync.WaitGroup isLen1 := len == 1 if !isLen1 { @@ -607,18 +591,18 @@ func (ec *executionContext) marshalArray(ctx context.Context, sel ast.SelectionS defer wg.Done() } switch typ := v.(type) { - case introspection.Type: + case wrapper.Type: ret[i] = ec.___Type(ctx, sel, &typ) - case introspection.Directive: + case wrapper.Directive: ret[i] = ec.___Directive(ctx, sel, &typ) - case introspection.InputValue: + case wrapper.InputValue: ret[i] = ec.___InputValue(ctx, sel, &typ) - case introspection.Field: + case wrapper.Field: ret[i] = ec.___Field(ctx, sel, &typ) - case introspection.EnumValue: + case wrapper.EnumValue: ret[i] = ec.___EnumValue(ctx, sel, &typ) case string: - ret[i] = graphql.MarshalString(typ) + ret[i] = gql.MarshalString(typ) default: panic(fmt.Sprintf("unknown type %T", typ)) } @@ -634,9 +618,9 @@ func (ec *executionContext) marshalArray(ctx context.Context, sel ast.SelectionS return ret } -func (ec *executionContext) marshalType(ctx context.Context, sel ast.SelectionSet, v *introspection.Type) graphql.Marshaler { +func (ec *executionContext) marshalType(ctx context.Context, sel ast.SelectionSet, v *wrapper.Type) gql.Marshaler { if v == nil { - return graphql.Null + return gql.Null } return ec.___Type(ctx, sel, v) } diff --git a/internal/graphql/context_operation.go b/internal/graphql/context_operation.go new file mode 100644 index 0000000000000..1089e20535b23 --- /dev/null +++ b/internal/graphql/context_operation.go @@ -0,0 +1,43 @@ +package graphql + +import ( + "context" + "errors" + + "github.com/Code-Hex/gqlparser/v2/ast" +) + +type OperationContext struct { + RawQuery string + Variables map[string]interface{} + OperationName string + Doc *ast.QueryDocument + + Operation *ast.OperationDefinition +} + +func (c *OperationContext) Validate(ctx context.Context) error { + if c.Doc == nil { + return errors.New("field 'Doc'is required") + } + if c.RawQuery == "" { + return errors.New("field 'RawQuery' is required") + } + if c.Variables == nil { + c.Variables = make(map[string]interface{}) + } + return nil +} + +type operationCtx struct{} + +func GetOperationContext(ctx context.Context) *OperationContext { + if val, ok := ctx.Value(operationCtx{}).(*OperationContext); ok && val != nil { + return val + } + panic("missing operation context") +} + +func WithOperationContext(ctx context.Context, rc *OperationContext) context.Context { + return context.WithValue(ctx, operationCtx{}, rc) +} diff --git a/internal/graphql/context_operation_test.go b/internal/graphql/context_operation_test.go new file mode 100644 index 0000000000000..6e14885950462 --- /dev/null +++ b/internal/graphql/context_operation_test.go @@ -0,0 +1,20 @@ +package graphql + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestGetOperationContext(t *testing.T) { + rc := &OperationContext{} + + ctx := WithOperationContext(context.Background(), rc) + + got := GetOperationContext(ctx) + + if diff := cmp.Diff(rc, got); diff != "" { + t.Errorf("(-want, +got)\n%s", diff) + } +} diff --git a/internal/graphql/executable_schema.go b/internal/graphql/executable_schema.go new file mode 100644 index 0000000000000..6c54002c9db02 --- /dev/null +++ b/internal/graphql/executable_schema.go @@ -0,0 +1,134 @@ +package graphql + +import ( + "fmt" + + "github.com/Code-Hex/gqlparser/v2/ast" +) + +// CollectFields returns the set of fields from an ast.SelectionSet where all collected fields satisfy at least one of the GraphQL types +// passed through satisfies. Providing an empty or nil slice for satisfies will return collect all fields regardless of fragment +// type conditions. +func CollectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies []string) []CollectedField { + return collectFields(reqCtx, selSet, satisfies, map[string]bool{}) +} + +func collectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies []string, visited map[string]bool) []CollectedField { + groupedFields := make([]CollectedField, 0, len(selSet)) + + for _, sel := range selSet { + switch sel := sel.(type) { + case *ast.Field: + if !shouldIncludeNode(sel.Directives, reqCtx.Variables) { + continue + } + f := getOrCreateAndAppendField(&groupedFields, sel.Name, sel.Alias, sel.ObjectDefinition, func() CollectedField { + return CollectedField{Field: sel} + }) + + f.Selections = append(f.Selections, sel.SelectionSet...) + case *ast.InlineFragment: + if !shouldIncludeNode(sel.Directives, reqCtx.Variables) { + continue + } + if len(satisfies) > 0 && !instanceOf(sel.TypeCondition, satisfies) { + continue + } + for _, childField := range collectFields(reqCtx, sel.SelectionSet, satisfies, visited) { + f := getOrCreateAndAppendField(&groupedFields, childField.Name, childField.Alias, childField.ObjectDefinition, func() CollectedField { return childField }) + f.Selections = append(f.Selections, childField.Selections...) + } + + case *ast.FragmentSpread: + if !shouldIncludeNode(sel.Directives, reqCtx.Variables) { + continue + } + fragmentName := sel.Name + if _, seen := visited[fragmentName]; seen { + continue + } + visited[fragmentName] = true + + fragment := reqCtx.Doc.Fragments.ForName(fragmentName) + if fragment == nil { + // should never happen, validator has already run + panic(fmt.Errorf("missing fragment %s", fragmentName)) + } + + if len(satisfies) > 0 && !instanceOf(fragment.TypeCondition, satisfies) { + continue + } + + for _, childField := range collectFields(reqCtx, fragment.SelectionSet, satisfies, visited) { + f := getOrCreateAndAppendField(&groupedFields, childField.Name, childField.Alias, childField.ObjectDefinition, func() CollectedField { return childField }) + f.Selections = append(f.Selections, childField.Selections...) + } + default: + panic(fmt.Errorf("unsupported %T", sel)) + } + } + + return groupedFields +} + +type CollectedField struct { + *ast.Field + + Selections ast.SelectionSet +} + +func instanceOf(val string, satisfies []string) bool { + for _, s := range satisfies { + if val == s { + return true + } + } + return false +} + +func getOrCreateAndAppendField(c *[]CollectedField, name string, alias string, objectDefinition *ast.Definition, creator func() CollectedField) *CollectedField { + for i, cf := range *c { + if cf.Name == name && cf.Alias == alias && (cf.ObjectDefinition == objectDefinition || (cf.ObjectDefinition != nil && objectDefinition != nil && cf.ObjectDefinition.Name == objectDefinition.Name)) { + return &(*c)[i] + } + } + + f := creator() + + *c = append(*c, f) + return &(*c)[len(*c)-1] +} + +func shouldIncludeNode(directives ast.DirectiveList, variables map[string]interface{}) bool { + if len(directives) == 0 { + return true + } + + skip, include := false, true + + if d := directives.ForName("skip"); d != nil { + skip = resolveIfArgument(d, variables) + } + + if d := directives.ForName("include"); d != nil { + include = resolveIfArgument(d, variables) + } + + return !skip && include +} + +func resolveIfArgument(d *ast.Directive, variables map[string]interface{}) bool { + arg := d.Arguments.ForName("if") + if arg == nil { + panic(fmt.Sprintf("%s: argument 'if' not defined", d.Name)) + } + value, err := arg.Value.Value(variables) + if err != nil { + panic(err) + } + ret, ok := value.(bool) + if !ok { + panic(fmt.Sprintf("%s: argument 'if' is not a boolean", d.Name)) + } + return ret +} diff --git a/internal/graphql/fieldset.go b/internal/graphql/fieldset.go new file mode 100644 index 0000000000000..437ff347b1480 --- /dev/null +++ b/internal/graphql/fieldset.go @@ -0,0 +1,104 @@ +package graphql + +import ( + "io" + "sync" + + gql "github.com/99designs/gqlgen/graphql" +) + +type FieldSet struct { + fields []CollectedField + Values []gql.Marshaler + delayed []delayedResult +} + +type delayedResult struct { + i int + f func() gql.Marshaler +} + +func NewFieldSet(fields []CollectedField) *FieldSet { + return &FieldSet{ + fields: fields, + Values: make([]gql.Marshaler, len(fields)), + } +} + +func (m *FieldSet) Concurrently(i int, f func() gql.Marshaler) { + m.delayed = append(m.delayed, delayedResult{i: i, f: f}) +} + +func (m *FieldSet) Dispatch() { + if len(m.delayed) == 1 { + // only one concurrent task, no need to spawn a goroutine or deal create waitgroups + d := m.delayed[0] + m.Values[d.i] = d.f() + } else if len(m.delayed) > 1 { + // more than one concurrent task, use the main goroutine to do one, only spawn goroutines for the others + + var wg sync.WaitGroup + for _, d := range m.delayed[1:] { + wg.Add(1) + go func(d delayedResult) { + m.Values[d.i] = d.f() + wg.Done() + }(d) + } + + m.Values[m.delayed[0].i] = m.delayed[0].f() + wg.Wait() + } +} + +var openBrace = []byte(`{`) +var closeBrace = []byte(`}`) +var colon = []byte(`:`) +var comma = []byte(`,`) + +func (m *FieldSet) MarshalGQL(writer io.Writer) { + writer.Write(openBrace) + for i, field := range m.fields { + if i != 0 { + writer.Write(comma) + } + writeQuotedString(writer, field.Alias) + writer.Write(colon) + m.Values[i].MarshalGQL(writer) + } + writer.Write(closeBrace) +} + +const encodeHex = "0123456789ABCDEF" + +func writeQuotedString(w io.Writer, s string) { + start := 0 + io.WriteString(w, `"`) + + for i, c := range s { + if c < 0x20 || c == '\\' || c == '"' { + io.WriteString(w, s[start:i]) + + switch c { + case '\t': + io.WriteString(w, `\t`) + case '\r': + io.WriteString(w, `\r`) + case '\n': + io.WriteString(w, `\n`) + case '\\': + io.WriteString(w, `\\`) + case '"': + io.WriteString(w, `\"`) + default: + io.WriteString(w, `\u00`) + w.Write([]byte{encodeHex[c>>4], encodeHex[c&0xf]}) + } + + start = i + 1 + } + } + + io.WriteString(w, s[start:]) + io.WriteString(w, `"`) +} diff --git a/internal/introspection/introspection.go b/internal/introspection/introspection.go index 25f1afac65efd..f25b3861908c8 100644 --- a/internal/introspection/introspection.go +++ b/internal/introspection/introspection.go @@ -1,3 +1,4 @@ +// introspection implements the spec defined in https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md#schema-introspection package introspection import ( @@ -9,6 +10,7 @@ import ( "github.com/Code-Hex/gqldoc/internal/pool" ) +// https://github.com/graphql/graphql-js/blob/main/src/utilities/getIntrospectionQuery.js const Query = ` query IntrospectionQuery { __schema { @@ -137,7 +139,7 @@ type Schema struct { MutationType *OperationType `json:"mutationType"` SubscriptionType *OperationType `json:"subscriptionType"` Types []*Types `json:"types"` - Directives []*Directives `json:"directives"` + Directives []*Directive `json:"directives"` memoizeTypes map[string]*Types } @@ -400,10 +402,12 @@ func (a Args) String() string { } type Arg struct { - Name string `json:"name"` - Description string `json:"description"` - Type *Type `json:"type"` - DefaultValue *string `json:"defaultValue"` + Name string `json:"name"` + Description string `json:"description"` + Type *Type `json:"type"` + DefaultValue *string `json:"defaultValue"` + IsDeprecated bool `json:"isDeprecated"` + DeprecationReason string `json:"deprecationReason"` } func (a *Arg) String() string { @@ -417,14 +421,14 @@ func (a *Arg) String() string { return ret } -type Directives struct { +type Directive struct { Name string `json:"name"` Description string `json:"description"` Locations []string `json:"locations"` Args Args `json:"args"` } -func (d *Directives) String() string { +func (d *Directive) String() string { if d == nil { return "" } @@ -473,10 +477,12 @@ func (f *Field) String() string { } type InputValue struct { - Name string `json:"name"` - Description string `json:"description"` - Type *Type `json:"type"` - DefaultValue *string `json:"defaultValue"` + Name string `json:"name"` + Description string `json:"description"` + Type *Type `json:"type"` + DefaultValue *string `json:"defaultValue"` + IsDeprecated bool `json:"isDeprecated"` + DeprecationReason string `json:"deprecationReason"` } func (i *InputValue) String() string { diff --git a/internal/wrapper/schema.go b/internal/wrapper/schema.go new file mode 100644 index 0000000000000..0a3c1e82f83c7 --- /dev/null +++ b/internal/wrapper/schema.go @@ -0,0 +1,72 @@ +package wrapper + +import ( + "strings" + + "github.com/Code-Hex/gqlparser/v2/ast" +) + +type Schema struct { + schema *ast.Schema +} + +func (s *Schema) Types() []Type { + types := make([]Type, 0, len(s.schema.Types)) + for _, typ := range s.schema.Types { + if strings.HasPrefix(typ.Name, "__") { + continue + } + types = append(types, *WrapTypeFromDef(s.schema, typ)) + } + return types +} + +func (s *Schema) QueryType() *Type { + return WrapTypeFromDef(s.schema, s.schema.Query) +} + +func (s *Schema) MutationType() *Type { + return WrapTypeFromDef(s.schema, s.schema.Mutation) +} + +func (s *Schema) SubscriptionType() *Type { + return WrapTypeFromDef(s.schema, s.schema.Subscription) +} + +func (s *Schema) Description() string { + return s.schema.Description +} + +func (s *Schema) Directives() []Directive { + res := make([]Directive, 0, len(s.schema.Directives)) + + for _, d := range s.schema.Directives { + res = append(res, s.directiveFromDef(d)) + } + + return res +} + +func (s *Schema) directiveFromDef(d *ast.DirectiveDefinition) Directive { + locs := make([]string, len(d.Locations)) + for i, loc := range d.Locations { + locs[i] = string(loc) + } + + args := make([]InputValue, len(d.Arguments)) + for i, arg := range d.Arguments { + args[i] = InputValue{ + Name: arg.Name, + Description: arg.Description, + DefaultValue: defaultValue(arg.DefaultValue), + Type: WrapTypeFromType(s.schema, arg.Type), + } + } + + return Directive{ + Name: d.Name, + Description: d.Description, + Locations: locs, + Args: args, + } +} diff --git a/internal/wrapper/type.go b/internal/wrapper/type.go new file mode 100644 index 0000000000000..75df8b8835b92 --- /dev/null +++ b/internal/wrapper/type.go @@ -0,0 +1,180 @@ +package wrapper + +import ( + "strings" + + "github.com/Code-Hex/gqlparser/v2/ast" +) + +type Type struct { + schema *ast.Schema + def *ast.Definition + typ *ast.Type +} + +func WrapTypeFromDef(s *ast.Schema, def *ast.Definition) *Type { + if def == nil { + return nil + } + return &Type{schema: s, def: def} +} + +func WrapTypeFromType(s *ast.Schema, typ *ast.Type) *Type { + if typ == nil { + return nil + } + + if !typ.NonNull && typ.NamedType != "" { + return &Type{schema: s, def: s.Types[typ.NamedType]} + } + return &Type{schema: s, typ: typ} +} + +func (t *Type) Kind() string { + if t.typ != nil { + if t.typ.NonNull { + return "NON_NULL" + } + + if t.typ.Elem != nil { + return "LIST" + } + } else { + return string(t.def.Kind) + } + + panic("UNKNOWN") +} + +func (t *Type) Name() *string { + if t.def == nil { + return nil + } + return &t.def.Name +} + +func (t *Type) Description() string { + if t.def == nil { + return "" + } + return t.def.Description +} + +func (t *Type) Fields(includeDeprecated bool) []Field { + if t.def == nil || (t.def.Kind != ast.Object && t.def.Kind != ast.Interface) { + return []Field{} + } + fields := []Field{} + for _, f := range t.def.Fields { + if strings.HasPrefix(f.Name, "__") { + continue + } + + if !includeDeprecated && f.Directives.ForName("deprecated") != nil { + continue + } + + var args []InputValue + for _, arg := range f.Arguments { + args = append(args, InputValue{ + Type: WrapTypeFromType(t.schema, arg.Type), + Name: arg.Name, + Description: arg.Description, + DefaultValue: defaultValue(arg.DefaultValue), + }) + } + + fields = append(fields, Field{ + Name: f.Name, + Description: f.Description, + Args: args, + Type: WrapTypeFromType(t.schema, f.Type), + deprecation: f.Directives.ForName("deprecated"), + }) + } + return fields +} + +func (t *Type) InputFields() []InputValue { + if t.def == nil || t.def.Kind != ast.InputObject { + return []InputValue{} + } + + res := []InputValue{} + for _, f := range t.def.Fields { + res = append(res, InputValue{ + Name: f.Name, + Description: f.Description, + Type: WrapTypeFromType(t.schema, f.Type), + DefaultValue: defaultValue(f.DefaultValue), + }) + } + return res +} + +func defaultValue(value *ast.Value) *string { + if value == nil { + return nil + } + val := value.String() + return &val +} + +func (t *Type) Interfaces() []Type { + if t.def == nil || t.def.Kind != ast.Object { + return []Type{} + } + + res := []Type{} + for _, intf := range t.def.Interfaces { + res = append(res, *WrapTypeFromDef(t.schema, t.schema.Types[intf])) + } + + return res +} + +func (t *Type) PossibleTypes() []Type { + if t.def == nil || (t.def.Kind != ast.Interface && t.def.Kind != ast.Union) { + return []Type{} + } + + res := []Type{} + for _, pt := range t.schema.GetPossibleTypes(t.def) { + res = append(res, *WrapTypeFromDef(t.schema, pt)) + } + return res +} + +func (t *Type) EnumValues(includeDeprecated bool) []EnumValue { + if t.def == nil || t.def.Kind != ast.Enum { + return []EnumValue{} + } + + res := []EnumValue{} + for _, val := range t.def.EnumValues { + if !includeDeprecated && val.Directives.ForName("deprecated") != nil { + continue + } + + res = append(res, EnumValue{ + Name: val.Name, + Description: val.Description, + deprecation: val.Directives.ForName("deprecated"), + }) + } + return res +} + +func (t *Type) OfType() *Type { + if t.typ == nil { + return nil + } + if t.typ.NonNull { + // fake non null nodes + cpy := *t.typ + cpy.NonNull = false + + return WrapTypeFromType(t.schema, &cpy) + } + return WrapTypeFromType(t.schema, t.typ.Elem) +} diff --git a/internal/wrapper/type_test.go b/internal/wrapper/type_test.go new file mode 100644 index 0000000000000..0246dc88aa869 --- /dev/null +++ b/internal/wrapper/type_test.go @@ -0,0 +1,63 @@ +package wrapper + +import ( + "testing" + + "github.com/Code-Hex/gqlparser/v2/ast" +) + +func TestType(t *testing.T) { + schemaType := Type{ + def: &ast.Definition{ + Name: "Query", + Description: "test description", + Fields: ast.FieldList{ + &ast.FieldDefinition{Name: "__schema"}, + &ast.FieldDefinition{Name: "test"}, + &ast.FieldDefinition{Name: "deprecated", Directives: ast.DirectiveList{ + &ast.Directive{Name: "deprecated"}, + }}, + }, + Kind: ast.Object, + }, + } + + t.Run("name", func(t *testing.T) { + got := *schemaType.Name() + want := "Query" + if got != want { + t.Fatalf("want %q but got %q", want, got) + } + }) + + t.Run("description", func(t *testing.T) { + got := schemaType.Description() + want := "test description" + if got != want { + t.Fatalf("want %q but got %q", want, got) + } + }) + + t.Run("fields", func(t *testing.T) { + fields := schemaType.Fields(false) + if len(fields) != 1 { + t.Fatalf("len(fields) = %d", len(fields)) + } + if fields[0].Name != "test" { + t.Fatalf("fields[0].Name = %q", fields[0].Name) + } + }) + + t.Run("fields includeDepricated", func(t *testing.T) { + fields := schemaType.Fields(true) + if len(fields) != 2 { + t.Fatalf("len(fields) = %d", len(fields)) + } + if fields[0].Name != "test" { + t.Fatalf("fields[0].Name = %q", fields[0].Name) + } + if fields[1].Name != "deprecated" { + t.Fatalf("fields[0].Name = %q", fields[1].Name) + } + }) +} diff --git a/internal/wrapper/wrapper.go b/internal/wrapper/wrapper.go new file mode 100644 index 0000000000000..11489c7793e0a --- /dev/null +++ b/internal/wrapper/wrapper.go @@ -0,0 +1,71 @@ +package wrapper + +import "github.com/Code-Hex/gqlparser/v2/ast" + +type ( + Directive struct { + Name string + Description string + Locations []string + Args []InputValue + } + + EnumValue struct { + Name string + Description string + deprecation *ast.Directive + } + + Field struct { + Name string + Description string + Type *Type + Args []InputValue + deprecation *ast.Directive + } + + InputValue struct { + Name string + Description string + DefaultValue *string + Type *Type + } +) + +func WrapSchema(schema *ast.Schema) *Schema { + return &Schema{schema: schema} +} + +func (f *EnumValue) IsDeprecated() bool { + return f.deprecation != nil +} + +func (f *EnumValue) DeprecationReason() *string { + if f.deprecation == nil { + return nil + } + + reason := f.deprecation.Arguments.ForName("reason") + if reason == nil { + return nil + } + + return &reason.Value.Raw +} + +func (f *Field) IsDeprecated() bool { + return f.deprecation != nil +} + +func (f *Field) DeprecationReason() *string { + if f.deprecation == nil { + return nil + } + + reason := f.deprecation.Arguments.ForName("reason") + if reason == nil { + return nil + } + + return &reason.Value.Raw +} diff --git a/loader/loader.go b/loader/loader.go index 87d9c60025368..e58b2c9867d8d 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -1,16 +1,15 @@ package loader import ( - "context" "encoding/json" - "github.com/99designs/gqlgen/graphql" "github.com/Code-Hex/gqldoc/internal/gqlgen" + "github.com/Code-Hex/gqldoc/internal/graphql" "github.com/Code-Hex/gqldoc/internal/introspection" + "github.com/Code-Hex/gqlparser/v2/ast" + "github.com/Code-Hex/gqlparser/v2/parser" + "github.com/Code-Hex/gqlparser/v2/validator" "github.com/pkg/errors" - "github.com/vektah/gqlparser/v2/ast" - "github.com/vektah/gqlparser/v2/parser" - "github.com/vektah/gqlparser/v2/validator" ) func LoadSchema(filenames ...string) (*introspection.Root, error) { @@ -20,7 +19,7 @@ func LoadSchema(filenames ...string) (*introspection.Root, error) { } ctx, err := CreateOperationContext(Params{ - Schema: es.Schema(), + Schema: es.ParsedSchema, Query: introspection.Query, Variables: map[string]interface{}{}, }) @@ -28,13 +27,10 @@ func LoadSchema(filenames ...string) (*introspection.Root, error) { return nil, errors.WithStack(err) } - resp := es.Exec(ctx)(context.Background()) - if len(resp.Errors) > 0 { - return nil, resp.Errors - } + resp := es.Exec(ctx) var res introspection.Root - if err := json.Unmarshal(resp.Data, &res); err != nil { + if err := json.NewDecoder(resp).Decode(&res); err != nil { return nil, err } return &res, nil @@ -46,7 +42,7 @@ type Params struct { Variables map[string]interface{} } -func CreateOperationContext(params Params) (context.Context, error) { +func CreateOperationContext(params Params) (*graphql.OperationContext, error) { doc, err := parseQuery(params.Schema, params.Query) if err != nil { return nil, err @@ -60,14 +56,12 @@ func CreateOperationContext(params Params) (context.Context, error) { return nil, errors.WithStack(verr) } - opCtx := &graphql.OperationContext{ + return &graphql.OperationContext{ RawQuery: params.Query, Variables: variables, Doc: doc, Operation: operation, - } - ctx := graphql.WithOperationContext(context.Background(), opCtx) - return ctx, nil + }, nil } func parseQuery(schema *ast.Schema, query string) (*ast.QueryDocument, error) {