Tool, which generates GraphQL Schema for graphql-go package based on external data sources
$ go get github.com/EGT-Ukraine/go2gql/cmd/go2gql
$ ./go2gql -c "<config path>"
The generation process is built around plugins. Plugin is a go type that implements interface
type Plugin interface {
Init(*GenerateConfig, []Plugin) error
Prepare() error
Name() string
PrintInfo(info Infos)
Infos() map[string]string
Generate() error
}
1) reading config 2) plugins initialization ( calling Init() method of each plugin ) 3) plugins preparation ( calling Prepare() method of each plugin ) 3) plugins generation ( calling Generate() method of each plugin )
Default plugins places in ./generator/plugins
graphql plugingraphql generates:
- GraphQL Schema based on config and relying on data sent to him from other packages
- GraphQL types and resolvers relying on data sent to him from other packages
Here's a simple config example of how to generate such GraphQL schema
type Query {
fieldA FieldA
}
type FieldA {
srvField SomeServiceObj
}
type SomeServiceObj {
... SomeService methods
}
type Mutation {
... SomeServiceMutations methods
}
...
...
graphql_schemas:
- name: "ExampleSchema" # Schema name
output_path: "./services_api/schema/auth.go" # Where to put schema code
output_package: "schema" # Package name
queries:
type: "OBJECT"
fields:
- field: "fieldA"
type: "OBJECT" # Field type (OBJECT|SERVICE)
object_name: "FieldA"
fields:
- field: "srvField"
object_name: "SomeServiceObj"
service: "SomeService" # Service name
type: "SERVICE"
exclude_methods:
- "excluded_method_1"
filter_methods:
- "excluded_method_1"
- "excluded_method_3"
mutations:
# similary to queries
type: "SERVICE"
service: "SomeServiceMutations"
...
proto2gql pluginproto2gql plugin parses .proto files, defined in config and pass them to graphql plugin.
For each service of each proto file, proto2gql adds two services to graphql plugins with names:
- ServiceName or ServiceAlias
- ServiceNameMutations or ServiceAliasMutations
...
...
proto2gql:
output_path: "./proto_out/" # path, where to put generated code
paths: # path, where parser will search for imports
- "./vendor"
- "$GOPATH/src"
import_aliases: # imports aliases for all files
- google/protobuf/descriptor.proto: "github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.proto"
- google/protobuf/timestamp.proto: "github.com/golang/protobuf/ptypes/timestamp/timestamp.proto"
- google/protobuf/empty.proto: "github.com/golang/protobuf/ptypes/empty/empty.proto"
files:
# If you want to add some settings to imported .proto file, just add it here
- name: "Name" # name of .proto file
proto_path: "./a.proto" # path to .proto file
output_path: "./schema/name" # file-specific path, where to put generated code
proto_go_package: "github.com/gogo/protobuf/types" # Go package of .proto file grpc client (which was previously generated by `protoc`)
gql_messages_prefix: "Msg" # prefix, which will be added to all generated GraphQL Messages(including maps)
gql_enums_prefix: "Enm" # prefix, which will be added to all generated GraphQL Enums
paths: # file specific path, where parser will search for imports
- "./a_proto_deps/"
imports_aiases: # file specific imports aliases
- google/protobuf/timestamp.proto: "github.com/gogo/protobuf/protobuf/google/protobuf/timestamp.proto"
services: # services settings
"serviceName": # service name
service_name: "someAlias" # service name alias
methods:
"methodName": # method name
alias: "methodAlias"
request_type: "QUERY" # method type in GraphQL Schema (QUERY|MUTATION)
messages: # messages settings
- "Request$": # message name match regex
unwrap_field: true # unpack input message field. Useful for google.protobuf.wrappers.
fields:
"ip_address":
context_key: "ip" # context key, where unmarshaller will get this field from
- "Response$":
unwrap_field: true # In proto we can't use primitive or repeated type in method response.
# If unwrap_field = true unpack response gql object with 1 field.
error_field: "Error" # name of payload error field
- ...
- ...
...
...
swagger2gql pluginproto2gql plugin parses swagger files, defined in config and pass them to graphql plugin.
For each tag of each swagger file, proto2gql adds two services to graphql plugins with names:
- pascalized(tag-name) or ServiceAlias
- pascalized(tag-name)Mutations or ServiceAliasMutations
pascalized() here means, that it converts string to CamelCase format and removes all characters that is not valid in GraphQL Object name
...
...
swagger2gql:
output_path: "./swagger_out/" # Path, where to put generated code
objects: # GraphQL objects configs
- "Response$": # Object name
error_field: "error" # name of payload error field
- "InputParams$":
fields: # Object fields config
ip: # field name
context_key: "user_ip" # context key, where unmarshaller will get this field from
files:
- name: "Swagger file number 1" # swagger file name
path: "./service1/swagger.json" # path to swagger file
models_go_path: "github.com/myproject/service1/client/models" # path to generated using goswagger models
output_pkg: "gql_service1" # output go package name
output_path: "./service1/schema/" # file-specific path, where to put generated code
gql_objects_prefix: "Srv1" # prefix, which will be added to all generated GraphQL Objects
tags: # tags settings
"some-swagger-tag": # tag name
client_go_package: "github.com/myproject/service1/client/some_swagger_tag/client" # go client package
service_name: "SomeSwaggerTag" # service name alias
methods: # tag method settings
"/somes": # request path
get: # request method (get/post/put/options...)
alias: "get"
request_type: "QUERY" # request type (QUERY|MUTATIONS)
params_config: # file specific object parameters settings
- param_name: "user_id"
context_key: "user_id"
objects: # file specific objects settings
- "Request$":
fields:
"user_id":
context_key: "user_id"
...
...
Here's a simple plugin, that gets access to graphql plugin. Fetches information about services and their methods, and then, render them to .yml file
package main
import (
"os"
"github.com/pkg/errors"
"github.com/EGT-Ukraine/go2gql/generator"
"github.com/EGT-Ukraine/go2gql/generator/plugins/graphql"
)
const Name = "gql_services_rules"
type plugin struct {
gqlPlugin *graphql.Plugin
}
func (p *plugin) Init(_ *generator.GenerateConfig, plugins []generator.Plugin) error {
for _, plugin := range plugins {
if g, ok := plugin.(*graphql.Plugin); ok {
p.gqlPlugin = g
return nil
}
}
return errors.New("graphql plugin was not found")
}
func (p plugin) Generate() error {
file, err := os.OpenFile("./services_access.yml", os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
return errors.Wrap(err, "failed to open services_access.yml")
}
defer file.Close()
for _, typesFile := range p.gqlPlugin.Types() {
for _, service := range typesFile.Services {
if len(service.QueryMethods) == 0 {
continue
}
file.WriteString(service.Name + ":\n")
for _, method := range service.QueryMethods {
file.WriteString(" " + method.Name + ":\n")
}
}
}
return nil
}
func (plugin) Name() string { return Name }
func (plugin) Prepare() error { return nil }
func (plugin) PrintInfo(info generator.Infos) {}
func (plugin) Infos() map[string]string { return nil }
func Plugin() generator.Plugin {
return new(plugin)
}
func main(){}
Config example:
data_loaders:
output_path: "./generated/schema/loaders/"
proto2gql:
output_path: "./generated/schema"
paths:
- "vendor"
- "$GOPATH/src"
imports_aliases:
- google/protobuf/empty.proto: "github.com/golang/protobuf/ptypes/empty/empty.proto"
files:
- proto_path: "./apis/reviews.proto"
services:
ItemsReviewService:
methods:
List:
data_loaders:
ItemReviewsByIDs:
request_field: "filter.item_ids"
result_field: "reviews"
match_field: "item_id"
type: "1-N" # Relation type (1-N|1-1)
wait_duration: 1h1m1s ( WARNING!! this is only example, use some small duration)
- proto_path: "./apis/category.proto"
services:
CategoryService:
methods:
List:
data_loaders:
CategoriesByIDs:
request_field: "filter.ids"
result_field: "categories"
match_field: "id"
type: "1-1"
wait_duration: 1s
- proto_path: "./apis/user.proto"
services:
UserService:
methods:
List:
data_loaders:
UsersByIDs:
request_field: "filter.ids"
result_field: "users"
match_field: "id"
type: "1-1"
wait_duration: 1s
- proto_path: "./apis/items.proto"
services:
ItemsService:
methods:
List:
alias: "list"
request_type: "QUERY"
messages:
- "Item$":
data_loaders:
- field_name: "category"
key_field_name: "category_id"
data_loader_name: "CategoriesByIDs"
- field_name: "comments"
key_field_name: "id"
data_loader_name: "CommentsLoader"
- field_name: "reviews"
key_field_name: "id"
data_loader_name: "ItemReviewsByIDs"
swagger2gql:
output_path: "./generated/schema"
files:
- name: "Comments"
path: "apis/swagger.json"
models_go_path: "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/models"
tags:
"comments-controller":
service_name: "CommentsService"
client_go_package: "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/client/comments_controller"
methods:
"/items/comments/":
post:
data_loader_provider:
name: "CommentsLoader"
wait_duration_ms: 5
objects:
- "ItemComment$":
data_loaders:
- field_name: "user"
key_field_name: "user_id"
data_loader_name: "UserService"
graphql_schemas:
- name: "API"
output_path: "./generated/schema/api.go"
output_package: "schema"
queries:
type: "OBJECT"
fields:
- field: "items"
object_name: "Items"
service: "ItemsService"
type: "SERVICE"
Default wait duration 10ms.
Full example can be found in tests.
service_name config field is removed.
Use queries_service_name & mutations_service_name instead.
alias config field for services is removed.
Use queries_service_name & mutations_service_name instead.
queries_service_name & mutations_service_name config fields is removed.
Use service_name instead.
queries_service_name & mutations_service_name config fields is removed.
Use service_name instead.
tracer.Tracer was replaced with opentracing.Tracer
Schema initialization before:
``` import "github.com/EGT-Ukraine/go2gql/tracer"
var tracer tracer.Tracer
sch, err := schema.GetAPISchema(schemaApiClientsFactory.GetAP
$ claude mcp add go2gql \
-- python -m otcore.mcp_server <graph>