Adnan Maolood: 1 cmd/gqlintrospect: new command 2 files changed, 394 insertions(+), 0 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~emersion/public-inbox/patches/33799/mbox | git am -3Learn more about email & git
--- v4: remove termfmt dependency cmd/gqlintrospect/main.go | 305 +++++++++++++++++++++++++++++++++++++ cmd/gqlintrospect/types.go | 89 +++++++++++ 2 files changed, 394 insertions(+) create mode 100644 cmd/gqlintrospect/main.go create mode 100644 cmd/gqlintrospect/types.go diff --git a/cmd/gqlintrospect/main.go b/cmd/gqlintrospect/main.go new file mode 100644 index 0000000..724c125 --- /dev/null +++ b/cmd/gqlintrospect/main.go @@ -0,0 +1,305 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net/http" + "strings" + "time" + + "git.sr.ht/~emersion/gqlclient" +) + +// The query used to determine type information +const query = ` +query IntrospectionQuery { + __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 + } + } + } + } + } + } + } +} +` + +type stringSliceFlag []string + +func (v *stringSliceFlag) String() string { + return fmt.Sprint([]string(*v)) +} + +func (v *stringSliceFlag) Set(s string) error { + *v = append(*v, s) + return nil +} + +type transport struct { + http.RoundTripper + + header http.Header +} + +func (tr *transport) RoundTrip(req *http.Request) (*http.Response, error) { + for k, values := range tr.header { + for _, v := range values { + req.Header.Add(k, v) + } + } + return tr.RoundTripper.RoundTrip(req) +} + +func typeKeyword(t Type) string { + switch t.Kind { + case TypeKindObject: + return "type" + case TypeKindInterface: + return "interface" + default: + panic("unreachable") + } +} + +func typeReference(t Type) string { + var modifiers []TypeKind + + ofType := &t + for ofType.OfType != nil { + switch ofType.Kind { + case TypeKindList, TypeKindNonNull: + modifiers = append(modifiers, ofType.Kind) + default: + panic("invalid type") + } + ofType = ofType.OfType + } + + if ofType.Name == nil { + return "<invalid>" + } + typeName := *ofType.Name + if len(modifiers) > 0 { + for i := len(modifiers) - 1; i >= 0; i-- { + switch modifiers[i] { + case TypeKindList: + typeName = "[" + typeName + "]" + case TypeKindNonNull: + typeName += "!" + } + } + } + return typeName +} + +func printDescription(desc string, prefix string) { + if !strings.Contains(desc, "\n") { + fmt.Printf("%s\"%s\"\n", prefix, desc) + } else { + fmt.Printf("%s\"\"\"\n", prefix) + for _, line := range strings.Split(desc, "\n") { + fmt.Printf("%s%s\n", prefix, line) + } + fmt.Printf("%s\"\"\"\n", prefix) + } +} + +func printType(t Type) { + if t.Description != nil && *t.Description != "" { + printDescription(*t.Description, "") + } + switch t.Kind { + case TypeKindScalar: + fmt.Printf("scalar %s\n\n", *t.Name) + case TypeKindUnion: + fmt.Printf("union %s = ", *t.Name) + for idx, i := range t.Interfaces { + if idx > 0 { + fmt.Print(" | ") + } + fmt.Printf("%s", typeReference(i)) + } + fmt.Print("\n\n") + case TypeKindEnum: + fmt.Printf("enum %s {\n", *t.Name) + for _, e := range t.EnumValues { + if e.Description != nil && *e.Description != "" { + printDescription(*e.Description, "\t") + } + fmt.Printf(" %s\n", e.Name) + } + fmt.Print("}\n\n") + case TypeKindInputObject: + fmt.Printf("input %s {\n", *t.Name) + for _, f := range t.InputFields { + if f.Description != nil && *f.Description != "" { + printDescription(*f.Description, "\t") + } + fmt.Printf(" %s: %s", f.Name, typeReference(*f.Type)) + if f.DefaultValue != nil { + fmt.Printf(" = %s", *f.DefaultValue) + } + fmt.Println() + } + fmt.Print("}\n\n") + case TypeKindObject, TypeKindInterface: + fmt.Printf("%s %s {\n", typeKeyword(t), *t.Name) + for _, f := range t.Fields { + if f.Description != nil && *f.Description != "" { + printDescription(*f.Description, "\t") + } + fmt.Printf(" %s", f.Name) + if len(f.Args) > 0 { + fmt.Print("(") + for idx, a := range f.Args { + if idx > 0 { + fmt.Print(", ") + } + fmt.Printf("%s: %s", a.Name, typeReference(*a.Type)) + if a.DefaultValue != nil { + fmt.Printf(" = %s", *a.DefaultValue) + } + } + fmt.Print(")") + } + fmt.Printf(": %s\n", typeReference(*f.Type)) + } + fmt.Print("}\n\n") + } +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + var header []string + flag.Var((*stringSliceFlag)(&header), "H", "set HTTP header") + flag.Parse() + + endpoint := flag.Arg(0) + if endpoint == "" { + log.Fatalf("missing endpoint") + } + + op := gqlclient.NewOperation(query) + + tr := transport{ + RoundTripper: http.DefaultTransport, + header: make(http.Header), + } + httpClient := http.Client{Transport: &tr} + gqlClient := gqlclient.New(endpoint, &httpClient) + + for _, kv := range header { + parts := strings.SplitN(kv, ":", 2) + if len(parts) != 2 { + log.Fatalf("in header definition %q: missing colon", kv) + } + tr.header.Add(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) + } + + var data struct { + Schema Schema `json:"__schema"` + } + if err := gqlClient.Execute(ctx, op, &data); err != nil { + log.Fatal(err) + } + + for _, t := range data.Schema.Types { + if t.Name != nil && strings.HasPrefix(*t.Name, "__") { + continue + } + printType(t) + } +} diff --git a/cmd/gqlintrospect/types.go b/cmd/gqlintrospect/types.go new file mode 100644 index 0000000..f977ae3 --- /dev/null +++ b/cmd/gqlintrospect/types.go @@ -0,0 +1,89 @@ +package main + +type Directive struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` + Locations []DirectiveLocation `json:"locations"` + Args []InputValue `json:"args"` + IsRepeatable bool `json:"isRepeatable"` +} + +type DirectiveLocation string + +const ( + DirectiveLocationQuery DirectiveLocation = "QUERY" + DirectiveLocationMutation DirectiveLocation = "MUTATION" + DirectiveLocationSubscription DirectiveLocation = "SUBSCRIPTION" + DirectiveLocationField DirectiveLocation = "FIELD" + DirectiveLocationFragmentDefinition DirectiveLocation = "FRAGMENT_DEFINITION" + DirectiveLocationFragmentSpread DirectiveLocation = "FRAGMENT_SPREAD" + DirectiveLocationInlineFragment DirectiveLocation = "INLINE_FRAGMENT" + DirectiveLocationVariableDefinition DirectiveLocation = "VARIABLE_DEFINITION" + DirectiveLocationSchema DirectiveLocation = "SCHEMA" + DirectiveLocationScalar DirectiveLocation = "SCALAR" + DirectiveLocationObject DirectiveLocation = "OBJECT" + DirectiveLocationFieldDefinition DirectiveLocation = "FIELD_DEFINITION" + DirectiveLocationArgumentDefinition DirectiveLocation = "ARGUMENT_DEFINITION" + DirectiveLocationInterface DirectiveLocation = "INTERFACE" + DirectiveLocationUnion DirectiveLocation = "UNION" + DirectiveLocationEnum DirectiveLocation = "ENUM" + DirectiveLocationEnumValue DirectiveLocation = "ENUM_VALUE" + DirectiveLocationInputObject DirectiveLocation = "INPUT_OBJECT" + DirectiveLocationInputFieldDefinition DirectiveLocation = "INPUT_FIELD_DEFINITION" +) + +type EnumValue struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` + IsDeprecated bool `json:"isDeprecated"` + DeprecationReason *string `json:"deprecationReason,omitempty"` +} + +type Field struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` + Args []InputValue `json:"args"` + Type *Type `json:"type"` + IsDeprecated bool `json:"isDeprecated"` + DeprecationReason *string `json:"deprecationReason,omitempty"` +} + +type InputValue struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` + Type *Type `json:"type"` + DefaultValue *string `json:"defaultValue,omitempty"` +} + +type Schema struct { + Types []Type `json:"types"` + QueryType *Type `json:"queryType"` + MutationType *Type `json:"mutationType,omitempty"` + SubscriptionType *Type `json:"subscriptionType,omitempty"` + Directives []Directive `json:"directives"` +} + +type Type struct { + Kind TypeKind `json:"kind"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Fields []Field `json:"fields,omitempty"` + Interfaces []Type `json:"interfaces,omitempty"` + PossibleTypes []Type `json:"possibleTypes,omitempty"` + EnumValues []EnumValue `json:"enumValues,omitempty"` + InputFields []InputValue `json:"inputFields,omitempty"` + OfType *Type `json:"ofType,omitempty"` +} + +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" +) -- 2.37.0
I applied v4. Thanks!