├── bin └── .gitkeep ├── .gitignore ├── tests ├── dataloader │ ├── .gitignore │ ├── apis │ │ ├── user.proto │ │ ├── category.proto │ │ ├── reviews.proto │ │ ├── items.proto │ │ └── swagger.json │ ├── Makefile │ ├── mock │ │ └── loader.go │ └── generate.yml ├── protounwrap │ ├── .gitignore │ ├── mock │ │ └── loader.go │ ├── Makefile │ ├── generate.yml │ └── apis │ │ └── items.proto ├── Makefile ├── utils.go └── swagger_templates │ └── client │ └── client.gotmpl ├── example ├── simple_plugin │ ├── plugin.so │ └── plugin.go ├── Makefile ├── main.go ├── out │ ├── loaders │ │ └── loaders.go │ ├── schema │ │ └── example.go │ └── well_known │ │ └── timestamp.go ├── generate.yml └── proto │ └── example.proto ├── generator ├── plugins │ ├── swagger2gql │ │ ├── templates │ │ │ ├── method_caller_null.gohtml │ │ │ ├── method_caller.gohtml │ │ │ ├── value_resolver_datetime.gohtml │ │ │ ├── value_resolver_ptr_datetime.gohtml │ │ │ └── value_resolver_array.gohtml │ │ ├── parser │ │ │ ├── kind_string.go │ │ │ └── file.go │ │ ├── generator.go │ │ ├── map_input_objects.go │ │ ├── map_output_objects.go │ │ ├── map_input_objects_resolvers.go │ │ ├── input_objects.go │ │ ├── templates_resolvers.go │ │ ├── config.go │ │ ├── helpers.go │ │ ├── input_object_resolvers.go │ │ └── output_object.go │ ├── graphql │ │ ├── templates │ │ │ ├── schemas_head.gohtml │ │ │ ├── types_head.gohtml │ │ │ ├── output_fields.gohtml │ │ │ ├── output_map_fields.gohtml │ │ │ ├── schemas_body.gohtml │ │ │ └── types_service.gohtml │ │ ├── lib │ │ │ ├── names │ │ │ │ ├── field_names_test.go │ │ │ │ └── field_names.go │ │ │ ├── pluginconfig │ │ │ │ └── decoder.go │ │ │ └── importer │ │ │ │ ├── importer_test.go │ │ │ │ └── importer.go │ │ ├── config.go │ │ ├── field_renderers.go │ │ ├── helpers.go │ │ ├── type_resolvers.go │ │ ├── plugin_test.go │ │ └── schemas_generator.go │ ├── proto2gql │ │ ├── parser │ │ │ ├── type.go │ │ │ ├── scalar.go │ │ │ ├── service.go │ │ │ ├── type_name.go │ │ │ ├── type_name_test.go │ │ │ ├── helper.go │ │ │ ├── enum.go │ │ │ ├── message_test.go │ │ │ ├── parser.go │ │ │ └── message.go │ │ ├── enums.go │ │ ├── maps_inputs.go │ │ ├── maps_resolvers.go │ │ ├── maps_outputs.go │ │ ├── plugin.go │ │ ├── helpers.go │ │ └── config.go │ └── dataloader │ │ ├── templates │ │ ├── loaders_head.gohtml │ │ ├── output_object_fields.gohtml │ │ └── loaders_body.gohtml │ │ ├── config.go │ │ ├── dataloader.go │ │ ├── field_renderers.go │ │ └── plugin.go ├── config.go └── generator.go ├── testdata ├── a │ └── main.go ├── test_scope.proto ├── common │ ├── proto2.proto │ ├── common.proto │ ├── proto2.pb.go │ └── common.pb.go ├── test2.proto ├── Makefile ├── out │ ├── test │ │ └── github.com │ │ │ └── EGT-Ukraine │ │ │ └── go2gql │ │ │ └── testdata │ │ │ ├── test_scope.go │ │ │ └── common │ │ │ ├── proto2.go │ │ │ └── common.go │ └── schema.go ├── generate.yml ├── test_scope.pb.go └── test.proto ├── api ├── multipartfile │ └── file.go └── interceptors │ └── interceptor.go ├── .golangci.yml ├── cmd └── go2gql │ ├── basic_plugins.go │ ├── plugins_with_external.go │ └── main.go ├── Makefile ├── .travis.yml ├── LICENSE └── Gopkg.toml /bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .idea -------------------------------------------------------------------------------- /tests/dataloader/.gitignore: -------------------------------------------------------------------------------- 1 | generated 2 | mock/* 3 | !mock/loader.go -------------------------------------------------------------------------------- /tests/protounwrap/.gitignore: -------------------------------------------------------------------------------- 1 | generated 2 | mock/* 3 | !mock/loader.go -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | generate_tests_data: 2 | $(MAKE) -C dataloader/ 3 | $(MAKE) -C protounwrap/ 4 | -------------------------------------------------------------------------------- /example/simple_plugin/plugin.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EGT-Ukraine/go2gql/HEAD/example/simple_plugin/plugin.so -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/templates/method_caller_null.gohtml: -------------------------------------------------------------------------------- 1 | func(req {{$.reqType}}) (interface{}, error) { 2 | return {{$.clientVar}}.{{$.methodName}}(req.WithContext(ctx)) 3 | }({{$.reqVar}}) -------------------------------------------------------------------------------- /testdata/a/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/EGT-Ukraine/go2gql/testdata/out/test" 5 | ) 6 | 7 | func main() { 8 | test.GetServiceExampleServiceMethods(nil, nil, nil) 9 | } 10 | -------------------------------------------------------------------------------- /testdata/test_scope.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example.test_scope; 4 | 5 | option go_package = "github.com/EGT-Ukraine/go2gql/testdata"; 6 | 7 | enum ParentScopeEnum { 8 | TestEnumVal0 = 0; 9 | } 10 | -------------------------------------------------------------------------------- /tests/protounwrap/mock/loader.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "github.com/EGT-Ukraine/go2gql/tests/protounwrap/generated/clients/apis" 5 | ) 6 | 7 | type Clients struct { 8 | ItemsClient apis.ItemsServiceClient 9 | } 10 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | default: proto generate 2 | 3 | proto: 4 | @protoc -I=${GOPATH}/src:. --go_out=plugins=grpc:. proto/example.proto 5 | 6 | generate: 7 | cd ../ && $(MAKE) build_templates 8 | go run ../cmd/go2gql/ 9 | 10 | .PHONY: proto 11 | -------------------------------------------------------------------------------- /testdata/common/proto2.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package common; 3 | 4 | option go_package = "github.com/EGT-Ukraine/go2gql/testdata/common"; 5 | 6 | // Service, which do smth 7 | message Proto2Message { 8 | optional int32 scalar = 1; 9 | } 10 | -------------------------------------------------------------------------------- /generator/plugins/graphql/templates/schemas_head.gohtml: -------------------------------------------------------------------------------- 1 | // This file was generated by github.com/EGT-Ukraine/go2gql. DO NOT EDIT IT 2 | package {{$.package}} 3 | 4 | import ( 5 | {{range $import := $.imports -}} 6 | {{$import.Alias}} "{{$import.Path}}" 7 | {{end -}} 8 | ) 9 | -------------------------------------------------------------------------------- /generator/plugins/graphql/templates/types_head.gohtml: -------------------------------------------------------------------------------- 1 | // This file was generated by github.com/EGT-Ukraine/go2gql. DO NOT EDIT IT 2 | package {{$.package}} 3 | 4 | import ( 5 | {{range $import := $.imports -}} 6 | {{$import.Alias}} "{{$import.Path}}" 7 | {{end -}} 8 | ) 9 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/type.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | type TypeKind byte 4 | 5 | const ( 6 | TypeUndefined TypeKind = iota 7 | TypeScalar 8 | TypeMessage 9 | TypeEnum 10 | TypeMap 11 | ) 12 | 13 | type Type interface { 14 | String() string 15 | Kind() TypeKind 16 | File() *File 17 | } 18 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/templates/method_caller.gohtml: -------------------------------------------------------------------------------- 1 | func(req {{$.reqType}}) (_ {{$.respType}}, rerr error) { 2 | res, err := {{$.clientVar}}.{{$.methodName}}(req.WithContext(ctx)) 3 | if err != nil { 4 | rerr = err 5 | return 6 | } 7 | return res.Payload, nil 8 | }({{$.reqVar}}) -------------------------------------------------------------------------------- /tests/protounwrap/Makefile: -------------------------------------------------------------------------------- 1 | generate_test_data: 2 | # Schema 3 | rm -rf generated/* 4 | mkdir -p generated/clients 5 | protoc --go_out=paths=source_relative,plugins=grpc:generated/clients apis/items.proto 6 | go run ../../cmd/go2gql/main.go ../../cmd/go2gql/basic_plugins.go 7 | 8 | # Mocks 9 | go generate ./... 10 | -------------------------------------------------------------------------------- /generator/plugins/dataloader/templates/loaders_head.gohtml: -------------------------------------------------------------------------------- 1 | {{- /*gotype: github.com/EGT-Ukraine/go2gql/generator/plugins/dataloader.LoadersHeadContext*/ -}} 2 | 3 | package loaders 4 | 5 | import ( 6 | "context" 7 | 8 | {{range $import := $.Imports -}} 9 | {{$import.Alias}} "{{$import.Path}}" 10 | {{end -}} 11 | ) 12 | -------------------------------------------------------------------------------- /testdata/common/common.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package common; 3 | 4 | option go_package = "github.com/EGT-Ukraine/go2gql/testdata/common"; 5 | 6 | // Service, which do smth 7 | message CommonMessage { 8 | int32 scalar = 1; 9 | } 10 | enum CommonEnum { 11 | CommonEnumVal0 = 0; 12 | CommonEnumVal1 = 1; 13 | } -------------------------------------------------------------------------------- /api/multipartfile/file.go: -------------------------------------------------------------------------------- 1 | package multipartfile 2 | 3 | import ( 4 | "mime/multipart" 5 | ) 6 | 7 | type MultipartFile struct { 8 | multipart.File 9 | Header *multipart.FileHeader 10 | } 11 | 12 | func (m MultipartFile) Name() string { 13 | if m.Header == nil { 14 | return "" 15 | } 16 | return m.Header.Filename 17 | } 18 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/scalar.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | type Scalar struct { 4 | file *File 5 | ScalarName string 6 | } 7 | 8 | func (s Scalar) String() string { 9 | return s.ScalarName 10 | } 11 | 12 | func (s Scalar) Kind() TypeKind { 13 | return TypeScalar 14 | } 15 | 16 | func (s Scalar) File() *File { 17 | return s.file 18 | } 19 | -------------------------------------------------------------------------------- /testdata/test2.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "common/common.proto"; 4 | option go_package = "github.com/EGT-Ukraine/go2gql/testdata"; 5 | 6 | // Service, which do smth 7 | service AnotherServiceExample { 8 | rpc getEmptiesMsg (common.CommonMessage) returns (common.CommonMessage); 9 | } 10 | enum someEnum { 11 | Val1 = 0; 12 | Val2 = 1; 13 | } -------------------------------------------------------------------------------- /generator/plugins/dataloader/config.go: -------------------------------------------------------------------------------- 1 | package dataloader 2 | 3 | type DataLoadersConfig struct { 4 | OutputPath string `mapstructure:"output_path"` 5 | } 6 | 7 | type FieldConfig struct { 8 | FieldName string `mapstructure:"field_name"` 9 | KeyFieldName string `mapstructure:"key_field_name"` 10 | DataLoader string `mapstructure:"data_loader_name"` 11 | } 12 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/service.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | type Service struct { 4 | Name string 5 | QuotedComment string 6 | Methods map[string]*Method 7 | } 8 | 9 | type Method struct { 10 | Name string 11 | QuotedComment string 12 | InputMessage *Message 13 | OutputMessage *Message 14 | Service *Service 15 | } 16 | -------------------------------------------------------------------------------- /generator/plugins/graphql/lib/names/field_names_test.go: -------------------------------------------------------------------------------- 1 | package names 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFilterNotSupportedFieldNameCharacters(t *testing.T) { 10 | var tests = map[string]string{ 11 | "-hello": "hello", 12 | "0hello": "hello", 13 | "h0el_lo": "h0el_lo", 14 | } 15 | for in, out := range tests { 16 | assert.Equal(t, out, FilterNotSupportedFieldNameCharacters(in)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /testdata/Makefile: -------------------------------------------------------------------------------- 1 | default: proto 2 | 3 | proto: 4 | @protoc -I=${GOPATH}/src:. --go_out=plugins=grpc:${GOPATH}/src test.proto 5 | @protoc -I=${GOPATH}/src:. --go_out=plugins=grpc:${GOPATH}/src common/common.proto 6 | @protoc -I=${GOPATH}/src:. --go_out=plugins=grpc:${GOPATH}/src common/proto2.proto 7 | @protoc -I=${GOPATH}/src:. --go_out=plugins=grpc:${GOPATH}/src test_scope.proto 8 | @go run ../cmd/go2gql/main.go ../cmd/go2gql/basic_plugins.go 9 | 10 | .PHONY: proto 11 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/type_name.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | type TypeName []string 4 | 5 | func (t TypeName) NewSubTypeName(subName string) TypeName { 6 | var res = make(TypeName, len(t)+1) 7 | copy(res, t) 8 | res[len(t)] = subName 9 | return res 10 | } 11 | func (t TypeName) Equal(t2 TypeName) bool { 12 | if len(t) != len(t2) { 13 | return false 14 | } 15 | for i, v := range t { 16 | if t2[i] != v { 17 | return false 18 | } 19 | } 20 | return true 21 | } 22 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: true 3 | disable: 4 | - gochecknoglobals 5 | - scopelint 6 | - prealloc 7 | - maligned 8 | - unparam 9 | - megacheck 10 | - nakedret 11 | 12 | linters-settings: 13 | goimports: 14 | local-prefixes: github.com/EGT-Ukraine/go2gql 15 | gocyclo: 16 | min-complexity: 20 # todo: decrease 17 | lll: 18 | line-length: 180 # todo: decrease 19 | golint: 20 | min-confidence: 0 21 | dupl: 22 | threshold: 260 # todo: decrease 23 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/templates/value_resolver_datetime.gohtml: -------------------------------------------------------------------------------- 1 | func(arg interface{}) (_ {{strfmtPkg}}.DateTime, err error) { 2 | if arg == nil { 3 | return 4 | } 5 | a := arg.(map[string]interface{}) 6 | if a["seconds"] == nil || a["nanos"] == nil { 7 | err = {{errorsPkg}}.New("not all datetime parameters passed") 8 | return 9 | } 10 | t := {{timePkg}}.Unix(a["seconds"].(int64), int64(a["nanos"].(int32))) 11 | return {{strfmtPkg}}.DateTime(&t), nil 12 | }({{$.arg}}) -------------------------------------------------------------------------------- /tests/dataloader/apis/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package apis; 4 | 5 | option go_package = "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/apis;apis"; 6 | 7 | service UserService { 8 | rpc List (UserListRequest) returns (UserListResponse) {} 9 | } 10 | 11 | message UserListRequest { 12 | repeated int64 id = 1; 13 | } 14 | 15 | message UserListResponse { 16 | repeated User users = 1; 17 | } 18 | 19 | message User { 20 | int64 id = 1; 21 | string name = 2; 22 | } 23 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/templates/value_resolver_ptr_datetime.gohtml: -------------------------------------------------------------------------------- 1 | func(arg interface{}) (*{{strfmtPkg}}.DateTime, error) { 2 | if arg == nil { 3 | return nil, nil 4 | } 5 | a := arg.(map[string]interface{}) 6 | if a["seconds"] == nil || a["nanos"] == nil { 7 | return nil, {{errorsPkg}}.New("not all datetime parameters passed") 8 | } 9 | t := {{timePkg}}.Unix(a["seconds"].(int64), int64(a["nanos"].(int32))) 10 | return (*{{strfmtPkg}}.DateTime)(&t), nil 11 | }({{$.arg}}) 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /generator/plugins/graphql/lib/pluginconfig/decoder.go: -------------------------------------------------------------------------------- 1 | package pluginconfig 2 | 3 | import "github.com/mitchellh/mapstructure" 4 | 5 | func Decode(input, output interface{}) error { 6 | config := &mapstructure.DecoderConfig{ 7 | DecodeHook: mapstructure.ComposeDecodeHookFunc( 8 | mapstructure.StringToTimeDurationHookFunc(), 9 | ), 10 | Metadata: nil, 11 | Result: output, 12 | } 13 | 14 | decoder, err := mapstructure.NewDecoder(config) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | return decoder.Decode(input) 20 | } 21 | -------------------------------------------------------------------------------- /tests/dataloader/apis/category.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package apis; 4 | 5 | option go_package = "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/apis;apis"; 6 | 7 | service CategoryService { 8 | rpc List (CategoryListRequest) returns (CategoryListResponse) {} 9 | } 10 | 11 | message CategoryListRequest { 12 | repeated uint64 id = 1; 13 | } 14 | 15 | message CategoryListResponse { 16 | repeated Category categories = 1; 17 | } 18 | 19 | message Category { 20 | uint64 id = 1; 21 | string name = 2; 22 | } 23 | -------------------------------------------------------------------------------- /tests/dataloader/apis/reviews.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package apis; 4 | 5 | option go_package = "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/apis;apis"; 6 | 7 | import "google/protobuf/empty.proto"; 8 | 9 | service ItemsReviewService { 10 | rpc List (ListRequest) returns (ListResponse) { 11 | } 12 | } 13 | 14 | message ListRequest { 15 | repeated int64 item_id = 1; 16 | } 17 | 18 | message ListResponse { 19 | repeated Review reviews = 1; 20 | } 21 | 22 | 23 | message Review { 24 | int64 id = 1; 25 | int64 item_id = 2; 26 | string text = 3; 27 | } 28 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/parser/kind_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Kind"; DO NOT EDIT. 2 | 3 | package parser 4 | 5 | import "strconv" 6 | 7 | const _Kind_name = "KindUnknownKindStringKindInt32KindInt64KindFloat32KindFloat64KindBooleanKindArrayKindObjectKindMapKindFileKindDateTimeKindNull" 8 | 9 | var _Kind_index = [...]uint8{0, 11, 21, 30, 39, 50, 61, 72, 81, 91, 98, 106, 118, 126} 10 | 11 | func (i Kind) String() string { 12 | if i >= Kind(len(_Kind_index)-1) { 13 | return "Kind(" + strconv.FormatInt(int64(i), 10) + ")" 14 | } 15 | return _Kind_name[_Kind_index[i]:_Kind_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /cmd/go2gql/basic_plugins.go: -------------------------------------------------------------------------------- 1 | // +build NOT (linux OR darwin) 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/urfave/cli" 7 | 8 | "github.com/EGT-Ukraine/go2gql/generator" 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/dataloader" 10 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 11 | "github.com/EGT-Ukraine/go2gql/generator/plugins/proto2gql" 12 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql" 13 | ) 14 | 15 | func Plugins(c *cli.Context) []generator.Plugin { 16 | return []generator.Plugin{ 17 | new(dataloader.Plugin), 18 | new(graphql.Plugin), 19 | new(swagger2gql.Plugin), 20 | new(proto2gql.Plugin), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /testdata/out/test/github.com/EGT-Ukraine/go2gql/testdata/test_scope.go: -------------------------------------------------------------------------------- 1 | // This file was generated by github.com/EGT-Ukraine/go2gql. DO NOT EDIT IT 2 | package testdata 3 | 4 | import ( 5 | graphql "github.com/graphql-go/graphql" 6 | ) 7 | 8 | // Enums 9 | var ParentScopeEnum = graphql.NewEnum(graphql.EnumConfig{ 10 | Name: "ParentScopeEnum", 11 | Description: "", 12 | Values: graphql.EnumValueConfigMap{ 13 | "TestEnumVal0": &graphql.EnumValueConfig{ 14 | Value: 0, 15 | }, 16 | }, 17 | }) 18 | 19 | // Input object 20 | // Input objects resolvers 21 | // Output objects 22 | // Maps input objects 23 | // Maps input objects resolvers 24 | // Maps output objects 25 | -------------------------------------------------------------------------------- /tests/dataloader/apis/items.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package apis; 4 | 5 | option go_package = "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/apis;apis"; 6 | 7 | import "google/protobuf/empty.proto"; 8 | 9 | service ItemsService { 10 | rpc List (google.protobuf.Empty) returns (ItemListResponse) {} 11 | rpc GetOne (google.protobuf.Empty) returns (Item) {} 12 | } 13 | 14 | message ItemListResponse { 15 | repeated Item items = 1; 16 | } 17 | 18 | message Item { 19 | int64 id = 1; 20 | string name = 2; 21 | uint64 category_id = 3; 22 | repeated uint64 category_ids = 4; 23 | } 24 | -------------------------------------------------------------------------------- /tests/dataloader/Makefile: -------------------------------------------------------------------------------- 1 | generate_test_data: 2 | # Schema 3 | rm -rf generated/* 4 | mkdir -p generated/clients 5 | protoc --go_out=paths=source_relative,plugins=grpc:generated/clients apis/category.proto 6 | protoc --go_out=paths=source_relative,plugins=grpc:generated/clients apis/items.proto 7 | protoc --go_out=paths=source_relative,plugins=grpc:generated/clients apis/user.proto 8 | protoc --go_out=paths=source_relative,plugins=grpc:generated/clients apis/reviews.proto 9 | 10 | swagger generate client --template-dir=../swagger_templates/ -f apis/swagger.json -t generated/clients 11 | 12 | go run ../../cmd/go2gql/main.go ../../cmd/go2gql/basic_plugins.go 13 | 14 | # Mocks 15 | go generate ./... 16 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/templates/value_resolver_array.gohtml: -------------------------------------------------------------------------------- 1 | func (arg interface{}) ({{call $.resultType}}, error) { 2 | elements, ok := arg.([]interface{}) 3 | if !ok { 4 | return nil, {{errorsPkg}}.New("argument is not array") 5 | } 6 | res := make({{call $.resultType}}, len(elements)) 7 | for i, element := range elements{ 8 | _ = element 9 | {{ if $.elemResolverWithErr -}} 10 | elVal, err := {{call $.elemResolver "element" $.rootCtx}} 11 | if err != nil{ 12 | return nil, {{errorsPkg}}.Wrap(err, "can't resolve array element") 13 | } 14 | res[i] = elVal 15 | {{ else -}} 16 | res[i] += {{call $.elemResolver "element" $.rootCtx}} 17 | {{ end -}} 18 | } 19 | return res, nil 20 | }({{$.arg}}) -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: build_templates install 2 | 3 | install: 4 | @go install ./cmd/go2gql/ 5 | 6 | build: 7 | @go build -o ./bin/go2gql ./cmd/go2gql 8 | 9 | build_templates: 10 | go-bindata -prefix ./generator/plugins/graphql -o ./generator/plugins/graphql/templates.go -pkg graphql ./generator/plugins/graphql/templates 11 | go-bindata -prefix ./generator/plugins/dataloader -o ./generator/plugins/dataloader/templates.go -pkg dataloader ./generator/plugins/dataloader/templates 12 | go-bindata -prefix ./generator/plugins/swagger2gql -o ./generator/plugins/swagger2gql/templates.go -pkg swagger2gql ./generator/plugins/swagger2gql/templates 13 | 14 | test: 15 | $(MAKE) -C tests 16 | go test ./... 17 | 18 | lint: 19 | golangci-lint run -v 20 | 21 | .PHONY: install 22 | -------------------------------------------------------------------------------- /generator/plugins/graphql/lib/names/field_names.go: -------------------------------------------------------------------------------- 1 | package names 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var fieldFirstCharRegex = regexp.MustCompile("^[_A-Za-z]$") 8 | var fieldBodyCharRegex = regexp.MustCompile("^[_0-9A-Za-z]$") 9 | 10 | func FilterNotSupportedFieldNameCharacters(fieldName string) string { 11 | runes := []rune(fieldName) 12 | if len(runes) == 0 { 13 | return fieldName 14 | } 15 | for len(runes) > 0 { 16 | if fieldFirstCharRegex.MatchString(string(runes[0])) { 17 | break 18 | } 19 | runes = runes[1:] 20 | } 21 | for i := 1; i < len(runes); i++ { 22 | if fieldBodyCharRegex.MatchString(string(runes[i])) { 23 | continue 24 | } 25 | runes = append(runes[:i], runes[i+1:]...) 26 | i-- 27 | } 28 | return string(runes) 29 | } 30 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/type_name_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestTypeName(t *testing.T) { 10 | Convey("Test TypeName", t, func() { 11 | Convey("Should check TypeName.Equal", func() { 12 | So(TypeName{}.Equal(TypeName{}), ShouldBeTrue) 13 | So(TypeName{}.Equal(TypeName{"1"}), ShouldBeFalse) 14 | So(TypeName{"1"}.Equal(TypeName{"1"}), ShouldBeTrue) 15 | So(TypeName{"1"}.Equal(TypeName{"2"}), ShouldBeFalse) 16 | So(TypeName{"2", "33"}.Equal(TypeName{"2", "33"}), ShouldBeTrue) 17 | So(TypeName{"2", "33"}.Equal(TypeName{"2", "44"}), ShouldBeFalse) 18 | }) 19 | Convey("Should check TypeName.NewSubTypeName", func() { 20 | So(TypeName{}.NewSubTypeName("1"), ShouldResemble, TypeName{"1"}) 21 | So(TypeName{"1"}.NewSubTypeName("2"), ShouldResemble, TypeName{"1", "2"}) 22 | }) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /generator/plugins/graphql/config.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | type SchemaNodeConfig struct { 4 | Type string `mapstructure:"type"` // "OBJECT|SERVICE" 5 | Service string `mapstructure:"service"` 6 | ObjectName string `mapstructure:"object_name"` 7 | Field string `mapstructure:"field"` 8 | Fields []SchemaNodeConfig `mapstructure:"fields"` 9 | ExcludeMethods []string `mapstructure:"exclude_methods"` 10 | FilterMethods []string `mapstructure:"filter_methods"` 11 | } 12 | type SchemaConfig struct { 13 | Name string `mapstructure:"name"` 14 | OutputPath string `mapstructure:"output_path"` 15 | OutputPackage string `mapstructure:"output_package"` 16 | Queries *SchemaNodeConfig `mapstructure:"queries"` 17 | Mutations *SchemaNodeConfig `mapstructure:"mutations"` 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.11" 5 | - "1.12" 6 | 7 | install: 8 | - go get github.com/mattn/goveralls 9 | - go get github.com/golang/dep/cmd/dep 10 | - go get github.com/saturn4er/go-swagger/cmd/swagger 11 | - go get github.com/golang/mock/gomock 12 | - go install github.com/golang/mock/mockgen 13 | - go get -u -d github.com/golang/protobuf/protoc-gen-go && cd ${GOPATH}/src/github.com/golang/protobuf/protoc-gen-go && git checkout v1.2.0 && go install && cd ${TRAVIS_BUILD_DIR} 14 | - dep ensure -vendor-only 15 | - curl -L https://github.com/google/protobuf/releases/download/v3.6.1/protoc-3.6.1-linux-x86_64.zip -o /tmp/protoc.zip 16 | - unzip /tmp/protoc.zip -d "$HOME"/protoc 17 | - mkdir -p "$HOME"/src && ln -s "$HOME"/protoc "$HOME"/src/protobuf 18 | - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.13.1 19 | 20 | env: 21 | - PATH=$HOME/protoc/bin:$PATH 22 | 23 | script: 24 | - make test 25 | - make lint 26 | #- goveralls -race -service=travis-ci -repotoken $COVERALLS_TOKEN 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mark Martin, Greg Poirier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/dataloader/mock/loader.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/apis" 5 | "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/client/comments_controller" 6 | ) 7 | 8 | type LoaderClients struct { 9 | clients *Clients 10 | } 11 | 12 | type Clients struct { 13 | ItemsClient apis.ItemsServiceClient 14 | CategoryClient apis.CategoryServiceClient 15 | CommentsClient comments_controller.IClient 16 | UserClient apis.UserServiceClient 17 | ReviewsClient apis.ItemsReviewServiceClient 18 | } 19 | 20 | func NewLoaderClients(clients *Clients) *LoaderClients { 21 | return &LoaderClients{ 22 | clients: clients, 23 | } 24 | } 25 | 26 | func (l *LoaderClients) GetCategoryServiceClient() apis.CategoryServiceClient { 27 | return l.clients.CategoryClient 28 | } 29 | 30 | func (l *LoaderClients) GetCommentsServiceClient() comments_controller.IClient { 31 | return l.clients.CommentsClient 32 | } 33 | 34 | func (l *LoaderClients) GetUserServiceClient() apis.UserServiceClient { 35 | return l.clients.UserClient 36 | } 37 | 38 | func (l *LoaderClients) GetItemsReviewServiceClient() apis.ItemsReviewServiceClient { 39 | return l.clients.ReviewsClient 40 | } 41 | -------------------------------------------------------------------------------- /tests/utils.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/graphql-go/graphql" 10 | "github.com/graphql-go/handler" 11 | ) 12 | 13 | func MakeRequest(ctx context.Context, schema graphql.Schema, opts *handler.RequestOptions) *graphql.Result { 14 | return graphql.Do(graphql.Params{ 15 | Schema: schema, 16 | RequestString: opts.Query, 17 | VariableValues: opts.Variables, 18 | OperationName: opts.OperationName, 19 | Context: ctx, 20 | }) 21 | } 22 | 23 | // From https://github.com/graphql-go/graphql/blob/v0.7.7/executor_test.go#L2016 24 | func AssertJSON(t *testing.T, expected string, actual interface{}) { 25 | var e interface{} 26 | if err := json.Unmarshal([]byte(expected), &e); err != nil { 27 | t.Fatalf(err.Error()) 28 | } 29 | aJSON, err := json.MarshalIndent(actual, "", " ") 30 | if err != nil { 31 | t.Fatalf(err.Error()) 32 | } 33 | var a interface{} 34 | if err := json.Unmarshal(aJSON, &a); err != nil { 35 | t.Fatalf(err.Error()) 36 | } 37 | if !reflect.DeepEqual(e, a) { 38 | eNormalizedJSON, err := json.MarshalIndent(e, "", " ") 39 | if err != nil { 40 | t.Fatalf(err.Error()) 41 | } 42 | t.Fatalf("Expected JSON:\n\n%v\n\nActual JSON:\n\n%v", string(eNormalizedJSON), string(aJSON)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | 22 | 23 | [[constraint]] 24 | name = "github.com/emicklei/proto" 25 | version = "1.6.5" 26 | 27 | [[constraint]] 28 | name = "github.com/golang/protobuf" 29 | version = "1.0.0" 30 | 31 | [[constraint]] 32 | name = "github.com/graphql-go/graphql" 33 | version = "0.7.7" 34 | 35 | [[constraint]] 36 | name = "github.com/pkg/errors" 37 | version = "0.8.0" 38 | 39 | [[constraint]] 40 | name = "github.com/smartystreets/goconvey" 41 | version = "1.6.3" 42 | 43 | [[constraint]] 44 | branch = "master" 45 | name = "golang.org/x/net" 46 | 47 | [[constraint]] 48 | branch = "master" 49 | name = "golang.org/x/tools" 50 | 51 | [[constraint]] 52 | name = "google.golang.org/grpc" 53 | version = "1.11.3" 54 | 55 | [[constraint]] 56 | name = "gopkg.in/yaml.v2" 57 | version = "2.2.1" 58 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/helper.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "github.com/emicklei/proto" 8 | ) 9 | 10 | func typeIsScalar(typ string) bool { 11 | switch typ { 12 | case "double", "float", "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64", "bool", "string", "bytes": 13 | return true 14 | } 15 | 16 | return false 17 | } 18 | 19 | func quoteComment(comment *proto.Comment, inlineComment *proto.Comment) string { 20 | var lines []string 21 | 22 | if comment != nil { 23 | lines = append(lines, comment.Lines...) 24 | } 25 | 26 | if inlineComment != nil { 27 | lines = append(lines, inlineComment.Lines...) 28 | } 29 | 30 | if len(lines) == 0 { 31 | return `""` 32 | } 33 | 34 | return strconv.Quote(strings.TrimSpace(strings.Join(lines, "\n"))) 35 | } 36 | 37 | func resolveFilePkgName(file *proto.Proto) string { 38 | for _, el := range file.Elements { 39 | if p, ok := el.(*proto.Package); ok { 40 | return p.Name 41 | } 42 | } 43 | 44 | return "" 45 | } 46 | 47 | func message(file *File, msg *proto.Message, typeName []string, parent *Message) *Message { 48 | m := &Message{ 49 | Name: msg.Name, 50 | QuotedComment: quoteComment(msg.Comment, nil), 51 | Descriptor: msg, 52 | TypeName: typeName, 53 | file: file, 54 | parentMsg: parent, 55 | } 56 | 57 | return m 58 | } 59 | -------------------------------------------------------------------------------- /generator/plugins/dataloader/dataloader.go: -------------------------------------------------------------------------------- 1 | package dataloader 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql/lib/importer" 10 | ) 11 | 12 | type DataLoader struct { 13 | OutputPath string 14 | Pkg string 15 | Loaders map[string]LoaderModel 16 | } 17 | 18 | type Service struct { 19 | Name string 20 | CallInterface graphql.GoType 21 | } 22 | 23 | type LoaderModel struct { 24 | Name string 25 | WaitDuration time.Duration 26 | Service *Service 27 | Method *graphql.Method 28 | InputGoType graphql.GoType 29 | OutputGoType graphql.GoType 30 | OutputGraphqlType graphql.TypeResolver 31 | OutputGraphqlTypeName string 32 | FetchCode func(importer *importer.Importer) string 33 | Slice bool 34 | } 35 | 36 | func (p *Plugin) createDataLoader(config *DataLoadersConfig, vendorPath string) (*DataLoader, error) { 37 | if config == nil { 38 | return nil, nil 39 | } 40 | 41 | goPkg, err := graphql.GoPackageByPath(config.OutputPath, vendorPath) 42 | 43 | if err != nil { 44 | return nil, errors.New("failed to get go package by path " + goPkg) 45 | } 46 | 47 | return &DataLoader{ 48 | OutputPath: config.OutputPath, 49 | Pkg: goPkg, 50 | Loaders: p.loaders, 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/enum.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/emicklei/proto" 7 | ) 8 | 9 | type Enum struct { 10 | Name string 11 | QuotedComment string 12 | Values []*EnumValue 13 | file *File 14 | TypeName TypeName 15 | Descriptor *proto.Enum 16 | } 17 | 18 | type EnumValue struct { 19 | Name string 20 | Value int 21 | QuotedComment string 22 | } 23 | 24 | func newEnum(file *File, enum *proto.Enum, typeName []string) *Enum { 25 | m := &Enum{ 26 | Name: enum.Name, 27 | QuotedComment: quoteComment(enum.Comment, nil), 28 | Descriptor: enum, 29 | TypeName: typeName, 30 | file: file, 31 | } 32 | for _, v := range enum.Elements { 33 | value, ok := v.(*proto.EnumField) 34 | if !ok { 35 | continue 36 | } 37 | m.Values = append(m.Values, &EnumValue{ 38 | Name: value.Name, 39 | Value: value.Integer, 40 | QuotedComment: quoteComment(value.Comment, value.InlineComment), 41 | }) 42 | } 43 | 44 | return m 45 | } 46 | 47 | func (e Enum) Kind() TypeKind { 48 | return TypeEnum 49 | } 50 | 51 | func (e Enum) String() string { 52 | return e.Name + " enum" 53 | } 54 | 55 | func (e Enum) File() *File { 56 | return e.file 57 | } 58 | 59 | func (e Enum) GetFullName() string { 60 | if e.file.PkgName == "" { 61 | return strings.Join(e.TypeName, ".") 62 | } 63 | 64 | return e.file.PkgName + "." + strings.Join(e.TypeName, ".") 65 | } 66 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/enums.go: -------------------------------------------------------------------------------- 1 | package proto2gql 2 | 3 | import ( 4 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 5 | "github.com/EGT-Ukraine/go2gql/generator/plugins/proto2gql/parser" 6 | ) 7 | 8 | func (g *Proto2GraphQL) enumTypeResolver(enumFile *parsedFile, enum *parser.Enum) (graphql.TypeResolver, error) { 9 | return func(ctx graphql.BodyContext) string { 10 | return ctx.Importer.Prefix(enumFile.OutputPkg) + g.enumVariable(enumFile, enum) 11 | }, nil 12 | } 13 | 14 | func (g *Proto2GraphQL) enumGraphQLName(enumFile *parsedFile, enum *parser.Enum) string { 15 | return enumFile.Config.GetGQLEnumsPrefix() + camelCaseSlice(enum.TypeName) 16 | } 17 | 18 | func (g *Proto2GraphQL) enumVariable(enumFile *parsedFile, enum *parser.Enum) string { 19 | return enumFile.Config.GetGQLEnumsPrefix() + camelCaseSlice(enum.TypeName) 20 | } 21 | 22 | func (g *Proto2GraphQL) prepareFileEnums(file *parsedFile) []graphql.Enum { 23 | var res []graphql.Enum 24 | for _, enum := range file.File.Enums { 25 | vals := make([]graphql.EnumValue, len(enum.Values)) 26 | for i, value := range enum.Values { 27 | vals[i] = graphql.EnumValue{ 28 | Name: value.Name, 29 | Value: value.Value, 30 | Comment: value.QuotedComment, 31 | } 32 | } 33 | res = append(res, graphql.Enum{ 34 | VariableName: g.enumVariable(file, enum), 35 | GraphQLName: g.enumGraphQLName(file, enum), 36 | Comment: enum.QuotedComment, 37 | Values: vals, 38 | }) 39 | } 40 | 41 | return res 42 | } 43 | -------------------------------------------------------------------------------- /generator/plugins/dataloader/templates/output_object_fields.gohtml: -------------------------------------------------------------------------------- 1 | {{- /*gotype: github.com/EGT-Ukraine/go2gql/generator/plugins/graphql.RenderFieldsContext*/ -}} 2 | {{ range $field := $.OutputObject.DataLoaderFields -}} 3 | {{$.OutputObject.VariableName}}.AddFieldConfig("{{$field.Name}}", &{{gqlPkg}}.Field{ 4 | Name: "{{$field.Name}}", 5 | Description: "", 6 | Type: {{graphqlOutputLoaderTypeName $.ObjectContext $field}}, 7 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 8 | parent := p.Source.(*{{goType $.OutputObject.GoType}}) 9 | 10 | loaders := {{loadersPkg}}.GetDataLoadersFromContext(p.Context) 11 | 12 | if loaders == nil { 13 | return nil, errors.New("Data loaders not found in context. Call loaders.GetContextWithLoaders") 14 | } 15 | 16 | {{if $field.KeyFieldSlice}} 17 | thunk := loaders.{{$field.DataLoaderName}}Loader.LoadAllThunk(parent.{{$field.NormalizedParentKeyFieldName}}) 18 | 19 | return func() (interface{}, error) { 20 | var loaderErrors error 21 | 22 | result, errs := thunk() 23 | 24 | for _, err := range errs { 25 | if err != nil { 26 | loaderErrors = {{multierrorPkg}}.Append(loaderErrors, err) 27 | } 28 | } 29 | 30 | return result, loaderErrors 31 | }, nil 32 | {{else}} 33 | thunk := loaders.{{$field.DataLoaderName}}Loader.LoadThunk(parent.{{$field.NormalizedParentKeyFieldName}}) 34 | 35 | return func() (interface{}, error) { 36 | return thunk() 37 | }, nil 38 | {{end}} 39 | }, 40 | }) 41 | {{ end -}} -------------------------------------------------------------------------------- /generator/plugins/graphql/templates/output_fields.gohtml: -------------------------------------------------------------------------------- 1 | {{- /*gotype: github.com/EGT-Ukraine/go2gql/generator/plugins/graphql.RenderFieldsContext*/ -}} 2 | {{ range $field := $.OutputObject.Fields -}} 3 | {{$.OutputObject.VariableName}}.AddFieldConfig("{{$field.Name}}", &{{gqlPkg}}.Field{ 4 | Name: "{{$field.Name}}", 5 | Description: {{$field.QuotedComment}}, 6 | Type: {{call $field.Type $.ObjectContext}}, 7 | Resolve: func(p {{gqlPkg}}.ResolveParams) (interface{}, error) { 8 | switch src := p.Source.(type){ 9 | case *{{goType $.OutputObject.GoType}}: 10 | if src == nil { 11 | return nil, nil 12 | } 13 | s := *src 14 | {{if $field.NeedCast -}} 15 | return {{$field.CastTo}}({{call $field.Value "s" $.ObjectContext}}), nil 16 | {{else -}} 17 | return {{call $field.Value "s" $.ObjectContext}}, nil 18 | {{ end -}} 19 | case {{goType $.OutputObject.GoType}}: 20 | {{if $field.NeedCast -}} 21 | return {{$field.CastTo}}({{call $field.Value "src" $}}), nil 22 | {{else -}} 23 | return {{call $field.Value "src" $.ObjectContext}}, nil 24 | {{end -}} 25 | } 26 | return nil, {{errorsPkg}}.New("source of unknown type") 27 | }, 28 | }) 29 | {{ end -}} -------------------------------------------------------------------------------- /generator/plugins/dataloader/templates/loaders_body.gohtml: -------------------------------------------------------------------------------- 1 | {{- /*gotype: github.com/EGT-Ukraine/go2gql/generator/plugins/dataloader.LoadersBodyContext*/ -}} 2 | 3 | type LoaderClients interface { 4 | {{range $service := $.Services -}} 5 | Get{{$service.Name}}Client() {{goType $service.CallInterface}} 6 | {{end -}} 7 | } 8 | 9 | type DataLoaders struct { 10 | {{range $loader := $.Loaders -}} 11 | {{$loader.Name}}Loader {{$loader.LoaderTypeName}} 12 | {{end -}} 13 | } 14 | 15 | type dataLoadersContextKeyType struct{} 16 | 17 | var dataLoadersContextKey = dataLoadersContextKeyType{} 18 | 19 | func GetContextWithLoaders(ctx context.Context, apiClients LoaderClients) context.Context { 20 | dataLoaders := &DataLoaders{ 21 | {{range $loader := $.Loaders -}} 22 | {{$loader.Name}}Loader: create{{$loader.Name}}(ctx, apiClients.Get{{$loader.Service.Name}}Client()), 23 | {{end -}} 24 | } 25 | 26 | return context.WithValue(ctx, dataLoadersContextKey, dataLoaders) 27 | } 28 | 29 | {{range $loader := $.Loaders -}} 30 | func create{{$loader.Name}}(ctx context.Context, client {{goType $loader.Service.CallInterface}}) {{$loader.LoaderTypeName}} { 31 | return {{$loader.LoaderTypeName}}{ 32 | fetch: func(keys {{goType $loader.RequestGoType}}) ([]{{goType $loader.ResponseGoType}}, []error) { 33 | {{$loader.FetchCode}} 34 | }, 35 | wait: {{duration $loader.WaitDuration}}, 36 | } 37 | } 38 | {{end -}} 39 | 40 | func GetDataLoadersFromContext(ctx context.Context) *DataLoaders { 41 | val := ctx.Value(dataLoadersContextKey) 42 | 43 | if val == nil { 44 | return nil 45 | } 46 | 47 | return val.(*DataLoaders) 48 | } 49 | -------------------------------------------------------------------------------- /testdata/generate.yml: -------------------------------------------------------------------------------- 1 | generate_tracer: true 2 | 3 | proto2gql: 4 | output_path: "./out/test" 5 | paths: 6 | - "$GOPATH/src" 7 | - "./" 8 | imports_aliases: 9 | - google/protobuf/timestamp.proto: "github.com/golang/protobuf/ptypes/timestamp/timestamp.proto" 10 | files: 11 | - name: "Example" 12 | proto_path: "./test.proto" 13 | output_path: "./out/test" 14 | output_package: "test" 15 | gql_messages_prefix: "Exmpl" 16 | gql_enums_prefix: "Exmpl" 17 | paths: 18 | - "$GOPATH/src/lib/a" 19 | messages: 20 | - "^RootMessage$": 21 | error_field: "ctx_map" 22 | fields: 23 | scalar_from_context: {context_key: "ctx_key"} 24 | ctx_map: {context_key: "ctx_map"} 25 | ctx_map_enum: {context_key: "ctx_map_enum"} 26 | 27 | graphql_schemas: 28 | - name: "SomeSchema" 29 | output_path: "./out/schema.go" 30 | output_package: "test_schema" 31 | queries: 32 | type: "SERVICE" 33 | proto: "Example" 34 | service: "ServiceExample" 35 | filter_fields: 36 | - "MsgsWithEpmty" 37 | 38 | mutations: 39 | type: "OBJECT" 40 | fields: 41 | - field: "nested_example_mutation" 42 | type: "OBJECT" 43 | object_name: "NestedExampleMutation" 44 | fields: 45 | - field: "ExampleService" 46 | type: "SERVICE" 47 | object_name: "ServiceExampleMutations" 48 | proto: "Example" 49 | service: "ServiceExample" 50 | filter_fields: 51 | - "MsgsWithEpmty" 52 | 53 | -------------------------------------------------------------------------------- /generator/config.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/pkg/errors" 9 | yaml "gopkg.in/yaml.v2" 10 | ) 11 | 12 | type ImportedPluginsConfigs struct { 13 | Path string 14 | PluginsConfigs PluginsConfigs 15 | } 16 | 17 | type PluginsConfigs map[string]interface{} 18 | type GenerateConfig struct { 19 | GenerateTraces bool `yaml:"generate_tracer"` 20 | VendorPath string `yaml:"vendor_path"` 21 | Imports []string `yaml:"imports"` 22 | PluginsConfigsImports []ImportedPluginsConfigs 23 | PluginsConfigs `yaml:",inline"` 24 | } 25 | 26 | func (gc *GenerateConfig) ParseImports() error { 27 | for _, importPath := range gc.Imports { 28 | normalizedPath := os.ExpandEnv(importPath) 29 | normalizedPath, err := filepath.Abs(normalizedPath) 30 | if err != nil { 31 | return errors.Wrapf(err, "failed to make normalized path '%s' absolute", normalizedPath) 32 | } 33 | 34 | cfg, err := ioutil.ReadFile(normalizedPath) 35 | if err != nil { 36 | return errors.Wrapf(err, "Failed to read import '%s' file", normalizedPath) 37 | } 38 | 39 | pluginsConfig := PluginsConfigs{} 40 | 41 | importedPluginsConfig := ImportedPluginsConfigs{ 42 | Path: normalizedPath, 43 | PluginsConfigs: pluginsConfig, 44 | } 45 | 46 | err = yaml.Unmarshal(cfg, pluginsConfig) 47 | if err != nil { 48 | return errors.Wrapf(err, "Failed to unmarshal import '%s' file", normalizedPath) 49 | } 50 | 51 | gc.PluginsConfigsImports = append(gc.PluginsConfigsImports, importedPluginsConfig) 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /generator/plugins/graphql/templates/output_map_fields.gohtml: -------------------------------------------------------------------------------- 1 | {{- /*gotype: github.com/EGT-Ukraine/go2gql/generator/plugins/graphql.RenderFieldsContext*/ -}} 2 | {{ range $field := $.OutputObject.MapFields -}} 3 | {{$.OutputObject.VariableName}}.AddFieldConfig("{{$field.Name}}", &{{gqlPkg}}.Field{ 4 | Name: "{{$field.Name}}", 5 | Type: {{call $field.Type $.ObjectContext}}, 6 | Resolve: func(p {{gqlPkg}}.ResolveParams) (interface{}, error) { 7 | switch src := p.Source.(type){ 8 | case *{{goType $.OutputObject.GoType}}: 9 | if src == nil { 10 | return nil, nil 11 | } 12 | s := *src 13 | var res []map[string]interface{} 14 | for key, value := range {{call $field.Value "s" $.ObjectContext}} { 15 | res = append(res, map[string]interface{}{ 16 | "key": key, 17 | "value": value, 18 | }) 19 | } 20 | return res, nil 21 | case {{goType $.OutputObject.GoType}}: 22 | var res []map[string]interface{} 23 | for key, value := range {{call $field.Value "src" $.ObjectContext}} { 24 | res = append(res, map[string]interface{}{ 25 | "key": key, 26 | "value": value, 27 | }) 28 | } 29 | return res, nil 30 | } 31 | return nil, {{errorsPkg}}.New("source of unknown type") 32 | }, 33 | }) 34 | {{ end -}} -------------------------------------------------------------------------------- /cmd/go2gql/plugins_with_external.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin 2 | 3 | package main 4 | 5 | import ( 6 | "path/filepath" 7 | "plugin" 8 | 9 | "github.com/pkg/errors" 10 | "github.com/urfave/cli" 11 | 12 | "github.com/EGT-Ukraine/go2gql/generator" 13 | "github.com/EGT-Ukraine/go2gql/generator/plugins/dataloader" 14 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 15 | "github.com/EGT-Ukraine/go2gql/generator/plugins/proto2gql" 16 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql" 17 | ) 18 | 19 | func Plugins(c *cli.Context) []generator.Plugin { 20 | res := []generator.Plugin{ 21 | new(dataloader.Plugin), 22 | new(graphql.Plugin), 23 | new(swagger2gql.Plugin), 24 | new(proto2gql.Plugin), 25 | } 26 | pluginsDir := c.String("plugins") 27 | if len(pluginsDir) > 0 { 28 | plugins, err := filepath.Glob(filepath.Join(pluginsDir, "*.so")) 29 | if err != nil { 30 | panic(errors.Wrap(err, "failed to scan plugins directory")) 31 | } 32 | for _, pluginPath := range plugins { 33 | p, err := plugin.Open(pluginPath) 34 | if err != nil { 35 | panic(errors.Wrapf(err, "failed to open plugin %s", pluginPath)) 36 | } 37 | s, err := p.Lookup("Plugin") 38 | if err != nil { 39 | panic(errors.Wrapf(err, "no `Plugin` symbol in plugin %s", pluginPath)) 40 | } 41 | pf, ok := s.(generator.PluginFabric) 42 | if !ok { 43 | panic(errors.Errorf("symbol `Plugin` does not implements `func() Plugin` interface in plugin %s", pluginPath)) 44 | } 45 | res = append(res, pf()) 46 | } 47 | } 48 | 49 | return res 50 | } 51 | 52 | func init() { //nolint:gochecknoinits 53 | appFlags = append(appFlags, cli.StringFlag{ 54 | Name: "plugins, p", 55 | Usage: "Plugins directory", 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /generator/plugins/graphql/lib/importer/importer_test.go: -------------------------------------------------------------------------------- 1 | package importer 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestImporter_New(t *testing.T) { 10 | Convey("Test Importer", t, func() { 11 | imp := Importer{} 12 | Convey("Should return same Alias for same Path", func() { 13 | Convey("With slash ended", func() { 14 | So(imp.New("a/b/c"), ShouldEqual, imp.New("a/b/c/")) 15 | So(imp.Imports(), ShouldResemble, []Import{{Path: "a/b/c", Alias: "c"}}) 16 | }) 17 | Convey("Without slash ended", func() { 18 | So(imp.New("a/b/c"), ShouldEqual, imp.New("a/b/c")) 19 | So(imp.Imports(), ShouldResemble, []Import{{Path: "a/b/c", Alias: "c"}}) 20 | }) 21 | Convey("With number ended", func() { 22 | So(imp.New("a/b/1"), ShouldEqual, imp.New("a/b/1")) 23 | So(imp.Imports(), ShouldResemble, []Import{{Path: "a/b/1", Alias: "imp1"}}) 24 | }) 25 | }) 26 | Convey("Should return another Alias for same package", func() { 27 | Convey("With slash ended", func() { 28 | So(imp.New("a/b/c")+"_1", ShouldEqual, imp.New("a/bb/c/")) 29 | So(imp.Imports(), ShouldResemble, []Import{{Path: "a/b/c", Alias: "c"}, {Path: "a/bb/c", Alias: "c_1"}}) 30 | }) 31 | Convey("Without slash ended", func() { 32 | So(imp.New("a/b/c")+"_1", ShouldEqual, imp.New("a/bb/c/")) 33 | So(imp.Imports(), ShouldResemble, []Import{{Path: "a/b/c", Alias: "c"}, {Path: "a/bb/c", Alias: "c_1"}}) 34 | }) 35 | Convey("With number ended", func() { 36 | So(imp.New("a/b/1")+"_1", ShouldEqual, imp.New("a/bb/1/")) 37 | So(imp.Imports(), ShouldResemble, []Import{{Path: "a/b/1", Alias: "imp1"}, {Path: "a/bb/1", Alias: "imp1_1"}}) 38 | }) 39 | }) 40 | }) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/maps_inputs.go: -------------------------------------------------------------------------------- 1 | package proto2gql 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 7 | "github.com/EGT-Ukraine/go2gql/generator/plugins/proto2gql/parser" 8 | ) 9 | 10 | func (g *Proto2GraphQL) inputMapGraphQLName(mapFile *parsedFile, res *parser.Map) string { 11 | return g.inputMessageVariable(mapFile, res.Message) + "__" + camelCase(res.Field.Name) 12 | } 13 | 14 | func (g *Proto2GraphQL) inputMapVariable(mapFile *parsedFile, res *parser.Map) string { 15 | return g.inputMessageVariable(mapFile, res.Message) + "__" + camelCase(res.Field.Name) 16 | } 17 | 18 | func (g *Proto2GraphQL) fileMapInputObjects(file *parsedFile) ([]graphql.MapInputObject, error) { 19 | var res []graphql.MapInputObject 20 | for _, msg := range file.File.Messages { 21 | for _, mapFld := range msg.MapFields { 22 | keyTypResolver, err := g.TypeInputGraphQLTypeResolver(file, mapFld.Map.KeyType) 23 | if err != nil { 24 | return nil, errors.Wrap(err, "failed to resolve key input type resolver") 25 | } 26 | valueFile, err := g.parsedFile(mapFld.Map.ValueType.File()) 27 | if err != nil { 28 | return nil, errors.Wrap(err, "failed to resolve value type file") 29 | } 30 | valueTypResolver, err := g.TypeInputGraphQLTypeResolver(valueFile, mapFld.Map.ValueType) 31 | if err != nil { 32 | return nil, errors.Wrap(err, "failed to resolve value input type resolver") 33 | } 34 | 35 | res = append(res, graphql.MapInputObject{ 36 | VariableName: g.inputMapVariable(file, mapFld.Map), 37 | GraphQLName: g.inputMapGraphQLName(file, mapFld.Map), 38 | KeyObjectType: keyTypResolver, 39 | ValueObjectType: valueTypResolver, 40 | }) 41 | } 42 | 43 | } 44 | return res, nil 45 | } 46 | -------------------------------------------------------------------------------- /example/simple_plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/EGT-Ukraine/go2gql/generator" 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 10 | ) 11 | 12 | const Name = "gql_services_rules" 13 | 14 | type plugin struct { 15 | gqlPlugin *graphql.Plugin 16 | } 17 | 18 | func (p *plugin) Init(_ *generator.GenerateConfig, plugins []generator.Plugin) error { 19 | for _, plugin := range plugins { 20 | if g, ok := plugin.(*graphql.Plugin); ok { 21 | p.gqlPlugin = g 22 | return nil 23 | } 24 | } 25 | return errors.New("graphql plugin was not found") 26 | } 27 | 28 | func (p plugin) Generate() error { 29 | file, err := os.OpenFile("./services_access.yml", os.O_TRUNC|os.O_WRONLY, 0666) 30 | if err != nil { 31 | return errors.Wrap(err, "failed to open services_access.yml") 32 | } 33 | defer file.Close() 34 | for _, typesFile := range p.gqlPlugin.Types() { 35 | for _, service := range typesFile.Services { 36 | if len(service.QueryMethods) == 0 { 37 | continue 38 | } 39 | _, err := file.WriteString(service.Name + ":\n") 40 | 41 | if err != nil { 42 | return errors.Wrap(err, "failed to write to file") 43 | } 44 | 45 | for _, method := range service.QueryMethods { 46 | _, err := file.WriteString(" " + method.Name + ":\n") 47 | 48 | if err != nil { 49 | return errors.Wrap(err, "failed to write to file") 50 | } 51 | } 52 | } 53 | } 54 | return nil 55 | } 56 | func (plugin) Name() string { return Name } 57 | func (plugin) Prepare() error { return nil } 58 | func (plugin) PrintInfo(info generator.Infos) {} 59 | func (plugin) Infos() map[string]string { return nil } 60 | 61 | func Plugin() generator.Plugin { //nolint:deadcode 62 | return new(plugin) 63 | } 64 | 65 | func main() {} 66 | -------------------------------------------------------------------------------- /generator/plugins/graphql/field_renderers.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "bytes" 5 | "text/template" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | type fieldsRenderer struct { 11 | templateFuncs map[string]interface{} 12 | } 13 | 14 | type mapFieldsRenderer struct { 15 | templateFuncs map[string]interface{} 16 | } 17 | 18 | type RenderFieldsContext struct { 19 | OutputObject OutputObject 20 | ObjectContext BodyContext 21 | } 22 | 23 | func (r *fieldsRenderer) RenderFields(o OutputObject, ctx BodyContext) (string, error) { 24 | buf := new(bytes.Buffer) 25 | tmpl, err := templatesOutput_fieldsGohtmlBytes() 26 | if err != nil { 27 | return "", errors.Wrap(err, "failed to get output fields template") 28 | } 29 | bodyTpl, err := template.New("fieldsRenderer").Funcs(r.templateFuncs).Parse(string(tmpl)) 30 | if err != nil { 31 | return "", errors.Wrap(err, "failed to parse template") 32 | } 33 | err = bodyTpl.Execute(buf, RenderFieldsContext{ 34 | OutputObject: o, 35 | ObjectContext: ctx, 36 | }) 37 | if err != nil { 38 | return "", errors.Wrap(err, "failed to execute template") 39 | } 40 | 41 | return buf.String(), nil 42 | } 43 | 44 | func (r *mapFieldsRenderer) RenderFields(o OutputObject, ctx BodyContext) (string, error) { 45 | buf := new(bytes.Buffer) 46 | tmpl, err := templatesOutput_map_fieldsGohtmlBytes() 47 | if err != nil { 48 | return "", errors.Wrap(err, "failed to get output fields template") 49 | } 50 | bodyTpl, err := template.New("mapFieldsRenderer").Funcs(r.templateFuncs).Parse(string(tmpl)) 51 | if err != nil { 52 | return "", errors.Wrap(err, "failed to parse template") 53 | } 54 | err = bodyTpl.Execute(buf, RenderFieldsContext{ 55 | OutputObject: o, 56 | ObjectContext: ctx, 57 | }) 58 | if err != nil { 59 | return "", errors.Wrap(err, "failed to execute template") 60 | } 61 | 62 | return buf.String(), nil 63 | } 64 | -------------------------------------------------------------------------------- /generator/plugins/dataloader/field_renderers.go: -------------------------------------------------------------------------------- 1 | package dataloader 2 | 3 | import ( 4 | "bytes" 5 | "text/template" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 10 | ) 11 | 12 | type fieldsRenderer struct { 13 | dataLoader *DataLoader 14 | } 15 | 16 | func (r *fieldsRenderer) RenderFields(o graphql.OutputObject, ctx graphql.BodyContext) (string, error) { 17 | templateFuncs := map[string]interface{}{ 18 | "goType": func(typ graphql.GoType) string { 19 | return typ.String(ctx.Importer) 20 | }, 21 | "gqlPkg": func() string { 22 | return ctx.Importer.New(graphql.GraphqlPkgPath) 23 | }, 24 | "multierrorPkg": func() string { 25 | return ctx.Importer.New("github.com/hashicorp/go-multierror") 26 | }, 27 | "loadersPkg": func() string { 28 | return ctx.Importer.New(r.dataLoader.Pkg) 29 | }, 30 | "graphqlOutputLoaderTypeName": func(ctx graphql.BodyContext, dataLoaderFieldConfig graphql.DataLoaderField) string { 31 | dataLoaderConfig := r.dataLoader.Loaders[dataLoaderFieldConfig.DataLoaderName] 32 | 33 | resolver := dataLoaderConfig.OutputGraphqlType 34 | 35 | if dataLoaderFieldConfig.KeyFieldSlice { 36 | resolver = graphql.GqlListTypeResolver(resolver) 37 | } 38 | 39 | return resolver(ctx) 40 | }, 41 | } 42 | 43 | buf := new(bytes.Buffer) 44 | tmpl, err := templatesOutput_object_fieldsGohtmlBytes() 45 | if err != nil { 46 | return "", errors.Wrap(err, "failed to get output fields template") 47 | } 48 | bodyTpl, err := template.New("head").Funcs(templateFuncs).Parse(string(tmpl)) 49 | if err != nil { 50 | return "", errors.Wrap(err, "failed to parse template") 51 | } 52 | err = bodyTpl.Execute(buf, graphql.RenderFieldsContext{ 53 | OutputObject: o, 54 | ObjectContext: ctx, 55 | }) 56 | if err != nil { 57 | return "", errors.Wrap(err, "failed to execute template") 58 | } 59 | 60 | return buf.String(), nil 61 | } 62 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/generator.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql/parser" 10 | ) 11 | 12 | type parsedFile struct { 13 | File *parser.File 14 | Config *SwaggerFileConfig 15 | OutputPath string 16 | OutputPkg string 17 | OutputPkgName string 18 | } 19 | 20 | func (p *Plugin) fileOutputPath(cfg *SwaggerFileConfig) (string, error) { 21 | if cfg.GetOutputPath() == "" { 22 | if p.config.GetOutputPath() == "" { 23 | return "", errors.Errorf("need to specify global output_path") 24 | } 25 | absFilePath, err := filepath.Abs(cfg.Path) 26 | if err != nil { 27 | return "", errors.Wrap(err, "failed to resolve file absolute path") 28 | } 29 | fileName := filepath.Base(cfg.Path) 30 | pkg, err := GoPackageByPath(filepath.Dir(absFilePath), p.generateConfig.VendorPath) 31 | var res string 32 | if err != nil { 33 | res, err = filepath.Abs(filepath.Join("./"+p.config.GetOutputPath()+"/", "./"+filepath.Dir(absFilePath), strings.TrimSuffix(fileName, ".json")+".go")) 34 | } else { 35 | res, err = filepath.Abs(filepath.Join("./"+p.config.GetOutputPath()+"/", "./"+pkg, strings.TrimSuffix(fileName, ".json")+".go")) 36 | } 37 | if err != nil { 38 | return "", errors.Wrap(err, "failed to resolve absolute output path") 39 | } 40 | return res, nil 41 | } 42 | return filepath.Join(cfg.OutputPath, strings.TrimSuffix(filepath.Base(cfg.Path), ".json")+".go"), nil 43 | } 44 | 45 | func (p *Plugin) fileOutputPackage(cfg *SwaggerFileConfig) (name, pkg string, err error) { 46 | outPath, err := p.fileOutputPath(cfg) 47 | if err != nil { 48 | return "", "", errors.Wrap(err, "failed to resolve file output path") 49 | } 50 | pkg, err = GoPackageByPath(filepath.Dir(outPath), p.generateConfig.VendorPath) 51 | if err != nil { 52 | return "", "", errors.Wrap(err, "failed to resolve file go package") 53 | } 54 | return strings.Replace(filepath.Base(pkg), "-", "_", -1), pkg, nil 55 | } 56 | -------------------------------------------------------------------------------- /generator/plugins/graphql/helpers.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "go/build" 5 | "path/filepath" 6 | "reflect" 7 | "strings" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | func typeIsScalar(p GoType) bool { 13 | switch p.Kind { 14 | case reflect.Bool, 15 | reflect.Int, 16 | reflect.Int8, 17 | reflect.Int16, 18 | reflect.Int32, 19 | reflect.Int64, 20 | reflect.Uint, 21 | reflect.Uint8, 22 | reflect.Uint16, 23 | reflect.Uint32, 24 | reflect.Uint64, 25 | reflect.Uintptr, 26 | reflect.Float32, 27 | reflect.Float64, 28 | reflect.Complex64, 29 | reflect.Complex128, 30 | reflect.String: 31 | return true 32 | } 33 | 34 | return false 35 | } 36 | 37 | func ResolverCall(resolverPkg, resolverFuncName string) ValueResolver { 38 | return func(arg string, ctx BodyContext) string { 39 | return ctx.Importer.Prefix(resolverPkg) + resolverFuncName + "(ctx, " + arg + ")" 40 | } 41 | } 42 | 43 | func GoPackageByPath(path, vendorPath string) (string, error) { 44 | path, err := filepath.Abs(path) 45 | if err != nil { 46 | return "", errors.Wrap(err, "failed to resolve absolute filepath") 47 | } 48 | var prefixes []string 49 | if vendorPath != "" { 50 | absVendorPath, err := filepath.Abs(vendorPath) 51 | if err != nil { 52 | return "", errors.Wrap(err, "failed to resolve absolute vendor path") 53 | } 54 | prefixes = append(prefixes, absVendorPath) 55 | } 56 | absGoPath, err := filepath.Abs(build.Default.GOPATH) 57 | if err != nil { 58 | return "", errors.Wrap(err, "failed to resolve absolute gopath") 59 | } 60 | prefixes = append(prefixes, filepath.Join(absGoPath, "src")) 61 | 62 | for _, prefix := range prefixes { 63 | if strings.HasPrefix(path, prefix) { 64 | return strings.TrimLeft(strings.TrimPrefix(path, prefix), " "+string(filepath.Separator)), nil 65 | } 66 | } 67 | 68 | return "", errors.Errorf("path '%s' is outside GOPATH or Vendor folder", path) 69 | } 70 | 71 | func IdentAccessValueResolver(ident string) ValueResolver { 72 | return func(arg string, ctx BodyContext) string { 73 | return arg + "." + ident 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /testdata/out/test/github.com/EGT-Ukraine/go2gql/testdata/common/proto2.go: -------------------------------------------------------------------------------- 1 | // This file was generated by github.com/EGT-Ukraine/go2gql. DO NOT EDIT IT 2 | package common 3 | 4 | import ( 5 | context "context" 6 | 7 | graphql "github.com/graphql-go/graphql" 8 | errors "github.com/pkg/errors" 9 | 10 | scalars "github.com/EGT-Ukraine/go2gql/api/scalars" 11 | common "github.com/EGT-Ukraine/go2gql/testdata/common" 12 | ) 13 | 14 | // Enums 15 | // Input object 16 | var Proto2MessageInput = graphql.NewInputObject(graphql.InputObjectConfig{ 17 | Name: "Proto2MessageInput", 18 | Fields: graphql.InputObjectConfigFieldMap{}, 19 | }) 20 | 21 | func init() { 22 | Proto2MessageInput.Fields()["scalar"] = &graphql.InputObjectField{PrivateName: "scalar", Type: scalars.GraphQLInt32Scalar} 23 | } 24 | 25 | // Input objects resolvers 26 | func ResolveProto2MessageInput(ctx context.Context, i interface{}) (_ *common.Proto2Message, rerr error) { 27 | if i == nil { 28 | return nil, nil 29 | } 30 | args := i.(map[string]interface{}) 31 | _ = args 32 | var result = new(common.Proto2Message) 33 | if args["scalar"] != nil { 34 | result.Scalar = func(arg interface{}) *int32 { 35 | val := arg.(int32) 36 | 37 | return &val 38 | }(args["scalar"]) 39 | } 40 | 41 | return result, nil 42 | } 43 | 44 | // Output objects 45 | var Proto2Message = graphql.NewObject(graphql.ObjectConfig{ 46 | Name: "Proto2Message", 47 | Fields: graphql.Fields{}, 48 | }) 49 | 50 | func init() { 51 | Proto2Message.AddFieldConfig("scalar", &graphql.Field{ 52 | Name: "scalar", 53 | Type: scalars.GraphQLInt32Scalar, 54 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 55 | switch src := p.Source.(type) { 56 | case *common.Proto2Message: 57 | if src == nil { 58 | return nil, nil 59 | } 60 | s := *src 61 | return s.Scalar, nil 62 | case common.Proto2Message: 63 | return src.Scalar, nil 64 | } 65 | 66 | return nil, errors.New("source of unknown type") 67 | }, 68 | }) 69 | } 70 | 71 | // Maps input objects 72 | // Maps input objects resolvers 73 | // Maps output objects 74 | -------------------------------------------------------------------------------- /generator/generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | type Infos []string 8 | 9 | func (i Infos) Contains(v string) bool { 10 | for _, val := range i { 11 | if val == v { 12 | return true 13 | } 14 | } 15 | return false 16 | } 17 | 18 | type Plugin interface { 19 | Init(*GenerateConfig, []Plugin) error 20 | Prepare() error 21 | Name() string 22 | PrintInfo(info Infos) 23 | Infos() map[string]string 24 | Generate() error 25 | } 26 | type PluginFabric = func() Plugin 27 | 28 | type Generator struct { 29 | Config *GenerateConfig 30 | Plugins []Plugin 31 | } 32 | 33 | type PluginContext struct { 34 | Plugins []Plugin 35 | } 36 | 37 | func (g *Generator) RegisterPlugin(p Plugin) error { 38 | for _, plugin := range g.Plugins { 39 | if plugin.Name() == p.Name() { 40 | return errors.Errorf("plugin with name '%s' already exists", p.Name()) 41 | } 42 | } 43 | g.Plugins = append(g.Plugins, p) 44 | return nil 45 | } 46 | func (g *Generator) Init() error { 47 | for _, plugin := range g.Plugins { 48 | err := plugin.Init(g.Config, g.Plugins) 49 | if err != nil { 50 | return errors.Wrapf(err, "failed to initialize plugin %s", plugin.Name()) 51 | } 52 | } 53 | return nil 54 | } 55 | 56 | func (g *Generator) Prepare() error { 57 | for _, plugin := range g.Plugins { 58 | err := plugin.Prepare() 59 | if err != nil { 60 | return errors.Wrapf(err, "failed to prepare plugin %s", plugin.Name()) 61 | } 62 | } 63 | return nil 64 | } 65 | 66 | func (g *Generator) Generate() error { 67 | for _, plugin := range g.Plugins { 68 | err := plugin.Generate() 69 | if err != nil { 70 | return errors.Wrapf(err, "plugin %s generation errors", plugin.Name()) 71 | } 72 | } 73 | return nil 74 | } 75 | func (g *Generator) PrintInfos(i []string) { 76 | for _, p := range g.Plugins { 77 | p.PrintInfo(Infos(i)) 78 | } 79 | } 80 | func (g *Generator) GetPluginsInfosKeys() map[string]map[string]string { 81 | res := make(map[string]map[string]string) 82 | for _, p := range g.Plugins { 83 | res[p.Name()] = p.Infos() 84 | } 85 | return res 86 | } 87 | -------------------------------------------------------------------------------- /tests/dataloader/apis/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "Api Documentation", 5 | "version": "1.0", 6 | "title": "Api Documentation", 7 | "termsOfService": "urn:tos", 8 | "contact": {}, 9 | "license": { 10 | "name": "Apache 2.0", 11 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 12 | } 13 | }, 14 | "host": "test.com", 15 | "basePath": "/", 16 | "tags": [ 17 | { 18 | "name": "test-controller", 19 | "description": "Test Controller" 20 | } 21 | ], 22 | "paths": { 23 | "/items/comments/": { 24 | "post": { 25 | "tags": [ 26 | "comments-controller" 27 | ], 28 | "operationId": "itemsComments", 29 | "consumes": [ 30 | "application/json" 31 | ], 32 | "produces": [ 33 | "application/json" 34 | ], 35 | "parameters": [ 36 | { 37 | "in": "query", 38 | "name": "itemIds", 39 | "required": true, 40 | "type": "array", 41 | "items": { 42 | "type": "integer" 43 | } 44 | } 45 | ], 46 | "responses": { 47 | "200": { 48 | "description": "OK", 49 | "schema": { 50 | "type": "array", 51 | "items": { 52 | "type": "array", 53 | "items": { 54 | "$ref": "#/definitions/ItemComment" 55 | } 56 | } 57 | } 58 | }, 59 | "404": { 60 | "description": "Not Found" 61 | } 62 | }, 63 | "deprecated": false 64 | } 65 | } 66 | }, 67 | "definitions": { 68 | "ItemComment": { 69 | "type": "object", 70 | "properties": { 71 | "id": { 72 | "type": "integer" 73 | }, 74 | "user_id": { 75 | "type": "integer" 76 | }, 77 | "text": { 78 | "type": "string" 79 | } 80 | }, 81 | "title": "Item comment" 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /api/interceptors/interceptor.go: -------------------------------------------------------------------------------- 1 | package interceptors 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | ) 6 | 7 | type Context struct { 8 | Service string 9 | Method string 10 | Params graphql.ResolveParams 11 | Request interface{} 12 | PayloadError interface{} 13 | } 14 | 15 | type ResolveArgsInvoker func() (result interface{}, err error) 16 | type CallMethodInvoker func(req interface{}) (result interface{}, err error) 17 | 18 | type ResolveArgsInterceptor func(ctx *Context, next ResolveArgsInvoker) (result interface{}, err error) 19 | type CallInterceptor func(ctx *Context, req interface{}, next CallMethodInvoker) (result interface{}, err error) 20 | 21 | type InterceptorHandler struct { 22 | ResolveArgsInterceptors []ResolveArgsInterceptor 23 | CallInterceptors []CallInterceptor 24 | } 25 | 26 | func (d *InterceptorHandler) ResolveArgs(c *Context, resolve ResolveArgsInterceptor) (res interface{}, err error) { 27 | chain := make([]ResolveArgsInterceptor, len(d.ResolveArgsInterceptors)+1) 28 | copy(chain, d.ResolveArgsInterceptors) 29 | chain[len(d.ResolveArgsInterceptors)] = resolve 30 | i := -1 31 | var invoker ResolveArgsInvoker 32 | invoker = func() (result interface{}, err error) { 33 | i++ 34 | res, err := chain[i](c, invoker) 35 | c.Request = res 36 | return res, err 37 | } 38 | return invoker() 39 | } 40 | func (d *InterceptorHandler) Call(c *Context, req interface{}, call CallInterceptor) (res interface{}, err error) { 41 | chain := make([]CallInterceptor, len(d.CallInterceptors)+1) 42 | copy(chain, d.CallInterceptors) 43 | chain[len(d.CallInterceptors)] = call 44 | i := -1 45 | var invoker CallMethodInvoker 46 | invoker = func(req interface{}) (result interface{}, err error) { 47 | i++ 48 | return chain[i](c, req, invoker) 49 | } 50 | return invoker(req) 51 | } 52 | 53 | func (d *InterceptorHandler) OnResolveArgs(i ResolveArgsInterceptor) { 54 | d.ResolveArgsInterceptors = append(d.ResolveArgsInterceptors, i) 55 | } 56 | 57 | func (d *InterceptorHandler) OnCall(i CallInterceptor) { 58 | d.CallInterceptors = append(d.CallInterceptors, i) 59 | } 60 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/maps_resolvers.go: -------------------------------------------------------------------------------- 1 | package proto2gql 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 7 | "github.com/EGT-Ukraine/go2gql/generator/plugins/proto2gql/parser" 8 | ) 9 | 10 | func (g *Proto2GraphQL) mapResolverFunctionName(mapFile *parsedFile, mp *parser.Map) string { 11 | return "Resolve" + g.inputMapVariable(mapFile, mp) 12 | } 13 | 14 | func (g *Proto2GraphQL) fileInputMapResolvers(file *parsedFile) ([]graphql.MapInputObjectResolver, error) { 15 | var res []graphql.MapInputObjectResolver 16 | for _, msg := range file.File.Messages { 17 | for _, mapFld := range msg.MapFields { 18 | keyGoType, err := g.goTypeByParserType(mapFld.Map.KeyType) 19 | if err != nil { 20 | return nil, errors.Wrap(err, "failed to resolve key go type") 21 | } 22 | valueGoType, err := g.goTypeByParserType(mapFld.Map.ValueType) 23 | if err != nil { 24 | return nil, errors.Wrap(err, "failed to resolve value go type") 25 | } 26 | keyTypeResolver, keyResolveWithErr, _, err := g.TypeValueResolver(file, mapFld.Map.KeyType, "", false) 27 | if err != nil { 28 | return nil, errors.Wrap(err, "failed to get key type value resolver") 29 | } 30 | valueParsedFile, err := g.parsedFile(mapFld.Map.ValueType.File()) 31 | if err != nil { 32 | return nil, errors.Wrapf(err, "failed to resolve message '%s' parsed file", dotedTypeName(msg.TypeName)) 33 | } 34 | valueTypeResolver, valueResolveWithErr, _, err := g.TypeValueResolver(valueParsedFile, mapFld.Map.ValueType, "", false) 35 | if err != nil { 36 | return nil, errors.Wrap(err, "failed to get value type value resolver") 37 | } 38 | 39 | res = append(res, graphql.MapInputObjectResolver{ 40 | FunctionName: g.mapResolverFunctionName(file, mapFld.Map), 41 | KeyGoType: keyGoType, 42 | ValueGoType: valueGoType, 43 | KeyResolver: keyTypeResolver, 44 | KeyResolverWithError: keyResolveWithErr, 45 | ValueResolver: valueTypeResolver, 46 | ValueResolverWithError: valueResolveWithErr, 47 | }) 48 | } 49 | 50 | } 51 | 52 | return res, nil 53 | } 54 | -------------------------------------------------------------------------------- /generator/plugins/graphql/lib/importer/importer.go: -------------------------------------------------------------------------------- 1 | package importer 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "unicode" 7 | "unicode/utf8" 8 | ) 9 | 10 | type Import struct { 11 | Alias string 12 | Path string 13 | } 14 | type Importer struct { 15 | CurrentPackage string 16 | imports []Import 17 | } 18 | 19 | func (i *Importer) resolveImport(path string) (alias, importPath string) { 20 | paths := strings.Split(path, "/") 21 | if paths[len(paths)-1] == "" { 22 | // if Path is something like `a/b/c/`(ends with slash), Alias will be "c" Path will be "a/b/c" 23 | alias, importPath = paths[len(paths)-2], strings.Join(paths[:len(paths)-1], "/") 24 | } else { 25 | alias, importPath = paths[len(paths)-1], strings.Join(paths, "/") 26 | } 27 | alias = strings.NewReplacer("-", "_", " ", "_").Replace(alias) 28 | r, _ := utf8.DecodeRune([]byte(alias)) 29 | if !unicode.IsLetter(r) { 30 | alias = "imp" + alias 31 | } 32 | return 33 | } 34 | func (i *Importer) findPath(path string) *Import { 35 | for _, imp := range i.imports { 36 | if imp.Path == path { 37 | return &imp 38 | } 39 | } 40 | return nil 41 | } 42 | func (i *Importer) aliasExists(alias string) bool { 43 | for _, imp := range i.imports { 44 | if imp.Alias == alias { 45 | return true 46 | } 47 | } 48 | return false 49 | } 50 | func (i *Importer) findAliasWithoutCollision(alias string) string { 51 | if !i.aliasExists(alias) { 52 | return alias 53 | } 54 | for j := 1; ; j++ { 55 | a := alias + "_" + strconv.Itoa(j) 56 | if !i.aliasExists(alias + "_" + strconv.Itoa(j)) { 57 | return a 58 | } 59 | } 60 | } 61 | func (i *Importer) New(path string) string { 62 | if i.CurrentPackage == path { 63 | return "" 64 | } 65 | alias, path := i.resolveImport(path) 66 | imp := i.findPath(path) 67 | if imp != nil { 68 | return imp.Alias 69 | } 70 | alias = i.findAliasWithoutCollision(alias) 71 | i.imports = append(i.imports, Import{ 72 | Alias: alias, 73 | Path: path, 74 | }) 75 | return alias 76 | } 77 | func (i *Importer) Prefix(path string) string { 78 | if i.CurrentPackage == path || path == "" { 79 | return "" 80 | } 81 | return i.New(path) + "." 82 | } 83 | 84 | func (i *Importer) Imports() []Import { 85 | res := make([]Import, len(i.imports)) 86 | copy(res, i.imports) 87 | return res 88 | } 89 | -------------------------------------------------------------------------------- /generator/plugins/graphql/type_resolvers.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | func GqlStringTypeResolver(ctx BodyContext) string { 4 | return ctx.Importer.New(GraphqlPkgPath) + ".String" 5 | } 6 | 7 | func GqlIntTypeResolver(ctx BodyContext) string { 8 | return ctx.Importer.New(GraphqlPkgPath) + ".Int" 9 | } 10 | 11 | func GqlFloatTypeResolver(ctx BodyContext) string { 12 | return ctx.Importer.New(GraphqlPkgPath) + ".Float" 13 | } 14 | 15 | func GqlBoolTypeResolver(ctx BodyContext) string { 16 | return ctx.Importer.New(GraphqlPkgPath) + ".Boolean" 17 | } 18 | 19 | func GqlDateTimeTypeResolver(ctx BodyContext) string { 20 | return ctx.Importer.New(GraphqlPkgPath) + ".DateTime" 21 | } 22 | 23 | func GqlListTypeResolver(r TypeResolver) TypeResolver { 24 | return func(ctx BodyContext) string { 25 | return ctx.Importer.New(GraphqlPkgPath) + ".NewList(" + r(ctx) + ")" 26 | } 27 | 28 | } 29 | 30 | func GqlNonNullTypeResolver(r TypeResolver) TypeResolver { 31 | return func(ctx BodyContext) string { 32 | return ctx.Importer.New(GraphqlPkgPath) + ".NewNonNull(" + r(ctx) + ")" 33 | } 34 | } 35 | 36 | func GqlNoDataTypeResolver(ctx BodyContext) string { 37 | return ctx.Importer.New(ScalarsPkgPath) + ".NoDataScalar" 38 | } 39 | 40 | func GqlInt64TypeResolver(ctx BodyContext) string { 41 | return ctx.Importer.New(ScalarsPkgPath) + ".GraphQLInt64Scalar" 42 | } 43 | 44 | func GqlInt32TypeResolver(ctx BodyContext) string { 45 | return ctx.Importer.New(ScalarsPkgPath) + ".GraphQLInt32Scalar" 46 | } 47 | 48 | func GqlUInt64TypeResolver(ctx BodyContext) string { 49 | return ctx.Importer.New(ScalarsPkgPath) + ".GraphQLUInt64Scalar" 50 | } 51 | 52 | func GqlUInt32TypeResolver(ctx BodyContext) string { 53 | return ctx.Importer.New(ScalarsPkgPath) + ".GraphQLUInt32Scalar" 54 | } 55 | 56 | func GqlFloat32TypeResolver(ctx BodyContext) string { 57 | return ctx.Importer.New(ScalarsPkgPath) + ".GraphQLFloat32Scalar" 58 | } 59 | 60 | func GqlFloat64TypeResolver(ctx BodyContext) string { 61 | return ctx.Importer.New(ScalarsPkgPath) + ".GraphQLFloat64Scalar" 62 | } 63 | 64 | func GqlBytesTypeResolver(ctx BodyContext) string { 65 | return ctx.Importer.New(ScalarsPkgPath) + ".GraphQLBytesScalar" 66 | } 67 | 68 | func GqlMultipartFileTypeResolver(ctx BodyContext) string { 69 | return ctx.Importer.New(ScalarsPkgPath) + ".MultipartFile" 70 | } 71 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/map_input_objects.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql/parser" 10 | ) 11 | 12 | func (p *Plugin) mapInputObjectGQLName(messageFile *parsedFile, obj *parser.Map) string { 13 | return messageFile.Config.GetGQLMessagePrefix() + pascalize(strings.Join(obj.Route, "__")) + "Input" 14 | } 15 | func (p *Plugin) mapInputObjectVariable(messageFile *parsedFile, obj *parser.Map) string { 16 | return messageFile.Config.GetGQLMessagePrefix() + pascalize(strings.Join(obj.Route, "")) + "Input" 17 | } 18 | 19 | func (p *Plugin) fileMapInputMessages(file *parsedFile) ([]graphql.MapInputObject, error) { 20 | var res []graphql.MapInputObject 21 | handledObjects := map[parser.Type]struct{}{} 22 | var handleType func(typ parser.Type) error 23 | handleType = func(typ parser.Type) error { 24 | switch t := typ.(type) { 25 | case *parser.Map: 26 | valueType, err := p.TypeInputTypeResolver(file, t.ElemType) 27 | if err != nil { 28 | return errors.Wrap(err, "failed to resolve map key type") 29 | } 30 | 31 | res = append(res, graphql.MapInputObject{ 32 | VariableName: p.mapInputObjectVariable(file, t), 33 | GraphQLName: p.mapInputObjectGQLName(file, t), 34 | KeyObjectType: graphql.GqlNonNullTypeResolver(graphql.GqlStringTypeResolver), 35 | ValueObjectType: valueType, 36 | }) 37 | case *parser.Object: 38 | if _, handled := handledObjects[t]; handled { 39 | return nil 40 | } 41 | handledObjects[t] = struct{}{} 42 | for _, property := range t.Properties { 43 | if err := handleType(property.Type); err != nil { 44 | return errors.Wrapf(err, "failed to handle object property %s type", property.Name) 45 | } 46 | } 47 | case *parser.Array: 48 | return handleType(t.ElemType) 49 | } 50 | return nil 51 | } 52 | for _, tag := range file.File.Tags { 53 | for _, method := range tag.Methods { 54 | for _, param := range method.Parameters { 55 | if err := handleType(param.Type); err != nil { 56 | return nil, errors.Wrapf(err, "failed to handle %s method %s parameter", method.OperationID, param.Name) 57 | } 58 | 59 | } 60 | } 61 | } 62 | return res, nil 63 | } 64 | -------------------------------------------------------------------------------- /testdata/out/test/github.com/EGT-Ukraine/go2gql/testdata/common/common.go: -------------------------------------------------------------------------------- 1 | // This file was generated by github.com/EGT-Ukraine/go2gql. DO NOT EDIT IT 2 | package common 3 | 4 | import ( 5 | context "context" 6 | 7 | graphql "github.com/graphql-go/graphql" 8 | errors "github.com/pkg/errors" 9 | 10 | scalars "github.com/EGT-Ukraine/go2gql/api/scalars" 11 | common "github.com/EGT-Ukraine/go2gql/testdata/common" 12 | ) 13 | 14 | // Enums 15 | var CommonEnum = graphql.NewEnum(graphql.EnumConfig{ 16 | Name: "CommonEnum", 17 | Description: "", 18 | Values: graphql.EnumValueConfigMap{ 19 | "CommonEnumVal0": &graphql.EnumValueConfig{ 20 | Value: 0, 21 | }, 22 | "CommonEnumVal1": &graphql.EnumValueConfig{ 23 | Value: 1, 24 | }, 25 | }, 26 | }) 27 | 28 | // Input object 29 | var CommonMessageInput = graphql.NewInputObject(graphql.InputObjectConfig{ 30 | Name: "CommonMessageInput", 31 | Fields: graphql.InputObjectConfigFieldMap{}, 32 | }) 33 | 34 | func init() { 35 | CommonMessageInput.Fields()["scalar"] = &graphql.InputObjectField{PrivateName: "scalar", Type: scalars.GraphQLInt32Scalar} 36 | } 37 | 38 | // Input objects resolvers 39 | func ResolveCommonMessageInput(ctx context.Context, i interface{}) (_ *common.CommonMessage, rerr error) { 40 | if i == nil { 41 | return nil, nil 42 | } 43 | args := i.(map[string]interface{}) 44 | _ = args 45 | var result = new(common.CommonMessage) 46 | if args["scalar"] != nil { 47 | result.Scalar = args["scalar"].(int32) 48 | } 49 | 50 | return result, nil 51 | } 52 | 53 | // Output objects 54 | var CommonMessage = graphql.NewObject(graphql.ObjectConfig{ 55 | Name: "CommonMessage", 56 | Fields: graphql.Fields{}, 57 | }) 58 | 59 | func init() { 60 | CommonMessage.AddFieldConfig("scalar", &graphql.Field{ 61 | Name: "scalar", 62 | Type: scalars.GraphQLInt32Scalar, 63 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 64 | switch src := p.Source.(type) { 65 | case *common.CommonMessage: 66 | if src == nil { 67 | return nil, nil 68 | } 69 | s := *src 70 | return s.Scalar, nil 71 | case common.CommonMessage: 72 | return src.Scalar, nil 73 | } 74 | 75 | return nil, errors.New("source of unknown type") 76 | }, 77 | }) 78 | } 79 | 80 | // Maps input objects 81 | // Maps input objects resolvers 82 | // Maps output objects 83 | -------------------------------------------------------------------------------- /testdata/test_scope.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: test_scope.proto 3 | 4 | package testdata 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 22 | 23 | type ParentScopeEnum int32 24 | 25 | const ( 26 | ParentScopeEnum_TestEnumVal0 ParentScopeEnum = 0 27 | ) 28 | 29 | var ParentScopeEnum_name = map[int32]string{ 30 | 0: "TestEnumVal0", 31 | } 32 | 33 | var ParentScopeEnum_value = map[string]int32{ 34 | "TestEnumVal0": 0, 35 | } 36 | 37 | func (x ParentScopeEnum) String() string { 38 | return proto.EnumName(ParentScopeEnum_name, int32(x)) 39 | } 40 | 41 | func (ParentScopeEnum) EnumDescriptor() ([]byte, []int) { 42 | return fileDescriptor_4934a5aa297c118f, []int{0} 43 | } 44 | 45 | func init() { 46 | proto.RegisterEnum("example.test_scope.ParentScopeEnum", ParentScopeEnum_name, ParentScopeEnum_value) 47 | } 48 | 49 | func init() { proto.RegisterFile("test_scope.proto", fileDescriptor_4934a5aa297c118f) } 50 | 51 | var fileDescriptor_4934a5aa297c118f = []byte{ 52 | // 132 bytes of a gzipped FileDescriptorProto 53 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x28, 0x49, 0x2d, 0x2e, 54 | 0x89, 0x2f, 0x4e, 0xce, 0x2f, 0x48, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4a, 0xad, 55 | 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0xd5, 0x43, 0xc8, 0x68, 0x29, 0x73, 0xf1, 0x07, 0x24, 0x16, 0xa5, 56 | 0xe6, 0x95, 0x04, 0x83, 0xb8, 0xae, 0x79, 0xa5, 0xb9, 0x42, 0x02, 0x5c, 0x3c, 0x21, 0xa9, 0xc5, 57 | 0x25, 0x20, 0x76, 0x58, 0x62, 0x8e, 0x81, 0x00, 0x83, 0x93, 0x46, 0x94, 0x5a, 0x7a, 0x66, 0x49, 58 | 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0xbe, 0xab, 0x7b, 0x88, 0x6e, 0x68, 0x76, 0x51, 0x62, 59 | 0x66, 0x5e, 0xaa, 0x7e, 0x7a, 0xbe, 0x51, 0x7a, 0x61, 0x8e, 0x3e, 0xc8, 0xc0, 0x94, 0xc4, 0x92, 60 | 0xc4, 0x24, 0x36, 0xb0, 0x4d, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x76, 0x87, 0x1f, 61 | 0x7d, 0x00, 0x00, 0x00, 62 | } 63 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/davecgh/go-spew/spew" 7 | "github.com/golang/protobuf/ptypes/timestamp" 8 | "github.com/graphql-go/graphql" 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc" 11 | 12 | "github.com/EGT-Ukraine/go2gql/example/out/schema" 13 | example "github.com/EGT-Ukraine/go2gql/example/proto" 14 | ) 15 | 16 | type Client struct { 17 | } 18 | 19 | func (Client) GetQueryMethod(ctx context.Context, in *example.AOneOffs, opts ...grpc.CallOption) (*example.B, error) { 20 | return &example.B{ 21 | RScalar: []int32{ 22 | 1, 2, 3, 4, 5, 23 | }, 24 | }, nil 25 | } 26 | 27 | func (Client) MutationMethod(ctx context.Context, in *example.B, opts ...grpc.CallOption) (*example.A, error) { 28 | return &example.A{ 29 | NREnum: example.A_Val5, 30 | }, nil 31 | } 32 | 33 | func (Client) QueryMethod(ctx context.Context, in *timestamp.Timestamp, opts ...grpc.CallOption) (*timestamp.Timestamp, error) { 34 | return ×tamp.Timestamp{ 35 | Seconds: time.Now().Unix(), 36 | Nanos: int32(time.Now().Nanosecond()), 37 | }, nil 38 | } 39 | 40 | func (Client) GetMutatuionMethod(ctx context.Context, in *example.MsgWithEmpty, opts ...grpc.CallOption) (*example.MsgWithEmpty, error) { 41 | return &example.MsgWithEmpty{}, nil 42 | } 43 | 44 | func (Client) GetEmptiesMsg(ctx context.Context, in *example.Empty, opts ...grpc.CallOption) (*example.Empty, error) { 45 | return &example.Empty{}, nil 46 | } 47 | 48 | func (Client) ListSomeEntities(ctx context.Context, in *example.ListSomeEntitiesRequest, opts ...grpc.CallOption) (*example.ListSomeEntitiesResponse, error) { 49 | return &example.ListSomeEntitiesResponse{}, nil 50 | } 51 | 52 | func main() { 53 | schem, err := schema.GetExampleSchemaSchema(schema.ExampleSchemaSchemaClients{ 54 | ServiceExampleClient: Client{}, 55 | }, nil) 56 | if err != nil { 57 | panic(err) 58 | } 59 | spew.Dump(graphql.Do(graphql.Params{ 60 | Schema: schem, 61 | RequestString: ` 62 | { 63 | getQueryMethod{ 64 | r_scalar 65 | } 66 | queryMethod{ 67 | seconds 68 | nanos 69 | } 70 | getEmptiesMsg 71 | } 72 | `, 73 | })) 74 | spew.Dump(graphql.Do(graphql.Params{ 75 | Schema: schem, 76 | RequestString: ` 77 | mutation { 78 | mutationMethod{ 79 | n_r_enum 80 | } 81 | getMutatuionMethod{ 82 | empty_field 83 | } 84 | } 85 | `, 86 | })) 87 | } 88 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/map_output_objects.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql/parser" 10 | ) 11 | 12 | func (p *Plugin) mapOutputObjectGQLName(messageFile *parsedFile, obj *parser.Map) string { 13 | return messageFile.Config.GetGQLMessagePrefix() + strings.Join(obj.Route, "__") 14 | } 15 | func (p *Plugin) mapOutputObjectVariable(messageFile *parsedFile, obj *parser.Map) string { 16 | return messageFile.Config.GetGQLMessagePrefix() + strings.Join(obj.Route, "") 17 | } 18 | 19 | func (p *Plugin) fileMapOutputMessages(file *parsedFile) ([]graphql.MapOutputObject, error) { 20 | var res []graphql.MapOutputObject 21 | handledObjects := map[parser.Type]struct{}{} 22 | var handleType func(typ parser.Type) error 23 | handleType = func(typ parser.Type) error { 24 | switch t := typ.(type) { 25 | case *parser.Map: 26 | valueType, err := p.TypeOutputTypeResolver(file, t.ElemType, true) 27 | if err != nil { 28 | return errors.Wrap(err, "failed to resolve map key type") 29 | } 30 | 31 | res = append(res, graphql.MapOutputObject{ 32 | VariableName: p.mapOutputObjectVariable(file, t), 33 | GraphQLName: p.mapOutputObjectGQLName(file, t), 34 | KeyObjectType: graphql.GqlNonNullTypeResolver(graphql.GqlStringTypeResolver), 35 | ValueObjectType: valueType, 36 | ValueResolver: func(arg string, ctx graphql.BodyContext) string { 37 | return `src := p.Source.(map[string]interface{}) 38 | if src == nil { 39 | return nil, nil 40 | } 41 | return src["value"], nil` 42 | }, 43 | }) 44 | case *parser.Object: 45 | if _, handled := handledObjects[t]; handled { 46 | return nil 47 | } 48 | handledObjects[t] = struct{}{} 49 | for _, property := range t.Properties { 50 | if err := handleType(property.Type); err != nil { 51 | return errors.Wrapf(err, "failed to handle object property %s type", property.Name) 52 | } 53 | } 54 | case *parser.Array: 55 | return handleType(t.ElemType) 56 | } 57 | return nil 58 | } 59 | for _, tag := range file.File.Tags { 60 | for _, method := range tag.Methods { 61 | for _, resp := range method.Responses { 62 | if err := handleType(resp.ResultType); err != nil { 63 | return nil, errors.Wrapf(err, "failed to handle %s method %d response", method.OperationID, resp.StatusCode) 64 | } 65 | 66 | } 67 | } 68 | } 69 | return res, nil 70 | } 71 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/map_input_objects_resolvers.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 10 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql/parser" 11 | ) 12 | 13 | func (p *Plugin) mapResolverFunctionName(file *parsedFile, mp *parser.Map) string { 14 | return "Resolve" + p.mapInputObjectVariable(file, mp) 15 | } 16 | 17 | func (p *Plugin) fileInputMapResolvers(file *parsedFile) ([]graphql.MapInputObjectResolver, error) { 18 | var res []graphql.MapInputObjectResolver 19 | handledObjects := map[parser.Type]struct{}{} 20 | var handleType func(typ parser.Type) error 21 | handleType = func(typ parser.Type) error { 22 | switch t := typ.(type) { 23 | case *parser.Map: 24 | valueGoType, err := p.goTypeByParserType(file, t.ElemType, false) 25 | if err != nil { 26 | return errors.Wrap(err, "failed to resolve map value go type") 27 | } 28 | valueResolver, valueWithErr, _, err := p.TypeValueResolver(file, t.ElemType, false, "") 29 | 30 | if err != nil { 31 | return err 32 | } 33 | 34 | res = append(res, graphql.MapInputObjectResolver{ 35 | FunctionName: p.mapResolverFunctionName(file, t), 36 | KeyGoType: graphql.GoType{ 37 | Kind: reflect.String, 38 | }, 39 | ValueGoType: valueGoType, 40 | KeyResolver: func(arg string, ctx graphql.BodyContext) string { 41 | return arg + ".(string)" 42 | }, 43 | KeyResolverWithError: false, 44 | ValueResolver: valueResolver, 45 | ValueResolverWithError: valueWithErr, 46 | }) 47 | case *parser.Object: 48 | if _, handled := handledObjects[t]; handled { 49 | return nil 50 | } 51 | handledObjects[t] = struct{}{} 52 | for _, property := range t.Properties { 53 | if err := handleType(property.Type); err != nil { 54 | return errors.Wrapf(err, "failed to handle object property %s type", property.Name) 55 | } 56 | } 57 | case *parser.Array: 58 | return handleType(t.ElemType) 59 | } 60 | return nil 61 | } 62 | for _, tag := range file.File.Tags { 63 | for _, method := range tag.Methods { 64 | for _, param := range method.Parameters { 65 | if err := handleType(param.Type); err != nil { 66 | return nil, errors.Wrapf(err, "failed to handle %s method %s parameter", method.OperationID, param.Name) 67 | } 68 | 69 | } 70 | } 71 | } 72 | sort.Slice(res, func(i, j int) bool { 73 | return res[i].FunctionName > res[j].FunctionName 74 | }) 75 | return res, nil 76 | } 77 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/message_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestMessageMethods(t *testing.T) { 10 | Convey("Test Message.HaveFields", t, func() { 11 | Convey("Should return true, if there's a normal field", func() { 12 | So(Message{NormalFields: []*NormalField{{}}}.HaveFields(), ShouldBeTrue) 13 | }) 14 | Convey("Should return true, if there's a map field", func() { 15 | So(Message{MapFields: []*MapField{{}}}.HaveFields(), ShouldBeTrue) 16 | }) 17 | Convey("Should return true, if there's a oneof", func() { 18 | So(Message{OneOffs: []*OneOf{{Fields: []*NormalField{{}}}}}.HaveFields(), ShouldBeTrue) 19 | }) 20 | Convey("Should return false, if there no fields", func() { 21 | So(Message{}.HaveFields(), ShouldBeFalse) 22 | }) 23 | }) 24 | Convey("Test Message.HaveFieldsExcept", t, func() { 25 | Convey("Should return true, if there's a normal field", func() { 26 | msg := Message{NormalFields: []*NormalField{ 27 | {Name: "a"}, 28 | {Name: "b"}, 29 | }} 30 | So(msg.HaveFieldsExcept("a"), ShouldBeTrue) 31 | }) 32 | Convey("Should return true, if there's a map field", func() { 33 | msg := Message{MapFields: []*MapField{ 34 | {Name: "a"}, 35 | {Name: "b"}, 36 | }} 37 | So(msg.HaveFieldsExcept("b"), ShouldBeTrue) 38 | }) 39 | Convey("Should return true, if there's a oneof", func() { 40 | msg := Message{OneOffs: []*OneOf{ 41 | { 42 | Fields: []*NormalField{ 43 | {Name: "a"}, 44 | {Name: "b"}, 45 | {Name: "c"}, 46 | }, 47 | }, 48 | }} 49 | So(msg.HaveFieldsExcept("b"), ShouldBeTrue) 50 | }) 51 | Convey("Should return false, if there's only excepted normal field", func() { 52 | msg := Message{NormalFields: []*NormalField{ 53 | {Name: "a"}, 54 | }} 55 | So(msg.HaveFieldsExcept("a"), ShouldBeFalse) 56 | }) 57 | Convey("Should return false, if there's only excepted map field", func() { 58 | msg := Message{MapFields: []*MapField{ 59 | {Name: "b"}, 60 | }} 61 | So(msg.HaveFieldsExcept("b"), ShouldBeFalse) 62 | }) 63 | 64 | Convey("Should return false, if there's only excepted oneof field", func() { 65 | msg := Message{OneOffs: []*OneOf{ 66 | { 67 | Fields: []*NormalField{ 68 | {Name: "b"}, 69 | }, 70 | }, 71 | }} 72 | So(msg.HaveFieldsExcept("b"), ShouldBeFalse) 73 | }) 74 | Convey("Should return false, if there's no filelds", func() { 75 | msg := Message{} 76 | So(msg.HaveFieldsExcept("b"), ShouldBeFalse) 77 | }) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /testdata/out/schema.go: -------------------------------------------------------------------------------- 1 | // This file was generated by github.com/EGT-Ukraine/go2gql. DO NOT EDIT IT 2 | package test_schema 3 | 4 | import ( 5 | graphql "github.com/graphql-go/graphql" 6 | opentracing_go "github.com/opentracing/opentracing-go" 7 | errors "github.com/pkg/errors" 8 | 9 | interceptors "github.com/EGT-Ukraine/go2gql/api/interceptors" 10 | testdata "github.com/EGT-Ukraine/go2gql/testdata" 11 | test "github.com/EGT-Ukraine/go2gql/testdata/out/test" 12 | ) 13 | 14 | type SomeSchemaSchemaClients struct { 15 | ServiceExampleClient testdata.ServiceExampleClient 16 | } 17 | 18 | func GetSomeSchemaSchema(cls SomeSchemaSchemaClients, ih *interceptors.InterceptorHandler, tr opentracing_go.Tracer) (graphql.Schema, error) { 19 | if cls.ServiceExampleClient == nil { 20 | return graphql.Schema{}, errors.Errorf("Service client ServiceExample can't be nil nil") 21 | } 22 | var ServiceExampleQueryFields = test.GetServiceExampleServiceQueryMethods(cls.ServiceExampleClient, ih, tr) 23 | var _ = ServiceExampleQueryFields 24 | var ServiceExampleMutationFields = test.GetServiceExampleServiceMutationMethods(cls.ServiceExampleClient, ih, tr) 25 | var _ = ServiceExampleMutationFields 26 | var Query = graphql.NewObject(graphql.ObjectConfig{ 27 | Name: "Query", 28 | Fields: graphql.Fields{ 29 | "getQueryMethod": ServiceExampleQueryFields["getQueryMethod"], 30 | }, 31 | }) 32 | var ServiceExampleMutations = graphql.NewObject(graphql.ObjectConfig{ 33 | Name: "ServiceExampleMutations", 34 | Fields: graphql.Fields{ 35 | "mutationMethod": ServiceExampleMutationFields["mutationMethod"], 36 | "EmptyMsgs": ServiceExampleMutationFields["EmptyMsgs"], 37 | "MsgsWithEpmty": ServiceExampleMutationFields["MsgsWithEpmty"], 38 | }, 39 | }) 40 | var NestedExampleMutation = graphql.NewObject(graphql.ObjectConfig{ 41 | Name: "NestedExampleMutation", 42 | Fields: graphql.Fields{ 43 | "ExampleService": &graphql.Field{ 44 | Name: "ExampleService", 45 | Type: ServiceExampleMutations, 46 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 47 | return struct{}{}, nil 48 | }, 49 | }, 50 | }, 51 | }) 52 | var Mutation = graphql.NewObject(graphql.ObjectConfig{ 53 | Name: "Mutation", 54 | Fields: graphql.Fields{ 55 | "nested_example_mutation": &graphql.Field{ 56 | Name: "nested_example_mutation", 57 | Type: NestedExampleMutation, 58 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 59 | return struct{}{}, nil 60 | }, 61 | }, 62 | }, 63 | }) 64 | 65 | return graphql.NewSchema(graphql.SchemaConfig{ 66 | Query: Query, 67 | Mutation: Mutation, 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /generator/plugins/graphql/templates/schemas_body.gohtml: -------------------------------------------------------------------------------- 1 | {{- /*gotype: github.com/EGT-Ukraine/go2gql/generator/plugins/graphql.SchemaBodyContext*/ -}} 2 | type {{$.SchemaName}}SchemaClients struct { 3 | {{ range $service := $.Services -}} 4 | {{$service.Name}}Client {{goType $service.ClientGoType}} 5 | {{ end -}} 6 | } 7 | 8 | {{ range $service := $.Services -}} 9 | func (c {{$.SchemaName}}SchemaClients) Get{{$service.Name}}Client() {{goType $service.ClientGoType}} { 10 | return c.{{$service.Name}}Client 11 | } 12 | {{ end -}} 13 | 14 | func Get{{$.SchemaName}}Schema(cls {{$.SchemaName}}SchemaClients, ih *{{interceptorsPkg}}.InterceptorHandler{{- if $.TracerEnabled -}}, tr {{opentracingPkg}}.Tracer{{- end -}}) ({{gqlPkg}}.Schema, error) { 15 | {{ range $service := $.Services -}} 16 | if cls.{{$service.Name}}Client == nil { 17 | return {{gqlPkg}}.Schema{}, {{errorsPkg}}.Errorf("Service client {{$service.Name}} can't be nil nil") 18 | } 19 | {{ end -}} 20 | {{ range $service := $.Services -}} 21 | var {{$service.Name}}QueryFields = {{serviceConstructor "Query" $service $}}(cls.{{$service.Name}}Client, ih{{- if $.TracerEnabled -}}, tr{{- end -}}) 22 | var _ = {{$service.Name}}QueryFields 23 | var {{$service.Name}}MutationFields = {{serviceConstructor "Mutation" $service $}}(cls.{{$service.Name}}Client, ih{{- if $.TracerEnabled -}}, tr{{- end -}}) 24 | var _ = {{$service.Name}}MutationFields 25 | {{ end -}} 26 | {{ range $object := $.Objects -}} 27 | var {{$object.Name}} = {{gqlPkg}}.NewObject({{gqlPkg}}.ObjectConfig{ 28 | Name: "{{$object.Name}}", 29 | {{ if ne $object.QuotedComment "" -}} 30 | Description: {{$object.QuotedComment}}, 31 | {{ end -}} 32 | Fields: {{gqlPkg}}.Fields{ 33 | {{ if len $object.Fields -}} 34 | {{ range $fld := $object.Fields -}} 35 | {{ if $fld.Service -}} 36 | "{{$fld.Name}}": {{$fld.Service.Name}}{{$object.TypeName}}Fields["{{$fld.Name}}"], 37 | {{ else -}} 38 | "{{$fld.Name}}": &{{gqlPkg}}.Field{ 39 | Name: "{{$fld.Name}}", 40 | Type: {{$fld.Object.Name}}, 41 | Description: {{$fld.QuotedComment}}, 42 | Resolve: func(p {{gqlPkg}}.ResolveParams) (interface{}, error) { 43 | return struct{}{}, nil 44 | }, 45 | }, 46 | {{ end -}} 47 | {{ end -}} 48 | {{ else -}} 49 | "noFields": &{{gqlPkg}}.Field{ 50 | Name: "noFields", 51 | Type: {{gqlPkg}}.String, 52 | }, 53 | {{ end -}} 54 | }, 55 | }) 56 | {{ end -}} 57 | return {{gqlPkg}}.NewSchema({{gqlPkg}}.SchemaConfig{ 58 | Query: {{$.QueryObject}}, 59 | {{ if $.MutationObject -}} 60 | Mutation: {{$.MutationObject}}, 61 | {{ end -}} 62 | }) 63 | } -------------------------------------------------------------------------------- /cmd/go2gql/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "github.com/pkg/errors" 10 | "github.com/urfave/cli" 11 | yaml "gopkg.in/yaml.v2" 12 | 13 | "github.com/EGT-Ukraine/go2gql/generator" 14 | ) 15 | 16 | var appFlags = []cli.Flag{ 17 | cli.StringFlag{ 18 | Name: "config, c", 19 | Value: "generate.yml", 20 | }, 21 | } 22 | 23 | func main() { 24 | app := cli.App{ 25 | Flags: appFlags, 26 | Before: func(c *cli.Context) error { 27 | cfg, err := ioutil.ReadFile(c.String("config")) 28 | if err != nil { 29 | return errors.Wrap(err, "Failed to read config file") 30 | } 31 | gc := new(generator.GenerateConfig) 32 | if err = yaml.Unmarshal(cfg, gc); err != nil { 33 | return errors.Wrap(err, "Failed to unmarshal config file") 34 | } 35 | 36 | if err = gc.ParseImports(); err != nil { 37 | return errors.Wrap(err, "Failed to parse config file imports") 38 | } 39 | 40 | g := &generator.Generator{ 41 | Config: gc, 42 | } 43 | 44 | for _, plugin := range Plugins(c) { 45 | if err := g.RegisterPlugin(plugin); err != nil { 46 | return errors.Wrap(err, "Failed to register plugin") 47 | } 48 | } 49 | if err = g.Init(); err != nil { 50 | return errors.Wrap(err, "failed to initialize generator") 51 | } 52 | if err = g.Prepare(); err != nil { 53 | return errors.Wrap(err, "failed to prepare generator") 54 | } 55 | c.App.Metadata["generator"] = g 56 | 57 | return nil 58 | }, 59 | Commands: []cli.Command{ 60 | { 61 | Name: "info-keys", 62 | Aliases: []string{"ik"}, 63 | Usage: "Print all possible info keys", 64 | Action: func(c *cli.Context) { 65 | g := c.App.Metadata["generator"].(*generator.Generator) 66 | for plugin, keys := range g.GetPluginsInfosKeys() { 67 | if len(keys) > 0 { 68 | fmt.Println(plugin) 69 | for key, description := range keys { 70 | fmt.Println("\t- "+key, "\t\t"+description) 71 | } 72 | } 73 | } 74 | }, 75 | }, 76 | { 77 | Name: "info", 78 | Aliases: []string{"i"}, 79 | Usage: "Print info", 80 | Flags: []cli.Flag{ 81 | cli.StringSliceFlag{ 82 | Name: "infos", 83 | }, 84 | }, 85 | Action: func(c *cli.Context) { 86 | g := c.App.Metadata["generator"].(*generator.Generator) 87 | g.PrintInfos(c.StringSlice("infos")) 88 | }, 89 | }, 90 | }, 91 | Action: func(c *cli.Context) { 92 | g := c.App.Metadata["generator"].(*generator.Generator) 93 | err := g.Generate() 94 | if err != nil { 95 | panic(errors.Wrap(err, "failed to generate")) 96 | } 97 | }, 98 | } 99 | 100 | if err := app.Run(os.Args); err != nil { 101 | log.Fatal(err) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /example/out/loaders/loaders.go: -------------------------------------------------------------------------------- 1 | package loaders 2 | 3 | import ( 4 | "context" 5 | 6 | proto "github.com/EGT-Ukraine/go2gql/example/proto" 7 | ) 8 | 9 | type LoaderClients interface { 10 | GetServiceExampleClient() proto.ServiceExampleClient 11 | } 12 | 13 | type DataLoaders struct { 14 | SomeEntitiesByIDLoader ListSomeEntitiesResponse_SomeEntityLoader 15 | SomeEntitiesByAIDLoader ListSomeEntitiesResponse_SomeEntitySliceLoader 16 | } 17 | 18 | type dataLoadersContextKeyType struct{} 19 | 20 | var dataLoadersContextKey = dataLoadersContextKeyType{} 21 | 22 | func GetContextWithLoaders(ctx context.Context, apiClients LoaderClients) context.Context { 23 | dataLoaders := &DataLoaders{ 24 | SomeEntitiesByIDLoader: createSomeEntitiesByID(ctx, apiClients.GetServiceExampleClient()), 25 | SomeEntitiesByAIDLoader: createSomeEntitiesByAID(ctx, apiClients.GetServiceExampleClient()), 26 | } 27 | 28 | return context.WithValue(ctx, dataLoadersContextKey, dataLoaders) 29 | } 30 | 31 | func createSomeEntitiesByID(ctx context.Context, client proto.ServiceExampleClient) ListSomeEntitiesResponse_SomeEntityLoader { 32 | return ListSomeEntitiesResponse_SomeEntityLoader{ 33 | fetch: func(keys []string) ([]*proto.ListSomeEntitiesResponse_SomeEntity, []error) { 34 | response, err := client.ListSomeEntities(ctx, &proto.ListSomeEntitiesRequest{Filter: &proto.ListSomeEntitiesRequest_Filter{Ids: keys}}) 35 | if err != nil { 36 | return nil, []error{err} 37 | } 38 | var result = make([]*proto.ListSomeEntitiesResponse_SomeEntity, len(keys)) 39 | for i, key := range keys { 40 | for _, value := range response.Entities { 41 | if value.Id == key { 42 | result[i] = value 43 | break 44 | } 45 | } 46 | } 47 | 48 | return result, nil 49 | }, 50 | wait: 10000000, 51 | } 52 | } 53 | func createSomeEntitiesByAID(ctx context.Context, client proto.ServiceExampleClient) ListSomeEntitiesResponse_SomeEntitySliceLoader { 54 | return ListSomeEntitiesResponse_SomeEntitySliceLoader{ 55 | fetch: func(keys []string) ([][]*proto.ListSomeEntitiesResponse_SomeEntity, []error) { 56 | response, err := client.ListSomeEntities(ctx, &proto.ListSomeEntitiesRequest{Filter: &proto.ListSomeEntitiesRequest_Filter{AIds: keys}}) 57 | if err != nil { 58 | return nil, []error{err} 59 | } 60 | var result = make([][]*proto.ListSomeEntitiesResponse_SomeEntity, len(keys)) 61 | for i, key := range keys { 62 | for _, value := range response.Entities { 63 | if value.AId == key { 64 | result[i] = append(result[i], value) 65 | } 66 | } 67 | } 68 | 69 | return result, nil 70 | }, 71 | wait: 10000000, 72 | } 73 | } 74 | func GetDataLoadersFromContext(ctx context.Context) *DataLoaders { 75 | val := ctx.Value(dataLoadersContextKey) 76 | 77 | if val == nil { 78 | return nil 79 | } 80 | 81 | return val.(*DataLoaders) 82 | } 83 | -------------------------------------------------------------------------------- /example/out/schema/example.go: -------------------------------------------------------------------------------- 1 | // This file was generated by github.com/EGT-Ukraine/go2gql. DO NOT EDIT IT 2 | package schema 3 | 4 | import ( 5 | interceptors "github.com/EGT-Ukraine/go2gql/api/interceptors" 6 | example "github.com/EGT-Ukraine/go2gql/example/out/example" 7 | proto "github.com/EGT-Ukraine/go2gql/example/proto" 8 | graphql "github.com/graphql-go/graphql" 9 | errors "github.com/pkg/errors" 10 | ) 11 | 12 | type ExampleSchemaSchemaClients struct { 13 | ServiceExampleClient proto.ServiceExampleClient 14 | } 15 | 16 | func (c ExampleSchemaSchemaClients) GetServiceExampleClient() proto.ServiceExampleClient { 17 | return c.ServiceExampleClient 18 | } 19 | func GetExampleSchemaSchema(cls ExampleSchemaSchemaClients, ih *interceptors.InterceptorHandler) (graphql.Schema, error) { 20 | if cls.ServiceExampleClient == nil { 21 | return graphql.Schema{}, errors.Errorf("Service client ServiceExample can't be nil nil") 22 | } 23 | var ServiceExampleQueryFields = example.GetServiceExampleServiceQueryMethods(cls.ServiceExampleClient, ih) 24 | var _ = ServiceExampleQueryFields 25 | var ServiceExampleMutationFields = example.GetServiceExampleServiceMutationMethods(cls.ServiceExampleClient, ih) 26 | var _ = ServiceExampleMutationFields 27 | var Example = graphql.NewObject(graphql.ObjectConfig{ 28 | Name: "Example", 29 | Description: "example result type", 30 | Fields: graphql.Fields{ 31 | "getQueryMethod": ServiceExampleQueryFields["getQueryMethod"], 32 | "queryMethod": ServiceExampleQueryFields["queryMethod"], 33 | "getEmptiesMsg": ServiceExampleQueryFields["getEmptiesMsg"], 34 | }, 35 | }) 36 | var Query = graphql.NewObject(graphql.ObjectConfig{ 37 | Name: "Query", 38 | Fields: graphql.Fields{ 39 | "example": &graphql.Field{ 40 | Name: "example", 41 | Type: Example, 42 | Description: "Service, which do smth", 43 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 44 | return struct{}{}, nil 45 | }, 46 | }, 47 | }, 48 | }) 49 | var ExampleMutation = graphql.NewObject(graphql.ObjectConfig{ 50 | Name: "ExampleMutation", 51 | Description: "example result type", 52 | Fields: graphql.Fields{ 53 | "mutationMethod": ServiceExampleMutationFields["mutationMethod"], 54 | "getMutatuionMethod": ServiceExampleMutationFields["getMutatuionMethod"], 55 | "ListSomeEntities": ServiceExampleMutationFields["ListSomeEntities"], 56 | }, 57 | }) 58 | var Mutation = graphql.NewObject(graphql.ObjectConfig{ 59 | Name: "Mutation", 60 | Fields: graphql.Fields{ 61 | "example": &graphql.Field{ 62 | Name: "example", 63 | Type: ExampleMutation, 64 | Description: "Service, which do smth", 65 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 66 | return struct{}{}, nil 67 | }, 68 | }, 69 | }, 70 | }) 71 | return graphql.NewSchema(graphql.SchemaConfig{ 72 | Query: Query, 73 | Mutation: Mutation, 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /example/generate.yml: -------------------------------------------------------------------------------- 1 | proto2gql: 2 | output_path: "./out" 3 | paths: 4 | - "vendor" 5 | - "$GOPATH/src" 6 | imports_aliases: 7 | - google/protobuf/wrappers.proto: "github.com/golang/protobuf/ptypes/wrappers/wrappers.proto" 8 | files: 9 | # Google wrappers 10 | - proto_path: "$GOPATH/src/github.com/golang/protobuf/ptypes/wrappers/wrappers.proto" 11 | proto_go_package: "github.com/golang/protobuf/ptypes/wrappers" 12 | messages: 13 | - "Value$": 14 | unwrap_field: true 15 | 16 | - name: "Google Timestamp" 17 | proto_path: "$GOPATH/src/github.com/golang/protobuf/ptypes/timestamp/timestamp.proto" 18 | proto_go_package: "github.com/golang/protobuf/ptypes/timestamp" 19 | output_path: "./out/well_known/" 20 | output_package: "well_known" 21 | 22 | - proto_path: "./proto/example.proto" 23 | output_path: "./out/example" 24 | output_package: "example" 25 | gql_messages_prefix: "Exmpl" 26 | gql_enums_prefix: "Exmpl" 27 | imports_aliases: 28 | - google/protobuf/timestamp.proto: "github.com/golang/protobuf/ptypes/timestamp/timestamp.proto" 29 | services: 30 | ServiceExample: 31 | methods: 32 | queryMethod: 33 | request_type: "QUERY" 34 | getMutatuionMethod: 35 | request_type: "MUTATION" 36 | ListSomeEntities: 37 | data_loaders: 38 | SomeEntitiesByAID: 39 | request_field: "filter.a_ids" 40 | result_field: "entities" 41 | match_field: "a_id" 42 | type: "1-N" 43 | SomeEntitiesByID: 44 | request_field: "filter.ids" 45 | result_field: "entities" 46 | match_field: "id" 47 | type: "1-1" 48 | 49 | messages: 50 | - ^A$: 51 | fields: 52 | message_with_oneoffs: {context_key: "a_msg_with_oneoffs"} 53 | data_loaders: 54 | - field_name: "someEntities2" 55 | key_field_name: "some_entity_ids" 56 | data_loader_name: "SomeEntitiesByID" 57 | - field_name: "someEntities" 58 | key_field_name: "id" 59 | data_loader_name: "SomeEntitiesByAID" 60 | - field_name: "someEntity" 61 | key_field_name: "some_entity_id" 62 | data_loader_name: "SomeEntitiesByID" 63 | 64 | graphql_schemas: 65 | - name: "ExampleSchema" 66 | output_path: "./out/schema/example.go" 67 | output_package: "schema" 68 | queries: 69 | type: "OBJECT" 70 | fields: 71 | - field: "example" 72 | type: "SERVICE" 73 | object_name: "Example" 74 | service: "ServiceExample" 75 | mutations: 76 | type: "OBJECT" 77 | fields: 78 | - field: "example" 79 | type: "SERVICE" 80 | object_name: "ExampleMutation" 81 | service: "ServiceExample" 82 | 83 | data_loaders: 84 | output_path: "./out/loaders/" -------------------------------------------------------------------------------- /tests/protounwrap/generate.yml: -------------------------------------------------------------------------------- 1 | vendor_path: "../../vendor" 2 | 3 | data_loaders: 4 | output_path: "./generated/schema/loaders/" 5 | 6 | proto2gql: 7 | output_path: "./generated/schema" 8 | paths: 9 | - "../../vendor" 10 | imports_aliases: 11 | - google/protobuf/empty.proto: "github.com/golang/protobuf/ptypes/empty/empty.proto" 12 | - google/protobuf/wrappers.proto: "github.com/golang/protobuf/ptypes/wrappers/wrappers.proto" 13 | files: 14 | - proto_path: "./apis/items.proto" 15 | services: 16 | ItemsService: 17 | methods: 18 | GetDeep: 19 | alias: "deep" 20 | request_type: "QUERY" 21 | List: 22 | alias: "list" 23 | request_type: "QUERY" 24 | ListDeepRepeated: 25 | alias: "listDeepRepeated" 26 | request_type: "QUERY" 27 | Activated: 28 | alias: "activated" 29 | request_type: "QUERY" 30 | TestRequestUnwrap: 31 | alias: "testRequestUnwrap" 32 | request_type: "QUERY" 33 | TestRequestUnwrapInnerMessage: 34 | alias: "testRequestUnwrapInnerMessage" 35 | request_type: "QUERY" 36 | TestRequestUnwrapRepeatedMessage: 37 | alias: "testRequestUnwrapRepeatedMessage" 38 | request_type: "QUERY" 39 | MapUnwrap: 40 | alias: "mapUnwrap" 41 | request_type: "QUERY" 42 | MapRepeatedUnwrap: 43 | alias: "mapRepeatedUnwrap" 44 | request_type: "QUERY" 45 | RequestUnwrapWithCasting: 46 | alias: "requestUnwrapWithCasting" 47 | request_type: "QUERY" 48 | messages: 49 | - "^DirectionValue$": 50 | unwrap_field: true 51 | - "^GetDeepResponse$": 52 | unwrap_field: true 53 | - "^GetDeepResponsePayload$": 54 | unwrap_field: true 55 | - "^ListDeepRepeatedResponse$": 56 | unwrap_field: true 57 | - "^ListDeepRepeatedResponsePayload$": 58 | unwrap_field: true 59 | - "^ActivatedResponse$": 60 | unwrap_field: true 61 | - "^ItemListResponse$": 62 | unwrap_field: true 63 | - "TestRequestUnwrapRepeatedMessageRequestPayload": 64 | unwrap_field: true 65 | - "^TestRequestUnwrapRepeatedMessage$": 66 | unwrap_field: true 67 | - "^RepeatedUnwrapResponse$": 68 | unwrap_field: true 69 | - proto_path: "../../vendor/github.com/golang/protobuf/ptypes/wrappers/wrappers.proto" 70 | proto_go_package: "github.com/golang/protobuf/ptypes/wrappers" 71 | messages: 72 | - "Value$": 73 | unwrap_field: true 74 | 75 | graphql_schemas: 76 | - name: "API" 77 | output_path: "./generated/schema/api.go" 78 | output_package: "schema" 79 | queries: 80 | type: "OBJECT" 81 | fields: 82 | - field: "items" 83 | object_name: "Items" 84 | service: "ItemsService" 85 | type: "SERVICE" 86 | -------------------------------------------------------------------------------- /testdata/common/proto2.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: common/proto2.proto 3 | 4 | package common 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 22 | 23 | // Service, which do smth 24 | type Proto2Message struct { 25 | Scalar *int32 `protobuf:"varint,1,opt,name=scalar" json:"scalar,omitempty"` 26 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 27 | XXX_unrecognized []byte `json:"-"` 28 | XXX_sizecache int32 `json:"-"` 29 | } 30 | 31 | func (m *Proto2Message) Reset() { *m = Proto2Message{} } 32 | func (m *Proto2Message) String() string { return proto.CompactTextString(m) } 33 | func (*Proto2Message) ProtoMessage() {} 34 | func (*Proto2Message) Descriptor() ([]byte, []int) { 35 | return fileDescriptor_749cd6b8b12ddea6, []int{0} 36 | } 37 | 38 | func (m *Proto2Message) XXX_Unmarshal(b []byte) error { 39 | return xxx_messageInfo_Proto2Message.Unmarshal(m, b) 40 | } 41 | func (m *Proto2Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 42 | return xxx_messageInfo_Proto2Message.Marshal(b, m, deterministic) 43 | } 44 | func (m *Proto2Message) XXX_Merge(src proto.Message) { 45 | xxx_messageInfo_Proto2Message.Merge(m, src) 46 | } 47 | func (m *Proto2Message) XXX_Size() int { 48 | return xxx_messageInfo_Proto2Message.Size(m) 49 | } 50 | func (m *Proto2Message) XXX_DiscardUnknown() { 51 | xxx_messageInfo_Proto2Message.DiscardUnknown(m) 52 | } 53 | 54 | var xxx_messageInfo_Proto2Message proto.InternalMessageInfo 55 | 56 | func (m *Proto2Message) GetScalar() int32 { 57 | if m != nil && m.Scalar != nil { 58 | return *m.Scalar 59 | } 60 | return 0 61 | } 62 | 63 | func init() { 64 | proto.RegisterType((*Proto2Message)(nil), "common.Proto2Message") 65 | } 66 | 67 | func init() { proto.RegisterFile("common/proto2.proto", fileDescriptor_749cd6b8b12ddea6) } 68 | 69 | var fileDescriptor_749cd6b8b12ddea6 = []byte{ 70 | // 123 bytes of a gzipped FileDescriptorProto 71 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0xce, 0xcf, 0xcd, 72 | 0xcd, 0xcf, 0xd3, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x37, 0xd2, 0x03, 0x53, 0x42, 0x6c, 0x10, 0x41, 73 | 0x25, 0x75, 0x2e, 0xde, 0x00, 0xb0, 0xb8, 0x6f, 0x6a, 0x71, 0x71, 0x62, 0x7a, 0xaa, 0x90, 0x18, 74 | 0x17, 0x5b, 0x71, 0x72, 0x62, 0x4e, 0x62, 0x91, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x6b, 0x10, 0x94, 75 | 0xe7, 0xa4, 0x1f, 0xa5, 0x9b, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0xef, 76 | 0xea, 0x1e, 0xa2, 0x1b, 0x9a, 0x5d, 0x94, 0x98, 0x99, 0x97, 0xaa, 0x9f, 0x9e, 0x6f, 0x94, 0x5e, 77 | 0x98, 0xa3, 0x5f, 0x92, 0x5a, 0x5c, 0x92, 0x92, 0x58, 0x92, 0xa8, 0x0f, 0x31, 0x19, 0x10, 0x00, 78 | 0x00, 0xff, 0xff, 0x06, 0x3b, 0x88, 0x20, 0x77, 0x00, 0x00, 0x00, 79 | } 80 | -------------------------------------------------------------------------------- /tests/protounwrap/apis/items.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package apis; 4 | 5 | option go_package = "github.com/EGT-Ukraine/go2gql/tests/protounwrap/generated/clients/apis;apis"; 6 | 7 | import "google/protobuf/empty.proto"; 8 | import "google/protobuf/wrappers.proto"; 9 | 10 | service ItemsService { 11 | rpc GetDeep (google.protobuf.Empty) returns (GetDeepResponse); 12 | rpc List (google.protobuf.Empty) returns (ItemListResponse); 13 | rpc ListDeepRepeated (google.protobuf.Empty) returns (ListDeepRepeatedResponse); 14 | rpc MapUnwrap(google.protobuf.Empty) returns (MapUnwrapResponse); 15 | rpc MapRepeatedUnwrap(google.protobuf.Empty) returns (MapRepeatedUnwrapResponse); 16 | rpc Activated (google.protobuf.Empty) returns (ActivatedResponse); 17 | rpc RequestUnwrapWithCasting (RequestUnwrapWithCastingRequest) returns (google.protobuf.Empty); 18 | rpc TestRequestUnwrap (TestRequestUnwrapRequest) returns (google.protobuf.Empty); 19 | rpc TestRequestUnwrapInnerMessage (TestRequestUnwrapInnerMessageRequest) returns (google.protobuf.Empty); 20 | rpc TestRequestUnwrapRepeatedMessage (TestRequestUnwrapRepeatedMessageRequest) returns (google.protobuf.Empty); 21 | } 22 | 23 | enum Direction { 24 | DEPOSIT = 0; 25 | WITHDRAW = 1; 26 | } 27 | 28 | message DirectionValue { 29 | Direction value = 1; 30 | } 31 | 32 | message RequestUnwrapWithCastingRequest { 33 | DirectionValue direction = 1; 34 | } 35 | 36 | message GetDeepResponse { 37 | GetDeepResponsePayload payload = 1; 38 | } 39 | message GetDeepResponsePayload { 40 | GetDeepResponsePayloadData data = 1; 41 | } 42 | message GetDeepResponsePayloadData { 43 | string id = 1; 44 | string name = 2; 45 | } 46 | 47 | message ListDeepRepeatedResponse { 48 | repeated ListDeepRepeatedResponsePayload payload = 1; 49 | } 50 | message ListDeepRepeatedResponsePayload { 51 | repeated ListDeepRepeatedResponsePayloadData data = 1; 52 | } 53 | message ListDeepRepeatedResponsePayloadData { 54 | string id = 1; 55 | string name = 2; 56 | } 57 | 58 | message TestRequestUnwrapRequest { 59 | google.protobuf.StringValue name = 1; 60 | } 61 | 62 | message ActivatedResponse { 63 | bool activated = 1; 64 | } 65 | 66 | message ItemListResponse { 67 | repeated Item items = 1; 68 | } 69 | 70 | message Item { 71 | int64 id = 1; 72 | string name = 2; 73 | } 74 | 75 | message TestRequestUnwrapInnerMessageRequest { 76 | repeated TestRequestUnwrapInnerMessageRequestPayload payload = 1; 77 | } 78 | 79 | message TestRequestUnwrapInnerMessageRequestPayload { 80 | repeated google.protobuf.StringValue names = 1; 81 | bool activated = 2; 82 | } 83 | 84 | message TestRequestUnwrapRepeatedMessageRequest { 85 | TestRequestUnwrapRepeatedMessageRequestPayload payload = 1; 86 | } 87 | 88 | message TestRequestUnwrapRepeatedMessageRequestPayload { 89 | repeated string test = 1; 90 | } 91 | 92 | message MapUnwrapResponse { 93 | map items = 1; 94 | } 95 | 96 | message MapRepeatedUnwrapResponse { 97 | map items = 1; 98 | } 99 | message RepeatedUnwrapResponse { 100 | repeated string items = 1; 101 | } -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/parser/file.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | //go:generate stringer -type=Kind 4 | import ( 5 | "github.com/go-openapi/spec" 6 | ) 7 | 8 | type Kind byte 9 | 10 | const ( 11 | KindUnknown Kind = iota 12 | KindString 13 | KindInt32 14 | KindInt64 15 | KindFloat32 16 | KindFloat64 17 | KindBoolean 18 | KindArray 19 | KindObject 20 | KindMap 21 | KindFile 22 | KindDateTime 23 | KindNull 24 | ) 25 | const ( 26 | ParameterPositionQuery byte = iota 27 | ParameterPositionBody 28 | ParameterPositionPath 29 | ParameterPositionHeader 30 | ParameterPositionFormData 31 | ) 32 | 33 | var parameterPositions = map[string]byte{ 34 | "path": ParameterPositionPath, 35 | "query": ParameterPositionQuery, 36 | "body": ParameterPositionBody, 37 | "header": ParameterPositionHeader, 38 | "formData": ParameterPositionFormData, 39 | } 40 | 41 | var ( 42 | scalarFloat32 = &Scalar{kind: KindFloat32} 43 | scalarFloat64 = &Scalar{kind: KindFloat64} 44 | scalarInt32 = &Scalar{kind: KindInt32} 45 | scalarInt64 = &Scalar{kind: KindInt64} 46 | scalarBoolean = &Scalar{kind: KindBoolean} 47 | scalarString = &Scalar{kind: KindString} 48 | scalarFile = &Scalar{kind: KindFile} 49 | 50 | ObjDateTime = &Object{ 51 | Name: "Timestamp", 52 | Route: []string{"Timestamp"}, 53 | Properties: []ObjectProperty{ 54 | { 55 | Name: "seconds", 56 | Required: true, 57 | Type: scalarInt64, 58 | }, 59 | { 60 | Name: "nanos", 61 | Required: true, 62 | Type: scalarInt32, 63 | }, 64 | }, 65 | } 66 | ) 67 | 68 | type Type interface { 69 | Kind() Kind 70 | } 71 | type Object struct { 72 | Name string 73 | Route []string 74 | Properties []ObjectProperty 75 | } 76 | 77 | func (o *Object) GetPropertyByName(name string) *ObjectProperty { 78 | for _, prop := range o.Properties { 79 | if prop.Name == name { 80 | return &prop 81 | } 82 | } 83 | return nil 84 | } 85 | 86 | func (Object) Kind() Kind { 87 | return KindObject 88 | } 89 | 90 | type Array struct { 91 | ElemType Type 92 | } 93 | 94 | func (Array) Kind() Kind { 95 | return KindArray 96 | } 97 | 98 | type Scalar struct { 99 | kind Kind 100 | } 101 | 102 | func (s Scalar) Kind() Kind { 103 | return s.kind 104 | } 105 | 106 | type Map struct { 107 | Route []string 108 | ElemType Type 109 | } 110 | 111 | func (Map) Kind() Kind { 112 | return KindMap 113 | } 114 | 115 | type ObjectProperty struct { 116 | Name string 117 | Description string 118 | Required bool 119 | Type Type 120 | } 121 | type MethodParameter struct { 122 | Type Type 123 | Position byte 124 | Name string 125 | Description string 126 | Required bool 127 | } 128 | type MethodResponse struct { 129 | StatusCode int 130 | Description string 131 | ResultType Type 132 | } 133 | type Tag struct { 134 | Name string 135 | Description string 136 | Methods []Method 137 | } 138 | type Method struct { 139 | Path string 140 | OperationID string 141 | Description string 142 | HTTPMethod string 143 | Parameters []MethodParameter 144 | Responses []MethodResponse 145 | } 146 | type File struct { 147 | file *spec.Swagger 148 | BasePath string 149 | Location string 150 | Tags []Tag 151 | Objects []Object 152 | } 153 | -------------------------------------------------------------------------------- /example/out/well_known/timestamp.go: -------------------------------------------------------------------------------- 1 | // This file was generated by github.com/EGT-Ukraine/go2gql. DO NOT EDIT IT 2 | package well_known 3 | 4 | import ( 5 | context "context" 6 | 7 | scalars "github.com/EGT-Ukraine/go2gql/api/scalars" 8 | timestamp "github.com/golang/protobuf/ptypes/timestamp" 9 | graphql "github.com/graphql-go/graphql" 10 | errors "github.com/pkg/errors" 11 | ) 12 | 13 | // Enums 14 | // Input object 15 | var TimestampInput = graphql.NewInputObject(graphql.InputObjectConfig{ 16 | Name: "TimestampInput", 17 | Fields: graphql.InputObjectConfigFieldMap{}, 18 | }) 19 | 20 | func init() { 21 | TimestampInput.AddFieldConfig("seconds", &graphql.InputObjectFieldConfig{Type: scalars.GraphQLInt64Scalar, Description: "Represents seconds of UTC time since Unix epoch\n 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n 9999-12-31T23:59:59Z inclusive."}) 22 | TimestampInput.AddFieldConfig("nanos", &graphql.InputObjectFieldConfig{Type: scalars.GraphQLInt32Scalar, Description: "Non-negative fractions of a second at nanosecond resolution. Negative\n second values with fractions must still have non-negative nanos values\n that count forward in time. Must be from 0 to 999,999,999\n inclusive."}) 23 | } 24 | 25 | // Input objects resolvers 26 | func ResolveTimestampInput(ctx context.Context, i interface{}) (_ *timestamp.Timestamp, rerr error) { 27 | if i == nil { 28 | return nil, nil 29 | } 30 | args, _ := i.(map[string]interface{}) 31 | _ = args 32 | var result = new(timestamp.Timestamp) 33 | if args["seconds"] != nil { 34 | result.Seconds = args["seconds"].(int64) 35 | } 36 | if args["nanos"] != nil { 37 | result.Nanos = args["nanos"].(int32) 38 | } 39 | 40 | return result, nil 41 | } 42 | 43 | // Output objects 44 | var Timestamp = graphql.NewObject(graphql.ObjectConfig{ 45 | Name: "Timestamp", 46 | Fields: graphql.Fields{}, 47 | }) 48 | 49 | func init() { 50 | 51 | Timestamp.AddFieldConfig("seconds", &graphql.Field{ 52 | Name: "seconds", 53 | Description: "Represents seconds of UTC time since Unix epoch\n 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n 9999-12-31T23:59:59Z inclusive.", 54 | Type: scalars.GraphQLInt64Scalar, 55 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 56 | switch src := p.Source.(type) { 57 | case *timestamp.Timestamp: 58 | if src == nil { 59 | return nil, nil 60 | } 61 | s := *src 62 | return s.GetSeconds(), nil 63 | case timestamp.Timestamp: 64 | return src.GetSeconds(), nil 65 | } 66 | return nil, errors.New("source of unknown type") 67 | }, 68 | }) 69 | Timestamp.AddFieldConfig("nanos", &graphql.Field{ 70 | Name: "nanos", 71 | Description: "Non-negative fractions of a second at nanosecond resolution. Negative\n second values with fractions must still have non-negative nanos values\n that count forward in time. Must be from 0 to 999,999,999\n inclusive.", 72 | Type: scalars.GraphQLInt32Scalar, 73 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 74 | switch src := p.Source.(type) { 75 | case *timestamp.Timestamp: 76 | if src == nil { 77 | return nil, nil 78 | } 79 | s := *src 80 | return s.GetNanos(), nil 81 | case timestamp.Timestamp: 82 | return src.GetNanos(), nil 83 | } 84 | return nil, errors.New("source of unknown type") 85 | }, 86 | }) 87 | 88 | } 89 | 90 | // Maps input objects 91 | // Maps input objects resolvers 92 | // Maps output objects 93 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/maps_outputs.go: -------------------------------------------------------------------------------- 1 | package proto2gql 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 7 | "github.com/EGT-Ukraine/go2gql/generator/plugins/proto2gql/parser" 8 | ) 9 | 10 | func (g *Proto2GraphQL) outputMapGraphQLName(mapFile *parsedFile, res *parser.Map) string { 11 | return g.outputMessageVariable(mapFile, res.Message) + "__" + res.Field.Name 12 | } 13 | 14 | func (g *Proto2GraphQL) outputMapVariable(mapFile *parsedFile, res *parser.Map) string { 15 | return g.outputMessageVariable(mapFile, res.Message) + "__" + res.Field.Name 16 | } 17 | 18 | func (g *Proto2GraphQL) fileMapOutputObjects(file *parsedFile) ([]graphql.MapOutputObject, error) { 19 | var res []graphql.MapOutputObject 20 | for _, msg := range file.File.Messages { 21 | for _, mapFld := range msg.MapFields { 22 | keyTypResolver, err := g.TypeOutputGraphQLTypeResolver(file, mapFld.Map.KeyType) 23 | if err != nil { 24 | return nil, errors.Wrap(err, "failed to resolve key input type resolver") 25 | } 26 | valueFile, err := g.parsedFile(mapFld.Map.ValueType.File()) 27 | if err != nil { 28 | return nil, errors.Wrap(err, "failed to resolve value type file") 29 | } 30 | 31 | valueResolver := func(arg string, ctx graphql.BodyContext) string { 32 | return `src := p.Source.(map[string]interface{}) 33 | if src == nil { 34 | return nil, nil 35 | } 36 | return src["value"], nil` 37 | } 38 | 39 | valueTypResolver, err := g.TypeOutputGraphQLTypeResolver(valueFile, mapFld.Map.ValueType) 40 | if err != nil { 41 | return nil, errors.Wrap(err, "failed to resolve value input type resolver") 42 | } 43 | 44 | valueMessage, ok := mapFld.Map.ValueType.(*parser.Message) 45 | 46 | if ok { 47 | msgCfg, err := valueFile.Config.MessageConfig(valueMessage.Name) 48 | if err != nil { 49 | return nil, errors.Wrapf(err, "failed to resolve message %s config", valueMessage.Name) 50 | } 51 | 52 | if msgCfg.UnwrapField { 53 | fields := valueMessage.GetFields() 54 | if len(fields) != 1 { 55 | return nil, errors.Errorf("can't unwrap %s output message because it contains more that 1 field", valueMessage.Name) 56 | } 57 | 58 | unwrappedField := fields[0] 59 | 60 | responseGoType, err := g.goTypeByParserType(valueMessage) 61 | 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | valueResolver = func(arg string, ctx graphql.BodyContext) string { 67 | return `src := p.Source.(map[string]interface{}) 68 | if src == nil { 69 | return nil, nil 70 | } 71 | 72 | return src["value"].(` + responseGoType.String(ctx.Importer) + `).` + camelCase(unwrappedField.GetName()) + `, nil` 73 | } 74 | 75 | valueTypResolver, err = g.TypeOutputGraphQLTypeResolver(valueFile, unwrappedField.GetType()) 76 | if err != nil { 77 | return nil, errors.Wrap(err, "failed to resolve value input type resolver") 78 | } 79 | if unwrappedField.IsRepeated() { 80 | valueTypResolver = graphql.GqlListTypeResolver(graphql.GqlNonNullTypeResolver(valueTypResolver)) 81 | } 82 | } 83 | } 84 | 85 | res = append(res, graphql.MapOutputObject{ 86 | VariableName: g.outputMapVariable(file, mapFld.Map), 87 | GraphQLName: g.outputMapGraphQLName(file, mapFld.Map), 88 | KeyObjectType: keyTypResolver, 89 | ValueObjectType: valueTypResolver, 90 | ValueResolver: valueResolver, 91 | }) 92 | } 93 | } 94 | return res, nil 95 | } 96 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/emicklei/proto" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | type Parser struct { 12 | parsedFiles []*File 13 | } 14 | 15 | func (p *Parser) ParsedFiles() []*File { 16 | return p.parsedFiles 17 | } 18 | func (p *Parser) parsedFile(filePath string) (*File, bool) { 19 | for _, f := range p.parsedFiles { 20 | if f.FilePath == filePath { 21 | return f, true 22 | } 23 | } 24 | return nil, false 25 | } 26 | 27 | func (p *Parser) importFilePath(filename string, importsAliases []map[string]string, paths []string) (filePath string, err error) { 28 | for _, aliases := range importsAliases { 29 | if v, ok := aliases[filename]; ok { 30 | filename = v 31 | break 32 | } 33 | } 34 | 35 | for _, path := range paths { 36 | p := filepath.Join(path, filename) 37 | if _, err := os.Stat(p); err == nil { 38 | return p, nil 39 | } 40 | } 41 | return "", errors.Errorf("can't find import %s in any of %s", filename, paths) 42 | } 43 | 44 | func (p *Parser) parseFileImports(file *File, importsAliases []map[string]string, paths []string) error { 45 | for _, v := range file.protoFile.Elements { 46 | imprt, ok := v.(*proto.Import) 47 | if !ok { 48 | continue 49 | } 50 | imprtPath, err := p.importFilePath(imprt.Filename, importsAliases, paths) 51 | if err != nil { 52 | return errors.Wrapf(err, "failed to resolve import(%s) File path", imprt.Filename) 53 | } 54 | absImprtPath, err := filepath.Abs(imprtPath) 55 | if err != nil { 56 | return errors.Wrapf(err, "failed to resolve import(%s) absolute File path", imprt.Filename) 57 | } 58 | if fl, ok := p.parsedFile(absImprtPath); ok { 59 | file.Imports = append(file.Imports, fl) 60 | continue 61 | } 62 | importFile, err := p.Parse(absImprtPath, importsAliases, paths) 63 | if err != nil { 64 | return errors.Wrapf(err, "can't parse import %s", imprtPath) 65 | } 66 | file.Imports = append(file.Imports, importFile) 67 | } 68 | return nil 69 | } 70 | 71 | func (p *Parser) Parse(path string, importAliases []map[string]string, paths []string) (*File, error) { 72 | absPath, err := filepath.Abs(path) 73 | if err != nil { 74 | return nil, errors.Wrap(err, "failed to resolve File absolute path") 75 | } 76 | if pf, ok := p.parsedFile(absPath); ok { 77 | return pf, nil 78 | } 79 | file, err := os.Open(absPath) 80 | if err != nil { 81 | return nil, errors.Wrapf(err, "failed to open File") 82 | } 83 | 84 | f, err := proto.NewParser(file).Parse() 85 | if err != nil { 86 | return nil, errors.Wrap(err, "failed to parse File") 87 | } 88 | result := &File{ 89 | FilePath: absPath, 90 | protoFile: f, 91 | PkgName: resolveFilePkgName(f), 92 | Services: map[string]*Service{}, 93 | Descriptors: map[string]Type{}, 94 | } 95 | result.parseGoPackage() 96 | err = p.parseFileImports(result, importAliases, paths) 97 | if err != nil { 98 | return nil, errors.Wrap(err, "failed to parse File imports") 99 | } 100 | result.parseMessages() 101 | result.parseEnums() 102 | err = result.parseServices() 103 | if err != nil { 104 | return nil, errors.Wrap(err, "failed to parse File services") 105 | } 106 | err = result.parseMessagesFields() 107 | if err != nil { 108 | return nil, errors.Wrap(err, "failed to parse messages fields") 109 | } 110 | p.parsedFiles = append(p.parsedFiles, result) 111 | return result, nil 112 | } 113 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/input_objects.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/pkg/errors" 9 | 10 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 11 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql/lib/names" 12 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql/parser" 13 | ) 14 | 15 | func (p *Plugin) inputObjectGQLName(file *parsedFile, obj *parser.Object) string { 16 | return file.Config.GetGQLMessagePrefix() + pascalize(strings.Join(obj.Route, "__")) + "Input" 17 | } 18 | func (p *Plugin) inputObjectVariable(msgFile *parsedFile, obj *parser.Object) string { 19 | return msgFile.Config.GetGQLMessagePrefix() + pascalize(strings.Join(obj.Route, "")) + "Input" 20 | } 21 | func (p *Plugin) methodParamsInputObjectGQLName(file *parsedFile, method parser.Method) string { 22 | return file.Config.GetGQLMessagePrefix() + pascalize(method.OperationID+"Params") + "Input" 23 | } 24 | 25 | func (p *Plugin) inputObjectTypeResolver(msgFile *parsedFile, obj *parser.Object) graphql.TypeResolver { 26 | if len(obj.Properties) == 0 { 27 | return graphql.GqlNoDataTypeResolver 28 | } 29 | 30 | return func(ctx graphql.BodyContext) string { 31 | return ctx.Importer.Prefix(msgFile.OutputPkg) + p.inputObjectVariable(msgFile, obj) 32 | } 33 | } 34 | 35 | func (p *Plugin) fileInputObjects(file *parsedFile) ([]graphql.InputObject, error) { 36 | var res []graphql.InputObject 37 | var handledObjects = map[parser.Type]struct{}{} 38 | var handleType func(typ parser.Type) error 39 | handleType = func(typ parser.Type) error { 40 | switch t := typ.(type) { 41 | case *parser.Object: 42 | if _, handled := handledObjects[typ]; handled { 43 | return nil 44 | } 45 | handledObjects[typ] = struct{}{} 46 | var fields []graphql.ObjectField 47 | for _, property := range t.Properties { 48 | if err := handleType(property.Type); err != nil { 49 | return err 50 | } 51 | 52 | paramCfg, err := file.Config.FieldConfig(t.Name, property.Name) 53 | if err != nil { 54 | return errors.Wrap(err, "failed to resolve property config") 55 | } 56 | 57 | if paramCfg.ContextKey != "" { 58 | continue 59 | } 60 | 61 | typeResolver, err := p.TypeInputTypeResolver(file, property.Type) 62 | if err != nil { 63 | return errors.Wrap(err, "failed to get input type resolver") 64 | } 65 | if property.Required { 66 | typeResolver = graphql.GqlNonNullTypeResolver(typeResolver) 67 | } 68 | fields = append(fields, graphql.ObjectField{ 69 | Name: names.FilterNotSupportedFieldNameCharacters(property.Name), 70 | Type: typeResolver, 71 | QuotedComment: strconv.Quote(property.Description), 72 | NeedCast: false, 73 | }) 74 | } 75 | sort.Slice(fields, func(i, j int) bool { 76 | return fields[i].Name > fields[j].Name 77 | }) 78 | res = append(res, graphql.InputObject{ 79 | VariableName: p.inputObjectVariable(file, t), 80 | GraphQLName: p.inputObjectGQLName(file, t), 81 | Fields: fields, 82 | }) 83 | 84 | case *parser.Array: 85 | return handleType(t.ElemType) 86 | } 87 | return nil 88 | } 89 | for _, tag := range file.File.Tags { 90 | for _, method := range tag.Methods { 91 | for _, parameter := range method.Parameters { 92 | err := handleType(parameter.Type) 93 | if err != nil { 94 | return nil, errors.Wrapf(err, "failed to handle method %s parameter %s", method.OperationID, parameter.Name) 95 | } 96 | } 97 | } 98 | } 99 | sort.Slice(res, func(i, j int) bool { 100 | return res[i].VariableName > res[j].VariableName 101 | }) 102 | return res, nil 103 | } 104 | -------------------------------------------------------------------------------- /testdata/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package example.package; 3 | 4 | import "common/common.proto"; 5 | import "common/proto2.proto"; 6 | import "test_scope.proto"; 7 | option go_package = "github.com/EGT-Ukraine/go2gql/testdata"; 8 | 9 | // Service, which do smth 10 | service ServiceExample { 11 | rpc getQueryMethod (RootMessage) returns (RootMessage); 12 | // rpc comment 13 | rpc mutationMethod (RootMessage2) returns (RootMessage.NestedMessage); 14 | rpc EmptyMsgs (Empty) returns (Empty); 15 | rpc MsgsWithEpmty (MessageWithEmpty) returns (MessageWithEmpty); 16 | 17 | 18 | } 19 | enum RootEnum { 20 | RootEnumVal0 = 0; 21 | RootEnumVal1 = 1; 22 | // It's a RootEnumVal2 23 | RootEnumVal2 = 2; 24 | //some comment in enum 25 | } 26 | 27 | message RootMessage { 28 | enum NestedEnum { 29 | NestedEnumVal0 = 0; 30 | NestedEnumVal1 = 1; 31 | } 32 | message NestedMessage { 33 | enum NestedNestedEnum { 34 | NestedNestedEnumVal0 = 0; 35 | NestedNestedEnumVal1 = 1; 36 | NestedNestedEnumVal2 = 2; 37 | NestedNestedEnumVal3 = 3; 38 | } 39 | repeated NestedEnum sub_r_enum = 1; // repeated Enum 40 | repeated NestedNestedEnum sub_sub_r_enum = 2; // repeated Enum 41 | } 42 | // enum_map 43 | map map_enum = 1; // Map with enum value 44 | // scalar map 45 | map map_scalar = 28; // Map with scalar value 46 | map map_msg = 2; // Map with Message value 47 | map ctx_map = 30; 48 | map ctx_map_enum = 31; 49 | // repeated Message 50 | repeated NestedMessage r_msg = 3; 51 | // repeated Scalar 52 | repeated int32 r_scalar = 4; 53 | // repeated Enum 54 | repeated RootEnum r_enum = 5; 55 | // repeated empty message 56 | repeated Empty r_empty_msg = 6; 57 | 58 | // non-repeated Enum 59 | common.CommonEnum n_r_enum = 7; 60 | // non-repeated Scalar 61 | int32 n_r_scalar = 8; 62 | // non-repeated Message 63 | common.CommonMessage n_r_msg = 9; 64 | // field from context 65 | int32 scalar_from_context = 10; 66 | // non-repeated empty message field 67 | Empty n_r_empty_msg = 11; 68 | 69 | 70 | oneof enum_first_oneoff { 71 | common.CommonEnum e_f_o_e = 12; 72 | int32 e_f_o_s = 13; 73 | common.CommonMessage e_f_o_m = 14; 74 | Empty e_f_o_em = 15; // non-repeated Message 75 | }; 76 | oneof scalar_first_oneoff { 77 | int32 s_f_o_s = 16; // non-repeated Scalar 78 | RootEnum s_f_o_e = 17; // non-repeated Enum 79 | RootMessage2 s_f_o_mes = 18; // non-repeated Message 80 | Empty s_f_o_m = 19; // non-repeated Message 81 | } 82 | oneof message_first_oneoff { 83 | RootMessage2 m_f_o_m = 20; // non-repeated Message 84 | int32 m_f_o_s = 21; // non-repeated Scalar 85 | RootEnum m_f_o_e = 22; // non-repeated Enum 86 | Empty m_f_o_em = 23; // non-repeated Message 87 | } 88 | oneof empty_first_oneoff { 89 | Empty em_f_o_em = 24; // non-repeated Message 90 | int32 em_f_o_s = 25; // non-repeated Scalar 91 | RootEnum em_f_o_en = 26; // non-repeated Enum 92 | RootMessage2 em_f_o_m = 27; // non-repeated Message 93 | } 94 | 95 | // leading dot in type name 96 | .common.CommonMessage leading_dot = 29; 97 | // parent scope 98 | test_scope.ParentScopeEnum parent_scope = 32; 99 | 100 | common.Proto2Message proto2message = 33; 101 | } 102 | message Empty { 103 | 104 | } 105 | message MessageWithEmpty { 106 | Empty empt = 1; 107 | } 108 | 109 | message RootMessage2 { 110 | int32 some_field = 1; 111 | } -------------------------------------------------------------------------------- /example/proto/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package proto; 3 | 4 | import "google/protobuf/timestamp.proto"; 5 | import "google/protobuf/wrappers.proto"; 6 | 7 | // Service, which do smth 8 | service ServiceExample { 9 | rpc getQueryMethod (AOneOffs) returns (B); // methods with prefix "get" is queries 10 | rpc mutationMethod (B) returns (A); // methods without prefix "get" is mutation 11 | rpc queryMethod (google.protobuf.Timestamp) returns (google.protobuf.Timestamp); // in generate.yml we mark, that this method is QUERY 12 | rpc getMutatuionMethod (MsgWithEmpty) returns (MsgWithEmpty); // in generate.yml we mark, that this method is MUTATION 13 | rpc getEmptiesMsg (Empty) returns (Empty); 14 | rpc ListSomeEntities (ListSomeEntitiesRequest) returns (ListSomeEntitiesResponse); 15 | } 16 | message ListSomeEntitiesRequest { 17 | message Filter { 18 | repeated string a_ids = 2; 19 | repeated string ids = 1; 20 | } 21 | Filter filter = 1; 22 | } 23 | message ListSomeEntitiesResponse { 24 | message SomeEntity { 25 | string id = 1; 26 | string name = 2; 27 | string a_id = 3; 28 | } 29 | repeated SomeEntity entities = 1; 30 | } 31 | enum someEnum { 32 | Val1 = 0; 33 | Val2 = 1; 34 | } 35 | message A { 36 | enum someEnum3 { 37 | Val5 = 0; 38 | Val6 = 1; 39 | } 40 | map map_enum = 1; // Map with enum value 41 | map map_scalar = 2; // Map with scalar value 42 | map map_msg = 3; // Map with Message value 43 | repeated google.protobuf.Timestamp r_msg = 4; // repeated Message 44 | repeated int32 r_scalar = 5; // repeated Scalar 45 | repeated someEnum r_enum = 6; // repeated Enum 46 | someEnum3 n_r_enum = 7; // non-repeated Enum 47 | int32 n_r_scalar = 8; // non-repeated Scalar 48 | google.protobuf.Timestamp n_r_msg = 9; // non-repeated Message 49 | int32 scalar_from_context = 10; 50 | someEnum enum_from_context = 11; 51 | google.protobuf.Timestamp message_from_context = 12; 52 | // repeated bytes r_bytes = 13; // TODO 53 | // bytes n_r_bytes = 14; // TODO 54 | AOneOffs message_with_oneoffs = 13; 55 | bytes test = 14; 56 | repeated string some_entity_ids = 15; 57 | repeated string some_entity_id = 16; 58 | string id = 17; 59 | google.protobuf.FloatValue unwrapped_field = 18; 60 | } 61 | message AOneOffs { 62 | oneof firstEnum { 63 | someEnum e_n_r_enum = 7; // non-repeated Enum 64 | int32 e_n_r_scalar = 8; // non-repeated Scalar 65 | google.protobuf.Timestamp e_n_r_msg = 9; // non-repeated Message 66 | }; 67 | oneof firstScalar { 68 | int32 s_n_r_scalar = 10; // non-repeated Scalar 69 | someEnum s_n_r_enum = 11; // non-repeated Enum 70 | 71 | google.protobuf.Timestamp s_n_r_msg = 12; // non-repeated Message 72 | } 73 | oneof firstMessage { 74 | google.protobuf.Timestamp m_n_r_msg = 13; // non-repeated Message 75 | int32 m_n_r_scalar = 14; // non-repeated Scalar 76 | someEnum m_n_r_enum = 15; // non-repeated Enum 77 | } 78 | } 79 | message MsgWithEmpty { 80 | Empty empty_field = 1; 81 | } 82 | message Empty { 83 | 84 | } 85 | 86 | message B { 87 | map map_enum = 1; // Map with enum value 88 | map map_scalar = 2; // Map with scalar value 89 | map map_msg = 3; // Map with Message value 90 | repeated google.protobuf.Timestamp r_msg = 4; // repeated Message 91 | repeated int32 r_scalar = 5; // repeated Scalar 92 | repeated someEnum r_enum = 6; // repeated Enum 93 | someEnum n_r_enum = 7; // non-repeated Enum 94 | int32 n_r_scalar = 8; // non-repeated Scalar 95 | google.protobuf.Timestamp n_r_msg = 9; // non-repeated Message 96 | } -------------------------------------------------------------------------------- /tests/swagger_templates/client/client.gotmpl: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | 4 | {{ if .Copyright -}}// {{ comment .Copyright -}}{{ end }} 5 | 6 | 7 | package {{ .Name }} 8 | 9 | // This file was generated by the swagger tool. 10 | // Editing this file might prove futile when you re-run the swagger generate command 11 | 12 | import ( 13 | "net/http" 14 | "github.com/go-openapi/errors" 15 | "github.com/go-openapi/swag" 16 | "github.com/go-openapi/runtime" 17 | "github.com/go-openapi/validate" 18 | 19 | strfmt "github.com/go-openapi/strfmt" 20 | 21 | {{ range .DefaultImports }}{{ printf "%q" .}} 22 | {{ end }} 23 | {{ range $key, $value := .Imports }}{{ $key }} {{ printf "%q" $value }} 24 | {{ end }} 25 | ) 26 | 27 | // New creates a new {{ humanize .Name }} API client. 28 | func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client { 29 | return &Client{transport: transport, formats: formats} 30 | } 31 | 32 | /* 33 | Client {{ if .Summary }}{{ .Summary }}{{ if .Description }} 34 | 35 | {{ blockcomment .Description }}{{ end }}{{ else if .Description}}{{ blockcomment .Description }}{{ else }}for {{ humanize .Name }} API{{ end }} 36 | */ 37 | type Client struct { 38 | transport runtime.ClientTransport 39 | formats strfmt.Registry 40 | Timeout time.Duration 41 | } 42 | type IClient interface { 43 | {{ range .Operations }} 44 | {{ pascalize .Name }}(params *{{ pascalize .Name }}Params{{ if .Authorized }}, authInfo runtime.ClientAuthInfoWriter{{end}}{{ if .HasStreamingResponse }}, writer io.Writer{{ end }}) {{ if .SuccessResponse }}({{ range .SuccessResponses }}*{{ pascalize .Name }}, {{ end }}{{ end }}error{{ if .SuccessResponse }}){{ end }} 45 | {{ end }} 46 | } 47 | {{ range .Operations }}/* 48 | {{ pascalize .Name }} {{ if .Summary }}{{ pluralizeFirstWord (humanize .Summary) }}{{ if .Description }} 49 | 50 | {{ blockcomment .Description }}{{ end }}{{ else if .Description}}{{ blockcomment .Description }}{{ else }}{{ humanize .Name }} API{{ end }} 51 | */ 52 | func (a *Client) {{ pascalize .Name }}(params *{{ pascalize .Name }}Params{{ if .Authorized }}, authInfo runtime.ClientAuthInfoWriter{{end}}{{ if .HasStreamingResponse }}, writer io.Writer{{ end }}) {{ if .SuccessResponse }}({{ range .SuccessResponses }}*{{ pascalize .Name }}, {{ end }}{{ end }}error{{ if .SuccessResponse }}){{ end }} { 53 | // TODO: Validate the params before sending 54 | if params == nil { 55 | params = New{{ pascalize .Name }}Params() 56 | } 57 | if params.timeout == 0 { 58 | params.timeout = a.Timeout 59 | } 60 | {{ $length := len .SuccessResponses }} 61 | {{ if .SuccessResponse }}result{{else}}_{{ end }}, err := a.transport.Submit(&runtime.ClientOperation{ 62 | ID: {{ printf "%q" .Name }}, 63 | Method: {{ printf "%q" .Method }}, 64 | PathPattern: {{ printf "%q" .Path }}, 65 | ProducesMediaTypes: {{ printf "%#v" .ProducesMediaTypes }}, 66 | ConsumesMediaTypes: {{ printf "%#v" .ConsumesMediaTypes }}, 67 | Schemes: {{ printf "%#v" .Schemes }}, 68 | Params: params, 69 | Reader: &{{ pascalize .Name }}Reader{formats: a.formats{{ if .HasStreamingResponse }}, writer: writer{{ end }}},{{ if .Authorized }} 70 | AuthInfo: authInfo,{{ end}} 71 | Context: params.Context, 72 | Client: params.HTTPClient, 73 | }) 74 | if err != nil { 75 | return {{ if .SuccessResponse }}{{ padSurround "nil" "nil" 0 $length }}, {{ end }}err 76 | } 77 | {{ if .SuccessResponse }}{{ if eq $length 1 }}return result.(*{{ pascalize .SuccessResponse.Name }}), nil{{ else }}switch value := result.(type) { {{ range $i, $v := .SuccessResponses }} 78 | case *{{ pascalize $v.Name }}: 79 | return {{ padSurround "value" "nil" $i $length }}, nil{{ end }} } 80 | return {{ padSurround "nil" "nil" 0 $length }}, nil{{ end }} 81 | {{ else }}return nil{{ end }} 82 | 83 | } 84 | {{ end }} 85 | 86 | // SetTransport changes the transport on the client 87 | func (a *Client) SetTransport(transport runtime.ClientTransport) { 88 | a.transport = transport 89 | } 90 | -------------------------------------------------------------------------------- /tests/dataloader/generate.yml: -------------------------------------------------------------------------------- 1 | vendor_path: "../../vendor" 2 | 3 | data_loaders: 4 | output_path: "./generated/schema/loaders/" 5 | 6 | proto2gql: 7 | output_path: "./generated/schema" 8 | paths: 9 | - "../../vendor" 10 | imports_aliases: 11 | - google/protobuf/empty.proto: "github.com/golang/protobuf/ptypes/empty/empty.proto" 12 | files: 13 | - proto_path: "./apis/reviews.proto" 14 | services: 15 | ItemsReviewService: 16 | methods: 17 | List: 18 | data_loaders: 19 | ItemReviewsByIDs: 20 | request_field: "item_id" 21 | result_field: "reviews" 22 | match_field: "item_id" 23 | type: "1-N" 24 | 25 | messages: 26 | - "^ListResponse$": 27 | unwrap_field: true 28 | 29 | - proto_path: "./apis/category.proto" 30 | services: 31 | CategoryService: 32 | methods: 33 | List: 34 | data_loaders: 35 | CategoriesByIDs: 36 | request_field: "id" 37 | result_field: "categories" 38 | match_field: "id" 39 | type: "1-1" 40 | wait_duration: 5ms 41 | 42 | 43 | - proto_path: "./apis/user.proto" 44 | services: 45 | UserService: 46 | methods: 47 | List: 48 | data_loaders: 49 | UsersByIDs: 50 | request_field: "id" 51 | result_field: "users" 52 | match_field: "id" 53 | type: "1-1" 54 | wait_duration: "5ms" 55 | - proto_path: "./apis/items.proto" 56 | services: 57 | ItemsService: 58 | methods: 59 | List: 60 | alias: "list" 61 | request_type: "QUERY" 62 | GetOne: 63 | alias: "GetOne" 64 | request_type: "QUERY" 65 | messages: 66 | - "^ItemListResponse$": 67 | unwrap_field: true 68 | - "Item$": 69 | data_loaders: 70 | - field_name: "category" 71 | key_field_name: "category_id" 72 | data_loader_name: "CategoriesByIDs" 73 | - field_name: "categories" 74 | key_field_name: "category_ids" 75 | data_loader_name: "CategoriesByIDs" 76 | - field_name: "comments" 77 | key_field_name: "id" 78 | data_loader_name: "CommentsLoader" 79 | - field_name: "reviews" 80 | key_field_name: "id" 81 | data_loader_name: "ItemReviewsByIDs" 82 | 83 | swagger2gql: 84 | output_path: "./generated/schema" 85 | files: 86 | - name: "Comments" 87 | path: "apis/swagger.json" 88 | models_go_path: "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/models" 89 | tags: 90 | "comments-controller": 91 | service_name: "CommentsService" 92 | client_go_package: "github.com/EGT-Ukraine/go2gql/tests/dataloader/generated/clients/client/comments_controller" 93 | methods: 94 | "/items/comments/": 95 | post: 96 | data_loader_provider: 97 | name: "CommentsLoader" 98 | wait_duration_ms: 5 99 | slice: true 100 | objects: 101 | - "ItemComment$": 102 | data_loaders: 103 | - field_name: "user" 104 | key_field_name: "user_id" 105 | data_loader_name: "UsersByIDs" 106 | 107 | graphql_schemas: 108 | - name: "API" 109 | output_path: "./generated/schema/api.go" 110 | output_package: "schema" 111 | queries: 112 | type: "OBJECT" 113 | fields: 114 | - field: "items" 115 | object_name: "Items" 116 | service: "ItemsService" 117 | type: "SERVICE" 118 | -------------------------------------------------------------------------------- /generator/plugins/graphql/plugin_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/pkg/errors" 7 | . "github.com/smartystreets/goconvey/convey" 8 | yaml "gopkg.in/yaml.v2" 9 | 10 | "github.com/EGT-Ukraine/go2gql/generator" 11 | ) 12 | 13 | func TestSchemaMergeRecursively(t *testing.T) { 14 | Convey("Given config with import", t, func() { 15 | var mainConfig = ` 16 | graphql_schemas: 17 | - name: "API" 18 | output_path: "./services_api/schema/api.go" 19 | output_package: "schema" 20 | queries: 21 | type: "OBJECT" 22 | fields: 23 | - field: "common" 24 | object_name: "Common" 25 | type: "OBJECT" 26 | fields: 27 | - field: "commonNested" 28 | object_name: "CommonNested" 29 | type: "OBJECT" 30 | fields: 31 | - field: "service1Field" 32 | object_name: "Service1Object" 33 | service: "Service1" 34 | type: "SERVICE" 35 | ` 36 | 37 | var importConfig = ` 38 | graphql_schemas: 39 | - name: "API" 40 | queries: 41 | type: "OBJECT" 42 | fields: 43 | - field: "common" 44 | object_name: "Common" 45 | type: "OBJECT" 46 | fields: 47 | - field: "commonNested" 48 | object_name: "CommonNested" 49 | type: "OBJECT" 50 | fields: 51 | - field: "service2Field" 52 | object_name: "Service2Object" 53 | service: "Service2" 54 | type: "SERVICE" 55 | ` 56 | gc, err := parseConfigs(mainConfig, importConfig) 57 | 58 | So(err, ShouldBeNil) 59 | 60 | Convey("When the graphql plugin is initialized", func() { 61 | graphqlPlugin := new(Plugin) 62 | 63 | if err := graphqlPlugin.Init(gc, []generator.Plugin{}); err != nil { 64 | t.Fatalf(err.Error()) 65 | } 66 | 67 | Convey("Graphql schema should be merged recursively", func() { 68 | So(graphqlPlugin.schemaConfigs[0], ShouldResemble, SchemaConfig{ 69 | Name: "API", 70 | OutputPath: "./services_api/schema/api.go", 71 | OutputPackage: "schema", 72 | Queries: &SchemaNodeConfig{ 73 | Type: "OBJECT", 74 | Fields: []SchemaNodeConfig{ 75 | { 76 | Type: "OBJECT", 77 | ObjectName: "Common", 78 | Field: "common", 79 | Fields: []SchemaNodeConfig{ 80 | { 81 | Type: "OBJECT", 82 | ObjectName: "CommonNested", 83 | Field: "commonNested", 84 | Fields: []SchemaNodeConfig{ 85 | { 86 | Type: "SERVICE", 87 | Service: "Service1", 88 | ObjectName: "Service1Object", 89 | Field: "service1Field", 90 | }, 91 | { 92 | Type: "SERVICE", 93 | Service: "Service2", 94 | ObjectName: "Service2Object", 95 | Field: "service2Field", 96 | }, 97 | }, 98 | }, 99 | }, 100 | }, 101 | }, 102 | }, 103 | }) 104 | }) 105 | }) 106 | }) 107 | } 108 | 109 | func parseConfigs(mainConfig string, importConfig string) (*generator.GenerateConfig, error) { 110 | gc := new(generator.GenerateConfig) 111 | 112 | pluginsConfig := generator.PluginsConfigs{} 113 | 114 | importedPluginsConfig := generator.ImportedPluginsConfigs{ 115 | Path: "/home/go/import.yml", 116 | PluginsConfigs: pluginsConfig, 117 | } 118 | 119 | if err := yaml.Unmarshal([]byte(importConfig), pluginsConfig); err != nil { 120 | return nil, errors.Wrap(err, "Failed to parse import config") 121 | } 122 | 123 | gc.PluginsConfigsImports = append(gc.PluginsConfigsImports, importedPluginsConfig) 124 | 125 | if err := yaml.Unmarshal([]byte(mainConfig), gc); err != nil { 126 | return nil, errors.Wrap(err, "Failed to parse main config") 127 | } 128 | 129 | return gc, nil 130 | } 131 | -------------------------------------------------------------------------------- /testdata/common/common.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: common/common.proto 3 | 4 | package common 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 22 | 23 | type CommonEnum int32 24 | 25 | const ( 26 | CommonEnum_CommonEnumVal0 CommonEnum = 0 27 | CommonEnum_CommonEnumVal1 CommonEnum = 1 28 | ) 29 | 30 | var CommonEnum_name = map[int32]string{ 31 | 0: "CommonEnumVal0", 32 | 1: "CommonEnumVal1", 33 | } 34 | 35 | var CommonEnum_value = map[string]int32{ 36 | "CommonEnumVal0": 0, 37 | "CommonEnumVal1": 1, 38 | } 39 | 40 | func (x CommonEnum) String() string { 41 | return proto.EnumName(CommonEnum_name, int32(x)) 42 | } 43 | 44 | func (CommonEnum) EnumDescriptor() ([]byte, []int) { 45 | return fileDescriptor_8f954d82c0b891f6, []int{0} 46 | } 47 | 48 | // Service, which do smth 49 | type CommonMessage struct { 50 | Scalar int32 `protobuf:"varint,1,opt,name=scalar,proto3" json:"scalar,omitempty"` 51 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 52 | XXX_unrecognized []byte `json:"-"` 53 | XXX_sizecache int32 `json:"-"` 54 | } 55 | 56 | func (m *CommonMessage) Reset() { *m = CommonMessage{} } 57 | func (m *CommonMessage) String() string { return proto.CompactTextString(m) } 58 | func (*CommonMessage) ProtoMessage() {} 59 | func (*CommonMessage) Descriptor() ([]byte, []int) { 60 | return fileDescriptor_8f954d82c0b891f6, []int{0} 61 | } 62 | 63 | func (m *CommonMessage) XXX_Unmarshal(b []byte) error { 64 | return xxx_messageInfo_CommonMessage.Unmarshal(m, b) 65 | } 66 | func (m *CommonMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 67 | return xxx_messageInfo_CommonMessage.Marshal(b, m, deterministic) 68 | } 69 | func (m *CommonMessage) XXX_Merge(src proto.Message) { 70 | xxx_messageInfo_CommonMessage.Merge(m, src) 71 | } 72 | func (m *CommonMessage) XXX_Size() int { 73 | return xxx_messageInfo_CommonMessage.Size(m) 74 | } 75 | func (m *CommonMessage) XXX_DiscardUnknown() { 76 | xxx_messageInfo_CommonMessage.DiscardUnknown(m) 77 | } 78 | 79 | var xxx_messageInfo_CommonMessage proto.InternalMessageInfo 80 | 81 | func (m *CommonMessage) GetScalar() int32 { 82 | if m != nil { 83 | return m.Scalar 84 | } 85 | return 0 86 | } 87 | 88 | func init() { 89 | proto.RegisterEnum("common.CommonEnum", CommonEnum_name, CommonEnum_value) 90 | proto.RegisterType((*CommonMessage)(nil), "common.CommonMessage") 91 | } 92 | 93 | func init() { proto.RegisterFile("common/common.proto", fileDescriptor_8f954d82c0b891f6) } 94 | 95 | var fileDescriptor_8f954d82c0b891f6 = []byte{ 96 | // 153 bytes of a gzipped FileDescriptorProto 97 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0xce, 0xcf, 0xcd, 98 | 0xcd, 0xcf, 0xd3, 0x87, 0x50, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x6c, 0x10, 0x9e, 0x92, 99 | 0x3a, 0x17, 0xaf, 0x33, 0x98, 0xe5, 0x9b, 0x5a, 0x5c, 0x9c, 0x98, 0x9e, 0x2a, 0x24, 0xc6, 0xc5, 100 | 0x56, 0x9c, 0x9c, 0x98, 0x93, 0x58, 0x24, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x1a, 0x04, 0xe5, 0x69, 101 | 0x99, 0x70, 0x71, 0x41, 0x14, 0xba, 0xe6, 0x95, 0xe6, 0x0a, 0x09, 0x71, 0xf1, 0x21, 0x78, 0x61, 102 | 0x89, 0x39, 0x06, 0x02, 0x0c, 0x18, 0x62, 0x86, 0x02, 0x8c, 0x4e, 0xfa, 0x51, 0xba, 0xe9, 0x99, 103 | 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0xae, 0xee, 0x21, 0xba, 0xa1, 0xd9, 0x45, 104 | 0x89, 0x99, 0x79, 0xa9, 0xfa, 0xe9, 0xf9, 0x46, 0xe9, 0x85, 0x39, 0xfa, 0x25, 0xa9, 0xc5, 0x25, 105 | 0x29, 0x89, 0x25, 0x89, 0x50, 0xd7, 0x25, 0xb1, 0x81, 0x9d, 0x67, 0x0c, 0x08, 0x00, 0x00, 0xff, 106 | 0xff, 0x95, 0xf0, 0xc2, 0xe5, 0xb5, 0x00, 0x00, 0x00, 107 | } 108 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/parser/message.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/emicklei/proto" 4 | 5 | type Messages []*Message 6 | 7 | func (m Messages) Copy() Messages { 8 | result := make(Messages, len(m)) 9 | copy(result, m) 10 | 11 | return result 12 | } 13 | 14 | func (m Messages) Contains(msg *Message) bool { 15 | for _, value := range m { 16 | if value == msg { 17 | return true 18 | } 19 | } 20 | 21 | return false 22 | } 23 | 24 | type Message struct { 25 | Name string 26 | QuotedComment string 27 | NormalFields []*NormalField 28 | MapFields []*MapField 29 | OneOffs []*OneOf 30 | Descriptor *proto.Message 31 | TypeName TypeName 32 | file *File 33 | parentMsg *Message 34 | } 35 | 36 | func (m Message) GetFields() []Field { 37 | var res []Field 38 | for _, field := range m.NormalFields { 39 | res = append(res, field) 40 | } 41 | for _, field := range m.MapFields { 42 | res = append(res, field) 43 | } 44 | for _, oneOff := range m.OneOffs { 45 | for _, field := range oneOff.Fields { 46 | res = append(res, field) 47 | } 48 | } 49 | 50 | return res 51 | } 52 | 53 | func (m Message) HaveFields() bool { 54 | if len(m.NormalFields) > 0 || len(m.MapFields) > 0 { 55 | return true 56 | } 57 | for _, of := range m.OneOffs { 58 | if len(of.Fields) > 0 { 59 | return true 60 | } 61 | } 62 | 63 | return false 64 | } 65 | 66 | func (m Message) GetFieldByName(name string) (Field, bool) { 67 | for _, field := range m.GetFields() { 68 | if field.GetName() == name { 69 | return field, true 70 | } 71 | } 72 | 73 | return nil, false 74 | } 75 | 76 | func (m Message) HaveFieldsExcept(field string) bool { 77 | for _, f := range m.NormalFields { 78 | if f.Name != field { 79 | return true 80 | } 81 | } 82 | for _, f := range m.MapFields { 83 | if f.Name != field { 84 | return true 85 | } 86 | } 87 | for _, of := range m.OneOffs { 88 | for _, f := range of.Fields { 89 | if f.Name != field { 90 | return true 91 | } 92 | } 93 | } 94 | 95 | return false 96 | } 97 | 98 | func (m Message) File() *File { 99 | return m.file 100 | } 101 | 102 | func (m Message) Kind() TypeKind { 103 | return TypeMessage 104 | } 105 | 106 | func (m Message) String() string { 107 | return m.Name + " message" 108 | } 109 | 110 | func (m Message) GetFullName() string { 111 | parentMessage := m.parentMsg 112 | 113 | if parentMessage != nil { 114 | parentMessageName := parentMessage.GetFullName() 115 | 116 | return parentMessageName + "." + m.Name 117 | } 118 | 119 | if m.file.PkgName != "" { 120 | return m.file.PkgName + "." + m.Name 121 | } 122 | 123 | return m.Name 124 | } 125 | 126 | type Field interface { 127 | GetName() string 128 | GetType() Type 129 | IsRepeated() bool 130 | } 131 | 132 | type NormalField struct { 133 | Name string 134 | QuotedComment string 135 | Repeated bool 136 | descriptor *proto.Field 137 | Type Type 138 | Optional bool 139 | Required bool 140 | OneOf *OneOf 141 | } 142 | 143 | func (n *NormalField) GetName() string { 144 | return n.Name 145 | } 146 | 147 | func (n *NormalField) GetType() Type { 148 | return n.Type 149 | } 150 | func (n *NormalField) IsRepeated() bool { 151 | return n.Repeated 152 | } 153 | 154 | type MapField struct { 155 | Name string 156 | QuotedComment string 157 | descriptor *proto.MapField 158 | Map *Map 159 | } 160 | 161 | func (n *MapField) GetName() string { 162 | return n.Name 163 | } 164 | 165 | func (n *MapField) GetType() Type { 166 | return n.Map 167 | } 168 | func (n *MapField) IsRepeated() bool { 169 | return false 170 | } 171 | 172 | type OneOf struct { 173 | Name string 174 | Fields []*NormalField 175 | } 176 | 177 | type Map struct { 178 | Message *Message 179 | KeyType Type 180 | ValueType Type 181 | Field *proto.MapField 182 | file *File 183 | } 184 | 185 | func (m Map) File() *File { 186 | return m.file 187 | } 188 | 189 | func (m Map) Kind() TypeKind { 190 | return TypeMap 191 | } 192 | 193 | func (m Map) String() string { 194 | return m.Message.Name + "." + m.Field.Name + " map" 195 | } 196 | -------------------------------------------------------------------------------- /generator/plugins/dataloader/plugin.go: -------------------------------------------------------------------------------- 1 | package dataloader 2 | 3 | import ( 4 | "path/filepath" 5 | 6 | "github.com/mitchellh/mapstructure" 7 | "github.com/pkg/errors" 8 | 9 | "github.com/EGT-Ukraine/go2gql/generator" 10 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 11 | ) 12 | 13 | const ( 14 | PluginName = "dataloader" 15 | DataLoadersConfigsKey = "data_loaders" 16 | ) 17 | 18 | type Plugin struct { 19 | gqlPlugin *graphql.Plugin 20 | generateCfg *generator.GenerateConfig 21 | dataLoader *DataLoader 22 | dataLoaderConfigs *DataLoadersConfig 23 | 24 | loaders map[string]LoaderModel 25 | } 26 | 27 | func (p *Plugin) AddLoader(loader LoaderModel) { 28 | p.loaders[loader.Name] = loader 29 | } 30 | 31 | func (p *Plugin) Prepare() error { 32 | return nil 33 | } 34 | 35 | func (p *Plugin) Init(config *generator.GenerateConfig, plugins []generator.Plugin) error { 36 | p.generateCfg = config 37 | 38 | for _, plugin := range plugins { 39 | if g, ok := plugin.(*graphql.Plugin); ok { 40 | p.gqlPlugin = g 41 | 42 | break 43 | } 44 | } 45 | 46 | if p.gqlPlugin == nil { 47 | return errors.New("graphql plugin was not found") 48 | } 49 | 50 | var dataLoadersConfig DataLoadersConfig 51 | 52 | if config.PluginsConfigs[DataLoadersConfigsKey] != nil { 53 | if err := mapstructure.Decode(config.PluginsConfigs[DataLoadersConfigsKey], &dataLoadersConfig); err != nil { 54 | return errors.Wrap(err, "failed to decode dataloaders config") 55 | } 56 | 57 | outPath, err := filepath.Abs(dataLoadersConfig.OutputPath) 58 | 59 | if err != nil { 60 | return errors.Wrapf(err, "Failed to normalize path") 61 | } 62 | 63 | dataLoadersConfig.OutputPath = outPath 64 | 65 | p.dataLoaderConfigs = &dataLoadersConfig 66 | } 67 | 68 | p.loaders = make(map[string]LoaderModel) 69 | 70 | return nil 71 | } 72 | 73 | func (p Plugin) Name() string { 74 | return PluginName 75 | } 76 | 77 | func (p *Plugin) validateOutputObjects(gqlFiles map[string]*graphql.TypesFile) error { 78 | for _, gqlFile := range gqlFiles { 79 | for _, outputObject := range gqlFile.OutputObjects { 80 | for _, dataLoaderField := range outputObject.DataLoaderFields { 81 | dataLoader, ok := p.loaders[dataLoaderField.DataLoaderName] 82 | 83 | if !ok { 84 | return errors.Errorf( 85 | "Failed to found dataloader with name %s in object %s", 86 | dataLoaderField.DataLoaderName, 87 | outputObject.GraphQLName, 88 | ) 89 | } 90 | 91 | outputArgument := outputObject.FindFieldByName(dataLoaderField.ParentKeyFieldName) 92 | 93 | if outputArgument == nil { 94 | return errors.Errorf( 95 | "Field `%s` not found in `%s`", 96 | dataLoaderField.ParentKeyFieldName, 97 | outputObject.GraphQLName, 98 | ) 99 | } 100 | 101 | if !outputArgument.GoType.Scalar { 102 | return errors.Errorf( 103 | "Field `%s` in `%s` must be scalar", 104 | dataLoaderField.ParentKeyFieldName, 105 | outputObject.GraphQLName, 106 | ) 107 | } 108 | 109 | if dataLoader.InputGoType.ElemType.Kind != outputArgument.GoType.Kind { 110 | // TODO: use type casting if possible. 111 | return errors.New("input argument must be same type as output") 112 | } 113 | } 114 | } 115 | } 116 | 117 | return nil 118 | } 119 | 120 | func (p *Plugin) PrintInfo(info generator.Infos) { 121 | } 122 | 123 | func (p *Plugin) Infos() map[string]string { 124 | return nil 125 | } 126 | 127 | func (p *Plugin) Generate() error { 128 | if p.dataLoaderConfigs == nil { 129 | return nil 130 | } 131 | 132 | dataLoader, err := p.createDataLoader(p.dataLoaderConfigs, p.generateCfg.VendorPath) 133 | 134 | if err != nil { 135 | return errors.Wrap(err, "failed to process dataloader config") 136 | } 137 | 138 | p.dataLoader = dataLoader 139 | 140 | p.gqlPlugin.AddOutputObjectFieldRenderer(&fieldsRenderer{ 141 | dataLoader: p.dataLoader, 142 | }) 143 | 144 | if err := p.validateOutputObjects(p.gqlPlugin.Types()); err != nil { 145 | return errors.Wrap(err, "failed to validate graphql files") 146 | } 147 | 148 | loaderGen := NewLoaderGenerator(dataLoader) 149 | 150 | if err := loaderGen.GenerateDataLoaders(); err != nil { 151 | return errors.Wrap(err, "failed to generate data loader files") 152 | } 153 | 154 | return nil 155 | } 156 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/templates_resolvers.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "bytes" 5 | "text/template" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 10 | ) 11 | 12 | func (p *Plugin) renderArrayValueResolver( 13 | arg string, 14 | resultGoTyp graphql.GoType, 15 | ctx graphql.BodyContext, 16 | elemResolver graphql.ValueResolver, 17 | elemResolverWithErr bool) (string, error) { 18 | 19 | tplBody, err := templatesValue_resolver_arrayGohtmlBytes() 20 | if err != nil { 21 | panic(errors.Wrap(err, "failed to get array value resolver template").Error()) 22 | } 23 | tpl, err := template.New("array_value_resolver").Funcs(template.FuncMap{ 24 | "errorsPkg": func() string { 25 | return ctx.Importer.New(graphql.ErrorsPkgPath) 26 | }, 27 | }).Parse(string(tplBody)) 28 | if err != nil { 29 | panic(errors.Wrap(err, "failed to parse array value resolver template")) 30 | } 31 | res := new(bytes.Buffer) 32 | err = tpl.Execute(res, map[string]interface{}{ 33 | "resultType": func() string { 34 | return resultGoTyp.String(ctx.Importer) 35 | }, 36 | "rootCtx": ctx, 37 | "elemResolver": elemResolver, 38 | "elemResolverWithErr": elemResolverWithErr, 39 | "arg": arg, 40 | }) 41 | return res.String(), err 42 | } 43 | 44 | func (p *Plugin) renderPtrDatetimeResolver(arg string, ctx graphql.BodyContext) (string, error) { 45 | tplBody, err := templatesValue_resolver_ptr_datetimeGohtmlBytes() 46 | if err != nil { 47 | panic(errors.Wrap(err, "failed to get array value resolver template").Error()) 48 | } 49 | tpl, err := template.New("array_value_resolver").Funcs(template.FuncMap{ 50 | "strfmtPkg": func() string { 51 | return ctx.Importer.New(strFmtPkg) 52 | }, 53 | "errorsPkg": func() string { 54 | return ctx.Importer.New(graphql.ErrorsPkgPath) 55 | }, 56 | "timePkg": func() string { 57 | return ctx.Importer.New(timePkg) 58 | }, 59 | }).Parse(string(tplBody)) 60 | if err != nil { 61 | panic(errors.Wrap(err, "failed to parse array value resolver template")) 62 | } 63 | res := new(bytes.Buffer) 64 | err = tpl.Execute(res, map[string]interface{}{ 65 | "arg": arg, 66 | }) 67 | return res.String(), err 68 | } 69 | 70 | func (p *Plugin) renderDatetimeValueResolverTemplate(arg string, ctx graphql.BodyContext) (string, error) { 71 | tplBody, err := templatesValue_resolver_datetimeGohtmlBytes() 72 | if err != nil { 73 | panic(errors.Wrap(err, "failed to get array value resolver template").Error()) 74 | } 75 | tpl, err := template.New("array_value_resolver").Funcs(template.FuncMap{ 76 | "strfmtPkg": func() string { 77 | return ctx.Importer.New(strFmtPkg) 78 | }, 79 | "errorsPkg": func() string { 80 | return ctx.Importer.New(graphql.ErrorsPkgPath) 81 | }, 82 | "timePkg": func() string { 83 | return ctx.Importer.New(timePkg) 84 | }, 85 | }).Parse(string(tplBody)) 86 | if err != nil { 87 | panic(errors.Wrap(err, "failed to parse array value resolver template")) 88 | } 89 | res := new(bytes.Buffer) 90 | err = tpl.Execute(res, map[string]interface{}{ 91 | "arg": arg, 92 | }) 93 | return res.String(), err 94 | } 95 | 96 | func (p *Plugin) renderMethodCaller(responseType, requestType, requestVar, clientVar, methodName string) (string, error) { 97 | tplBody, err := templatesMethod_callerGohtmlBytes() 98 | if err != nil { 99 | panic(errors.Wrap(err, "failed to get array value resolver template").Error()) 100 | } 101 | tpl, err := template.New("array_value_resolver").Parse(string(tplBody)) 102 | if err != nil { 103 | panic(errors.Wrap(err, "failed to parse array value resolver template")) 104 | } 105 | res := new(bytes.Buffer) 106 | err = tpl.Execute(res, map[string]interface{}{ 107 | "clientVar": clientVar, 108 | "methodName": methodName, 109 | "reqType": requestType, 110 | "reqVar": requestVar, 111 | "respType": responseType, 112 | }) 113 | return res.String(), err 114 | } 115 | 116 | func (p *Plugin) renderNullMethodCaller(requestType, requestVar, clientVar, methodName string) (string, error) { 117 | tplBody, err := templatesMethod_caller_nullGohtmlBytes() 118 | if err != nil { 119 | panic(errors.Wrap(err, "failed to get array value resolver template").Error()) 120 | } 121 | tpl, err := template.New("array_value_resolver").Parse(string(tplBody)) 122 | if err != nil { 123 | panic(errors.Wrap(err, "failed to parse array value resolver template")) 124 | } 125 | res := new(bytes.Buffer) 126 | err = tpl.Execute(res, map[string]interface{}{ 127 | "clientVar": clientVar, 128 | "methodName": methodName, 129 | "reqType": requestType, 130 | "reqVar": requestVar, 131 | }) 132 | return res.String(), err 133 | } 134 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/config.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "regexp" 5 | "time" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/dataloader" 10 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql/lib/names" 11 | ) 12 | 13 | type FieldConfig struct { 14 | ContextKey string `mapstructure:"context_key"` 15 | } 16 | type ObjectConfig struct { 17 | Fields map[string]FieldConfig `mapstructure:"fields"` 18 | DataLoaders []dataloader.FieldConfig `mapstructure:"data_loaders"` 19 | } 20 | 21 | type MethodConfig struct { 22 | Alias string `mapstructure:"alias"` 23 | RequestType string `mapstructure:"request_type"` // QUERY | MUTATION 24 | DataLoaderProvider ProviderConfig `mapstructure:"data_loader_provider"` 25 | } 26 | 27 | type ProviderConfig struct { 28 | Name string `mapstructure:"name"` 29 | WaitDuration time.Duration `mapstructure:"wait_duration"` 30 | Slice bool 31 | } 32 | 33 | type TagConfig struct { 34 | ClientGoPackage string `mapstructure:"client_go_package"` 35 | ServiceName string `mapstructure:"service_name"` 36 | Methods map[string]map[string]MethodConfig `mapstructure:"methods"` 37 | } 38 | type Config struct { 39 | Files []*SwaggerFileConfig `mapstructure:"files"` 40 | OutputPath string `mapstructure:"output_path"` 41 | Messages []map[string]ObjectConfig `mapstructure:"messages"` 42 | } 43 | 44 | func (c *Config) GetOutputPath() string { 45 | if c == nil { 46 | return "" 47 | } 48 | 49 | return c.OutputPath 50 | } 51 | 52 | type ParamConfig struct { 53 | ParamName string `mapstructure:"param_name"` 54 | ContextKey string `mapstructure:"context_key"` 55 | } 56 | 57 | type SwaggerFileConfig struct { 58 | Name string `mapstructure:"name"` 59 | 60 | Path string `mapstructure:"path"` 61 | 62 | ModelsGoPath string `mapstructure:"models_go_path"` 63 | 64 | OutputPkg string `mapstructure:"output_package"` 65 | OutputPath string `mapstructure:"output_path"` 66 | 67 | GQLObjectsPrefix string `mapstructure:"gql_objects_prefix"` 68 | 69 | Tags map[string]*TagConfig `mapstructure:"tags"` 70 | Objects []map[string]ObjectConfig `mapstructure:"objects"` 71 | ParamsConfig []ParamConfig `mapstructure:"params_config"` 72 | } 73 | 74 | func (pc *SwaggerFileConfig) ObjectConfig(objName string) (ObjectConfig, error) { 75 | if pc == nil { 76 | return ObjectConfig{}, nil 77 | } 78 | for _, cfgs := range pc.Objects { 79 | for msgNameRegex, cfg := range cfgs { 80 | r, err := regexp.Compile(msgNameRegex) 81 | if err != nil { 82 | return ObjectConfig{}, errors.Wrapf(err, "failed to compile object name regex '%s'", msgNameRegex) 83 | } 84 | if r.MatchString(objName) { 85 | return cfg, nil 86 | } 87 | } 88 | } 89 | 90 | return ObjectConfig{}, nil 91 | } 92 | 93 | func (pc *SwaggerFileConfig) FieldConfig(objName string, fieldName string) (FieldConfig, error) { 94 | cfg, err := pc.ObjectConfig(objName) 95 | 96 | if err != nil { 97 | return FieldConfig{}, errors.Wrap(err, "failed to resolve property config") 98 | } 99 | 100 | if cfg.Fields != nil { 101 | paramGqlName := names.FilterNotSupportedFieldNameCharacters(fieldName) 102 | 103 | paramCfg, ok := cfg.Fields[paramGqlName] 104 | 105 | if ok { 106 | return paramCfg, nil 107 | } 108 | } 109 | 110 | for _, paramConfig := range pc.ParamsConfig { 111 | if paramConfig.ParamName == fieldName { 112 | return FieldConfig{ContextKey: paramConfig.ContextKey}, nil 113 | } 114 | } 115 | 116 | return FieldConfig{}, nil 117 | } 118 | 119 | func (pc *SwaggerFileConfig) GetName() string { 120 | if pc == nil { 121 | return "" 122 | } 123 | 124 | return pc.Name 125 | } 126 | 127 | func (pc *SwaggerFileConfig) GetPath() string { 128 | if pc == nil { 129 | return "" 130 | } 131 | 132 | return pc.Path 133 | } 134 | 135 | func (pc *SwaggerFileConfig) GetOutputPkg() string { 136 | if pc == nil { 137 | return "" 138 | } 139 | 140 | return pc.OutputPkg 141 | } 142 | 143 | func (pc *SwaggerFileConfig) GetOutputPath() string { 144 | if pc == nil { 145 | return "" 146 | } 147 | 148 | return pc.OutputPath 149 | } 150 | 151 | func (pc *SwaggerFileConfig) GetGQLMessagePrefix() string { 152 | if pc == nil { 153 | return "" 154 | } 155 | 156 | return pc.GQLObjectsPrefix 157 | } 158 | 159 | func (pc *SwaggerFileConfig) GetTags() map[string]*TagConfig { 160 | if pc == nil { 161 | return map[string]*TagConfig{} 162 | } 163 | 164 | return pc.Tags 165 | } 166 | 167 | func (pc *SwaggerFileConfig) GetObjects() []map[string]ObjectConfig { 168 | if pc == nil { 169 | return []map[string]ObjectConfig{} 170 | } 171 | 172 | return pc.Objects 173 | } 174 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/plugin.go: -------------------------------------------------------------------------------- 1 | package proto2gql 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/mitchellh/mapstructure" 8 | "github.com/pkg/errors" 9 | 10 | "github.com/EGT-Ukraine/go2gql/generator" 11 | "github.com/EGT-Ukraine/go2gql/generator/plugins/dataloader" 12 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 13 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql/lib/pluginconfig" 14 | ) 15 | 16 | const ( 17 | PluginName = "proto2gql" 18 | PluginConfigKey = "proto2gql" 19 | PluginImportConfigKey = "proto2gql_files" 20 | ) 21 | 22 | type Plugin struct { 23 | graphql *graphql.Plugin 24 | dataLoaderPlugin *dataloader.Plugin 25 | config *Config 26 | generateConfig *generator.GenerateConfig 27 | } 28 | 29 | func (p *Plugin) Init(config *generator.GenerateConfig, plugins []generator.Plugin) error { 30 | for _, plugin := range plugins { 31 | switch plugin.Name() { 32 | case graphql.PluginName: 33 | p.graphql = plugin.(*graphql.Plugin) 34 | case dataloader.PluginName: 35 | p.dataLoaderPlugin = plugin.(*dataloader.Plugin) 36 | } 37 | 38 | } 39 | if p.graphql == nil { 40 | return errors.New("'graphql' plugin is not installed") 41 | } 42 | if p.dataLoaderPlugin == nil { 43 | return errors.New("'dataloader' plugin is not installed") 44 | } 45 | cfg := new(Config) 46 | err := pluginconfig.Decode(config.PluginsConfigs[PluginConfigKey], cfg) 47 | if err != nil { 48 | return errors.Wrap(err, "failed to decode config") 49 | } 50 | p.generateConfig = config 51 | p.config = cfg 52 | 53 | if err = p.parseImports(); err != nil { 54 | return errors.Wrap(err, "failed to decode imports") 55 | } 56 | 57 | err = p.normalizeGenerateConfigPaths() 58 | if err != nil { 59 | return errors.Wrap(err, "failed to normalize config paths") 60 | } 61 | 62 | return nil 63 | } 64 | 65 | func (p *Plugin) parseImports() error { 66 | for _, pluginsConfigsImports := range p.generateConfig.PluginsConfigsImports { 67 | configs := new([]*ProtoFileConfig) 68 | if err := mapstructure.Decode(pluginsConfigsImports.PluginsConfigs[PluginImportConfigKey], configs); err != nil { 69 | return errors.Wrap(err, "failed to decode config") 70 | } 71 | 72 | for _, config := range *configs { 73 | var importFileDir = filepath.Dir(pluginsConfigsImports.Path) 74 | 75 | var protoPath = filepath.Join(importFileDir, config.ProtoPath) 76 | 77 | config.ProtoPath = protoPath 78 | config.Paths = append(config.Paths, importFileDir) 79 | p.config.Files = append(p.config.Files, config) 80 | } 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func (p *Plugin) normalizeGenerateConfigPaths() error { 87 | for i, path := range p.config.Paths { 88 | normalizedPath := os.ExpandEnv(path) 89 | normalizedPath, err := filepath.Abs(normalizedPath) 90 | if err != nil { 91 | return errors.Wrapf(err, "failed to make normalized path '%s' absolute", normalizedPath) 92 | } 93 | p.config.Paths[i] = normalizedPath 94 | } 95 | for i, file := range p.config.Files { 96 | normalizedPath := os.ExpandEnv(file.ProtoPath) 97 | normalizedPath, err := filepath.Abs(normalizedPath) 98 | if err != nil { 99 | return errors.Wrapf(err, "failed to make normalized path '%s' absolute", normalizedPath) 100 | } 101 | p.config.Files[i].ProtoPath = normalizedPath 102 | 103 | } 104 | 105 | return nil 106 | } 107 | 108 | func (p *Plugin) prepareFileConfig(fileCfg *ProtoFileConfig) { 109 | fileCfg.Paths = append(fileCfg.Paths, p.config.Paths...) 110 | 111 | fileCfg.ImportsAliases = append(fileCfg.ImportsAliases, p.config.ImportsAliases...) 112 | } 113 | 114 | func (p *Plugin) PrintInfo(info generator.Infos) { 115 | } 116 | 117 | func (p *Plugin) Infos() map[string]string { 118 | return nil 119 | } 120 | 121 | func (p *Plugin) Prepare() error { 122 | pr := new(Proto2GraphQL) 123 | pr.VendorPath = p.generateConfig.VendorPath 124 | pr.DataLoaderPlugin = p.dataLoaderPlugin 125 | pr.GenerateTracers = p.generateConfig.GenerateTraces 126 | pr.OutputPath = p.config.GetOutputPath() 127 | for _, file := range p.config.Files { 128 | p.prepareFileConfig(file) 129 | 130 | if err := pr.AddSourceByConfig(file); err != nil { 131 | return errors.Wrap(err, "failed to parse file "+file.ProtoPath) 132 | } 133 | } 134 | for _, file := range pr.parser.ParsedFiles() { 135 | pf, err := pr.parsedFile(file) 136 | if err != nil { 137 | return errors.Wrapf(err, "failed to resolve parsed file of '%s'", file.FilePath) 138 | } 139 | 140 | commonFile, err := pr.prepareFile(pf) 141 | if err != nil { 142 | return errors.Wrap(err, "failed to prepare file for generation") 143 | } 144 | p.graphql.AddTypesFile(pf.OutputPath, commonFile) 145 | } 146 | 147 | return nil 148 | } 149 | 150 | func (Plugin) Name() string { 151 | return PluginName 152 | } 153 | 154 | func (Plugin) Generate() error { 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/helpers.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "go/build" 5 | "path/filepath" 6 | "reflect" 7 | "strings" 8 | 9 | "github.com/go-openapi/swag" 10 | "github.com/pkg/errors" 11 | 12 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 13 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql/parser" 14 | ) 15 | 16 | const ( 17 | strFmtPkg = "github.com/go-openapi/strfmt" 18 | timePkg = "time" 19 | ) 20 | 21 | var scalarsGoTypesNames = map[parser.Kind]string{ 22 | parser.KindString: "string", 23 | parser.KindFloat32: "float32", 24 | parser.KindFloat64: "float64", 25 | parser.KindInt64: "int64", 26 | parser.KindInt32: "int32", 27 | parser.KindBoolean: "bool", 28 | parser.KindFile: "File", 29 | } 30 | var scalarsGoTypes = map[parser.Kind]graphql.GoType{ 31 | parser.KindBoolean: {Scalar: true, Kind: reflect.Bool}, 32 | parser.KindFloat64: {Scalar: true, Kind: reflect.Float64}, 33 | parser.KindFloat32: {Scalar: true, Kind: reflect.Float32}, 34 | parser.KindInt64: {Scalar: true, Kind: reflect.Int64}, 35 | parser.KindInt32: {Scalar: true, Kind: reflect.Int32}, 36 | parser.KindString: {Scalar: true, Kind: reflect.String}, 37 | } 38 | 39 | func GoPackageByPath(path, vendorPath string) (string, error) { 40 | path, err := filepath.Abs(path) 41 | if err != nil { 42 | return "", errors.Wrap(err, "failed to resolve absolute filepath") 43 | } 44 | var prefixes []string 45 | if vendorPath != "" { 46 | absVendorPath, err := filepath.Abs(vendorPath) 47 | if err != nil { 48 | return "", errors.Wrap(err, "failed to resolve absolute vendor path") 49 | } 50 | prefixes = append(prefixes, absVendorPath) 51 | } 52 | absGoPath, err := filepath.Abs(build.Default.GOPATH) 53 | if err != nil { 54 | return "", errors.Wrap(err, "failed to resolve absolute gopath") 55 | } 56 | prefixes = append(prefixes, filepath.Join(absGoPath, "src")) 57 | 58 | for _, prefix := range prefixes { 59 | if strings.HasPrefix(path, prefix) { 60 | return strings.TrimLeft(strings.TrimPrefix(path, prefix), " "+string(filepath.Separator)), nil 61 | } 62 | } 63 | return "", errors.Errorf("path '%s' is outside GOPATH or Vendor folder", path) 64 | } 65 | func (p *Plugin) goTypeByParserType(typeFile *parsedFile, typ parser.Type, ptrObj bool) (_ graphql.GoType, err error) { 66 | if typ == parser.ObjDateTime { 67 | t := graphql.GoType{ 68 | Kind: reflect.Struct, 69 | Name: "DateTime", 70 | Pkg: strFmtPkg, 71 | } 72 | if ptrObj { 73 | tcp := t 74 | t = graphql.GoType{ 75 | Kind: reflect.Ptr, 76 | ElemType: &tcp, 77 | } 78 | } 79 | return t, nil 80 | } 81 | switch t := typ.(type) { 82 | case *parser.Scalar: 83 | goTyp, ok := scalarsGoTypes[t.Kind()] 84 | if !ok { 85 | err = errors.Errorf("convertation of scalar %s to golang type is not implemented", typ.Kind()) 86 | return 87 | } 88 | return goTyp, nil 89 | case *parser.Object: 90 | if ptrObj { 91 | return graphql.GoType{ 92 | Kind: reflect.Ptr, 93 | ElemType: &graphql.GoType{ 94 | Kind: reflect.Struct, 95 | Name: pascalize(camelCaseSlice(t.Route)), 96 | Pkg: typeFile.Config.ModelsGoPath, 97 | }, 98 | }, nil 99 | } 100 | return graphql.GoType{ 101 | Kind: reflect.Struct, 102 | Name: pascalize(camelCaseSlice(t.Route)), 103 | Pkg: typeFile.Config.ModelsGoPath, 104 | }, nil 105 | case *parser.Array: 106 | elemGoType, err := p.goTypeByParserType(typeFile, t.ElemType, ptrObj) 107 | if err != nil { 108 | err = errors.Wrap(err, "failed to resolve array element go type") 109 | return graphql.GoType{}, err 110 | } 111 | return graphql.GoType{ 112 | Kind: reflect.Slice, 113 | ElemType: &elemGoType, 114 | }, nil 115 | case *parser.Map: 116 | valueType, err := p.goTypeByParserType(typeFile, t.ElemType, true) 117 | if err != nil { 118 | return graphql.GoType{}, errors.Wrap(err, "failed to resolve map output type") 119 | } 120 | return graphql.GoType{ 121 | Kind: reflect.Map, 122 | ElemType: &graphql.GoType{ 123 | Kind: reflect.String, 124 | }, 125 | Elem2Type: &valueType, 126 | }, nil 127 | } 128 | err = errors.Errorf("unknown type %v", typ.Kind().String()) 129 | return 130 | } 131 | 132 | // camelCaseSlice is like camelCase, but the argument is a slice of strings to 133 | // be joined with "_". 134 | func camelCaseSlice(elem []string) string { return pascalize(strings.Join(elem, "")) } 135 | func snakeCamelCaseSlice(elem []string) string { return pascalize(strings.Join(elem, "_")) } 136 | func pascalize(arg string) string { 137 | arg = strings.NewReplacer(">=", "Ge", "<=", "Le", ">", "Gt", "<", "Lt", "=", "Eq").Replace(arg) 138 | if len(arg) == 0 || arg[0] > '9' { 139 | return swag.ToGoName(arg) 140 | } 141 | if arg[0] == '+' { 142 | return swag.ToGoName("Plus " + arg[1:]) 143 | } 144 | if arg[0] == '-' { 145 | return swag.ToGoName("Minus " + arg[1:]) 146 | } 147 | 148 | return swag.ToGoName("Nr " + arg) 149 | } 150 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/input_object_resolvers.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 10 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql/lib/names" 11 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql/parser" 12 | ) 13 | 14 | func (p *Plugin) objectResolverFunctionName(obj *parser.Object) string { 15 | return "Resolve" + snakeCamelCaseSlice(obj.Route) 16 | } 17 | func (p *Plugin) methodParametersObjectResolverFuncName(method parser.Method) string { 18 | return "Resolve" + pascalize(method.OperationID) + "Params" 19 | } 20 | 21 | func (p *Plugin) methodParametersInputObjectResolver(file *parsedFile, tag string, method parser.Method) (*graphql.InputObjectResolver, error) { 22 | var fields []graphql.InputObjectResolverField 23 | gqlName := p.methodParamsInputObjectGQLName(file, method) 24 | 25 | for _, param := range method.Parameters { 26 | // goTyp, err := p.goTypeByParserType(file, param.Type, true) 27 | // if err != nil { 28 | // return nil, errors.Wrap(err, "failed to resolve parameter go type") 29 | // } 30 | paramGqlName := names.FilterNotSupportedFieldNameCharacters(param.Name) 31 | paramCfg, err := file.Config.FieldConfig(gqlName, param.Name) 32 | 33 | if err != nil { 34 | return nil, errors.Wrap(err, "failed to resolve property config") 35 | } 36 | 37 | valueResolver, withErr, fromArgs, err := p.TypeValueResolver(file, param.Type, !param.Required, paramCfg.ContextKey) 38 | if err != nil { 39 | return nil, errors.Wrap(err, "failed to get parameter value resolver") 40 | } 41 | fields = append(fields, graphql.InputObjectResolverField{ 42 | OutputFieldName: pascalize(param.Name), 43 | GraphQLInputFieldName: paramGqlName, 44 | ValueResolver: valueResolver, 45 | ResolverWithError: withErr, 46 | IsFromArgs: fromArgs, 47 | }) 48 | } 49 | 50 | return &graphql.InputObjectResolver{ 51 | FunctionName: p.methodParametersObjectResolverFuncName(method), 52 | Fields: fields, 53 | OutputGoType: graphql.GoType{ 54 | Kind: reflect.Ptr, 55 | ElemType: &graphql.GoType{ 56 | Kind: reflect.Struct, 57 | Name: pascalize(method.OperationID) + "Params", 58 | Pkg: file.Config.Tags[tag].ClientGoPackage, 59 | }, 60 | }, 61 | }, nil 62 | } 63 | 64 | func (p *Plugin) fileInputMessagesResolvers(file *parsedFile) ([]graphql.InputObjectResolver, error) { 65 | var res []graphql.InputObjectResolver 66 | var handledObjects = map[parser.Type]struct{}{} 67 | var handleType func(typ parser.Type) error 68 | handleType = func(typ parser.Type) error { 69 | switch t := typ.(type) { 70 | case *parser.Array: 71 | return handleType(t.ElemType) 72 | case *parser.Object: 73 | if t == parser.ObjDateTime { 74 | return nil 75 | } 76 | var fields []graphql.InputObjectResolverField 77 | if _, handled := handledObjects[t]; handled { 78 | return nil 79 | } 80 | gqlObjName := p.inputObjectGQLName(file, t) 81 | handledObjects[t] = struct{}{} 82 | for _, property := range t.Properties { 83 | gqlName := names.FilterNotSupportedFieldNameCharacters(property.Name) 84 | 85 | paramCfg, err := file.Config.FieldConfig(gqlObjName, property.Name) 86 | if err != nil { 87 | return errors.Wrapf(err, "failed to resolve property %s config", property.Name) 88 | } 89 | 90 | err = handleType(property.Type) 91 | if err != nil { 92 | return errors.Wrapf(err, "failed to resolve property %s objects resolvers", property.Name) 93 | } 94 | valueResolver, withErr, fromArgs, err := p.TypeValueResolver(file, property.Type, property.Required, paramCfg.ContextKey) 95 | if err != nil { 96 | return errors.Wrap(err, "failed to get property value resolver") 97 | } 98 | fields = append(fields, graphql.InputObjectResolverField{ 99 | GraphQLInputFieldName: gqlName, 100 | OutputFieldName: pascalize(property.Name), 101 | ValueResolver: valueResolver, 102 | ResolverWithError: withErr, 103 | GoType: graphql.GoType{ 104 | Kind: reflect.Uint, 105 | Scalar: true, 106 | }, 107 | IsFromArgs: fromArgs, 108 | }) 109 | } 110 | resGoType, err := p.goTypeByParserType(file, t, true) 111 | if err != nil { 112 | return errors.Wrap(err, "failed to resolve object go type") 113 | } 114 | sort.Slice(fields, func(i, j int) bool { 115 | return fields[i].GraphQLInputFieldName > fields[j].GraphQLInputFieldName 116 | }) 117 | res = append(res, graphql.InputObjectResolver{ 118 | FunctionName: p.objectResolverFunctionName(t), 119 | Fields: fields, 120 | OutputGoType: resGoType, 121 | }) 122 | 123 | } 124 | return nil 125 | } 126 | for _, tag := range file.File.Tags { 127 | _, ok := file.Config.Tags[tag.Name] 128 | 129 | if !ok { 130 | continue 131 | } 132 | 133 | for _, method := range tag.Methods { 134 | paramsResolver, err := p.methodParametersInputObjectResolver(file, tag.Name, method) 135 | if err != nil { 136 | return nil, errors.Wrap(err, "failed to get method partameters input object resolver") 137 | } 138 | res = append(res, *paramsResolver) 139 | for _, parameter := range method.Parameters { 140 | err := handleType(parameter.Type) 141 | if err != nil { 142 | return nil, errors.Wrapf(err, "failed to handle type %v", parameter.Type.Kind()) 143 | } 144 | } 145 | } 146 | } 147 | sort.Slice(res, func(i, j int) bool { 148 | return res[i].FunctionName > res[j].FunctionName 149 | }) 150 | return res, nil 151 | } 152 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/helpers.go: -------------------------------------------------------------------------------- 1 | package proto2gql 2 | 3 | import ( 4 | "go/build" 5 | "path/filepath" 6 | "reflect" 7 | "strings" 8 | 9 | "github.com/pkg/errors" 10 | 11 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 12 | "github.com/EGT-Ukraine/go2gql/generator/plugins/proto2gql/parser" 13 | ) 14 | 15 | var goTypesScalars = map[string]graphql.GoType{ 16 | "double": {Scalar: true, Kind: reflect.Float64}, 17 | "float": {Scalar: true, Kind: reflect.Float32}, 18 | "bool": {Scalar: true, Kind: reflect.Bool}, 19 | "string": {Scalar: true, Kind: reflect.String}, 20 | 21 | "int64": {Scalar: true, Kind: reflect.Int64}, 22 | "sfixed64": {Scalar: true, Kind: reflect.Int64}, 23 | "sint64": {Scalar: true, Kind: reflect.Int64}, 24 | 25 | "int32": {Scalar: true, Kind: reflect.Int32}, 26 | "sfixed32": {Scalar: true, Kind: reflect.Int32}, 27 | "sint32": {Scalar: true, Kind: reflect.Int32}, 28 | 29 | "uint32": {Scalar: true, Kind: reflect.Uint32}, 30 | "fixed32": {Scalar: true, Kind: reflect.Uint32}, 31 | 32 | "uint64": {Scalar: true, Kind: reflect.Uint64}, 33 | "fixed64": {Scalar: true, Kind: reflect.Uint64}, 34 | 35 | "bytes": {Kind: graphql.KindBytes}, 36 | } 37 | 38 | func (g *Proto2GraphQL) goTypeByParserType(typ parser.Type) (_ graphql.GoType, err error) { 39 | switch pType := typ.(type) { 40 | case *parser.Scalar: 41 | res, ok := goTypesScalars[pType.ScalarName] 42 | if !ok { 43 | err = errors.New("unknown scalar") 44 | return 45 | } 46 | return res, nil 47 | case *parser.Map: 48 | keyT, err := g.goTypeByParserType(pType.KeyType) 49 | if err != nil { 50 | return graphql.GoType{}, errors.Wrap(err, "failed to resolve key type") 51 | } 52 | valueT, err := g.goTypeByParserType(pType.ValueType) 53 | if err != nil { 54 | return graphql.GoType{}, errors.Wrap(err, "failed to resolve value type") 55 | } 56 | return graphql.GoType{ 57 | Pkg: pType.File().GoPackage, 58 | Kind: reflect.Map, 59 | ElemType: &keyT, 60 | Elem2Type: &valueT, 61 | }, nil 62 | case *parser.Message: 63 | file, err := g.parsedFile(pType.File()) 64 | if err != nil { 65 | err = errors.Wrap(err, "failed to resolve type parsed file") 66 | return graphql.GoType{}, err 67 | } 68 | msgType := &graphql.GoType{ 69 | Pkg: file.GRPCSourcesPkg, 70 | Name: snakeCamelCaseSlice(pType.TypeName), 71 | Kind: reflect.Struct, 72 | } 73 | return graphql.GoType{ 74 | Pkg: file.GRPCSourcesPkg, 75 | Kind: reflect.Ptr, 76 | ElemType: msgType, 77 | }, nil 78 | 79 | case *parser.Enum: 80 | file, err := g.parsedFile(pType.File()) 81 | if err != nil { 82 | err = errors.Wrap(err, "failed to resolve type parsed file") 83 | return graphql.GoType{}, err 84 | } 85 | return graphql.GoType{ 86 | Pkg: file.GRPCSourcesPkg, 87 | Name: snakeCamelCaseSlice(pType.TypeName), 88 | Kind: reflect.Int32, 89 | }, nil 90 | } 91 | err = errors.Errorf("unknown type " + typ.String()) 92 | return 93 | } 94 | 95 | func GoPackageByPath(path, vendorPath string) (string, error) { 96 | path, err := filepath.Abs(path) 97 | if err != nil { 98 | return "", errors.Wrap(err, "failed to resolve absolute filepath") 99 | } 100 | var prefixes []string 101 | if vendorPath != "" { 102 | absVendorPath, err := filepath.Abs(vendorPath) 103 | if err != nil { 104 | return "", errors.Wrap(err, "failed to resolve absolute vendor path") 105 | } 106 | prefixes = append(prefixes, absVendorPath) 107 | } 108 | absGoPath, err := filepath.Abs(build.Default.GOPATH) 109 | if err != nil { 110 | return "", errors.Wrap(err, "failed to resolve absolute gopath") 111 | } 112 | prefixes = append(prefixes, filepath.Join(absGoPath, "src")) 113 | 114 | for _, prefix := range prefixes { 115 | if strings.HasPrefix(path, prefix) { 116 | return strings.TrimLeft(strings.TrimPrefix(path, prefix), " "+string(filepath.Separator)), nil 117 | } 118 | } 119 | return "", errors.Errorf("path '%s' is outside GOPATH or Vendor folder", path) 120 | } 121 | 122 | // Is c an ASCII lower-case letter? 123 | func isASCIILower(c byte) bool { 124 | return 'a' <= c && c <= 'z' 125 | } 126 | 127 | // Is c an ASCII digit? 128 | func isASCIIDigit(c byte) bool { 129 | return '0' <= c && c <= '9' 130 | } 131 | 132 | func camelCase(s string) string { 133 | if s == "" { 134 | return "" 135 | } 136 | t := make([]byte, 0, 32) 137 | i := 0 138 | if s[0] == '_' { 139 | // Need a capital letter; drop the '_'. 140 | t = append(t, 'X') 141 | i++ 142 | } 143 | // Invariant: if the next letter is lower case, it must be converted 144 | // to upper case. 145 | // That is, we process a word at a time, where words are marked by _ or 146 | // upper case letter. Digits are treated as words. 147 | for ; i < len(s); i++ { 148 | c := s[i] 149 | if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { 150 | continue // Skip the underscore in s. 151 | } 152 | if isASCIIDigit(c) { 153 | t = append(t, c) 154 | continue 155 | } 156 | // Assume we have a letter now - if not, it's a bogus identifier. 157 | // The next word is a sequence of characters that must start upper case. 158 | if isASCIILower(c) { 159 | c ^= ' ' // Make it a capital letter. 160 | } 161 | t = append(t, c) // Guaranteed not lower case. 162 | // Accept lower case sequence that follows. 163 | for i+1 < len(s) && isASCIILower(s[i+1]) { 164 | i++ 165 | t = append(t, s[i]) 166 | } 167 | } 168 | return string(t) 169 | } 170 | 171 | // camelCaseSlice is like camelCase, but the argument is a slice of strings to 172 | // be joined with "_". 173 | func camelCaseSlice(elem []string) string { return camelCase(strings.Join(elem, "")) } 174 | func snakeCamelCaseSlice(elem []string) string { return camelCase(strings.Join(elem, "_")) } 175 | func dotedTypeName(elems []string) string { return camelCase(strings.Join(elems, ".")) } 176 | -------------------------------------------------------------------------------- /generator/plugins/swagger2gql/output_object.go: -------------------------------------------------------------------------------- 1 | package swagger2gql 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/pkg/errors" 9 | 10 | "github.com/EGT-Ukraine/go2gql/generator/plugins/dataloader" 11 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql" 12 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql/lib/names" 13 | "github.com/EGT-Ukraine/go2gql/generator/plugins/swagger2gql/parser" 14 | ) 15 | 16 | func (p *Plugin) outputObjectGQLName(messageFile *parsedFile, obj *parser.Object) string { 17 | return messageFile.Config.GetGQLMessagePrefix() + pascalize(strings.Join(obj.Route, "__")) 18 | } 19 | func (p *Plugin) outputObjectVariable(messageFile *parsedFile, obj *parser.Object) string { 20 | return messageFile.Config.GetGQLMessagePrefix() + pascalize(strings.Join(obj.Route, "")) 21 | } 22 | 23 | func (p *Plugin) outputMessageTypeResolver(messageFile *parsedFile, obj *parser.Object) graphql.TypeResolver { 24 | if len(obj.Properties) == 0 { 25 | return graphql.GqlNoDataTypeResolver 26 | } 27 | return func(ctx graphql.BodyContext) string { 28 | return ctx.Importer.Prefix(messageFile.OutputPkg) + p.outputObjectVariable(messageFile, obj) 29 | } 30 | } 31 | 32 | func (p *Plugin) fileOutputMessages(file *parsedFile) ([]graphql.OutputObject, error) { 33 | var res []graphql.OutputObject 34 | handledObjects := map[parser.Type]struct{}{} 35 | var handleType func(typ parser.Type) error 36 | handleType = func(typ parser.Type) error { 37 | switch t := typ.(type) { 38 | case *parser.Object: 39 | if _, handled := handledObjects[t]; handled { 40 | return nil 41 | } 42 | handledObjects[t] = struct{}{} 43 | for _, property := range t.Properties { 44 | if err := handleType(property.Type); err != nil { 45 | return errors.Wrapf(err, "failed to handle object property %s type", property.Name) 46 | } 47 | } 48 | goTyp, err := p.goTypeByParserType(file, t, false) 49 | if err != nil { 50 | return errors.Wrap(err, "failed to resolve object go type") 51 | } 52 | var fields []graphql.ObjectField 53 | var mapFields []graphql.ObjectField 54 | for _, prop := range t.Properties { 55 | tr, err := p.TypeOutputTypeResolver(file, prop.Type, false) 56 | if err != nil { 57 | return errors.Wrap(err, "failed to resolve property output type resolver") 58 | } 59 | valueResolver := graphql.IdentAccessValueResolver(pascalize(prop.Name)) 60 | if typ == parser.ObjDateTime { 61 | switch prop.Name { 62 | case "seconds": 63 | valueResolver = func(arg string, ctx graphql.BodyContext) string { 64 | return `(time.Time)(` + arg + `).Unix()` 65 | } 66 | case "nanos": 67 | valueResolver = func(arg string, ctx graphql.BodyContext) string { 68 | return `int32((time.Time)(` + arg + `).Nanosecond())` 69 | } 70 | } 71 | 72 | } 73 | 74 | propGoType, err := p.goTypeByParserType(file, prop.Type, false) 75 | if err != nil { 76 | return errors.Wrap(err, "failed to resolve property go type") 77 | } 78 | 79 | propObj := graphql.ObjectField{ 80 | Name: names.FilterNotSupportedFieldNameCharacters(prop.Name), 81 | QuotedComment: strconv.Quote(prop.Description), 82 | Type: tr, 83 | Value: valueResolver, 84 | NeedCast: false, 85 | GoType: propGoType, 86 | } 87 | if prop.Type.Kind() == parser.KindMap { 88 | mapFields = append(mapFields, propObj) 89 | 90 | } else { 91 | fields = append(fields, propObj) 92 | } 93 | } 94 | sort.Slice(fields, func(i, j int) bool { 95 | return fields[i].Name > fields[j].Name 96 | }) 97 | 98 | objectName := p.outputObjectGQLName(file, t) 99 | objectConfig, err := file.Config.ObjectConfig(objectName) 100 | 101 | if err != nil { 102 | return errors.Wrap(err, "failed to get object config "+objectName) 103 | } 104 | 105 | dataLoaderFields, err := p.dataLoaderFields(objectConfig.DataLoaders, t) 106 | if err != nil { 107 | return errors.Wrapf(err, "failed to resolve output object %s data loaders", objectName) 108 | } 109 | 110 | res = append(res, graphql.OutputObject{ 111 | VariableName: p.outputObjectVariable(file, t), 112 | GraphQLName: p.outputObjectGQLName(file, t), 113 | GoType: goTyp, 114 | Fields: fields, 115 | MapFields: mapFields, 116 | DataLoaderFields: dataLoaderFields, 117 | }) 118 | case *parser.Array: 119 | return handleType(t.ElemType) 120 | } 121 | return nil 122 | } 123 | for _, tag := range file.File.Tags { 124 | for _, method := range tag.Methods { 125 | for _, resp := range method.Responses { 126 | if err := handleType(resp.ResultType); err != nil { 127 | return nil, errors.Wrapf(err, "failed to handle %s method %d response", method.OperationID, resp.StatusCode) 128 | } 129 | 130 | } 131 | } 132 | } 133 | sort.Slice(res, func(i, j int) bool { 134 | return res[i].VariableName > res[j].VariableName 135 | }) 136 | return res, nil 137 | } 138 | 139 | func (p *Plugin) dataLoaderFields(configs []dataloader.FieldConfig, object *parser.Object) ([]*graphql.DataLoaderField, error) { 140 | var fields []*graphql.DataLoaderField 141 | 142 | for _, cfg := range configs { 143 | prop := object.GetPropertyByName(cfg.KeyFieldName) 144 | 145 | if prop == nil { 146 | return nil, errors.Errorf("Can't find property %s for dataloader", cfg.KeyFieldName) 147 | } 148 | 149 | field := &graphql.DataLoaderField{ 150 | Name: cfg.FieldName, 151 | ParentKeyFieldName: cfg.KeyFieldName, 152 | KeyFieldSlice: prop.Type.Kind() == parser.KindArray, 153 | NormalizedParentKeyFieldName: pascalize(cfg.KeyFieldName), 154 | DataLoaderName: cfg.DataLoader, 155 | } 156 | 157 | fields = append(fields, field) 158 | } 159 | 160 | return fields, nil 161 | } 162 | -------------------------------------------------------------------------------- /generator/plugins/graphql/templates/types_service.gohtml: -------------------------------------------------------------------------------- 1 | {{- /*gotype: github.com/EGT-Ukraine/go2gql/generator/plugins/graphql.ServiceContext*/ -}} 2 | func Get{{.Service.Name}}Service{{.FieldType}}Methods(c {{goType .Service.CallInterface}}, ih *{{interceptorsPkg}}.InterceptorHandler {{ if $.TracerEnabled }} ,tr {{opentracingPkg}}.Tracer {{end}}) {{gqlPkg}}.Fields { 3 | {{ if .ServiceMethods -}} 4 | return {{gqlPkg}}.Fields{ 5 | {{range $method := .ServiceMethods -}} 6 | "{{$method.Name}}": &{{gqlPkg}}.Field{ 7 | Name: "{{$method.Name}}", 8 | Description: {{$method.QuotedComment}}, 9 | Type: {{call $method.GraphQLOutputType $.BodyContext}}, 10 | {{ if $method.Arguments -}} 11 | Args: {{gqlPkg}}.FieldConfigArgument{ 12 | {{ range $arg := $method.Arguments -}} 13 | "{{$arg.Name}}": &{{gqlPkg}}.ArgumentConfig{Type: {{call $arg.Type $.BodyContext}}, Description: {{$arg.QuotedComment}}}, 14 | {{ end -}} 15 | }, 16 | {{ end -}} 17 | Resolve: func(p {{gqlPkg}}.ResolveParams) (_ interface{}, rerr error) { 18 | ctx := p.Context 19 | _ = ctx 20 | {{ if $.TracerEnabled -}} 21 | var parentSpanCtx {{opentracingPkg}}.SpanContext 22 | if parent := {{opentracingPkg}}.SpanFromContext(p.Context); parent != nil { 23 | parentSpanCtx = parent.Context() 24 | } 25 | span := tr.StartSpan("{{$.Service.Name}}.{{$method.Name}} Resolver", {{opentracingPkg}}.ChildOf(parentSpanCtx)) 26 | spanContext := {{opentracingPkg}}.ContextWithSpan(p.Context, span) 27 | defer span.Finish() 28 | p.Context = spanContext 29 | ctx = spanContext 30 | defer func(){ 31 | if rerr != nil { 32 | span.SetTag("error", true).LogFields({{logPkg}}.Error(rerr)) 33 | } 34 | }() 35 | {{end -}} 36 | if ih == nil { 37 | {{ if $method.RequestResolver -}} 38 | req, err := {{call $method.RequestResolver "p.Args" $.BodyContext}}{{- if not $method.RequestResolverWithErr -}}, error(nil){{end}} 39 | if err != nil { 40 | return nil, err 41 | } 42 | return {{call $method.ClientMethodCaller "c" "req" $.BodyContext}} 43 | {{ else -}} 44 | req := new({{goTypeForNew $method.RequestType}}) 45 | return {{call $method.ClientMethodCaller "c" "req" $.BodyContext}} 46 | {{ end -}} 47 | } 48 | ictx := &{{interceptorsPkg}}.Context{ 49 | Service: "{{$.Service.Name}}", 50 | Method: "{{$method.Name}}", 51 | Params: p, 52 | } 53 | req, err := ih.ResolveArgs(ictx, func(ictx *{{interceptorsPkg}}.Context, next {{interceptorsPkg}}.ResolveArgsInvoker) (result interface{}, err error) { 54 | ctx := ictx.Params.Context 55 | _ = ctx 56 | {{ if $method.RequestResolver -}} 57 | return {{call $method.RequestResolver "p.Args" $.BodyContext}}{{- if not $method.RequestResolverWithErr -}}, error(nil){{end}} 58 | {{ else -}} 59 | return new({{goTypeForNew $method.RequestType}}), nil 60 | {{ end -}} 61 | }) 62 | if err != nil { 63 | return nil, {{errorsPkg}}.Wrap(err, "failed to resolve args") 64 | } 65 | return ih.Call(ictx, req, func(ictx *{{interceptorsPkg}}.Context, req interface{}, next {{interceptorsPkg}}.CallMethodInvoker) (result interface{}, err error) { 66 | r, ok := req.({{goType $method.RequestType}}) 67 | if !ok { 68 | return nil, {{errorsPkg}}.New({{fmtPkg}}.Sprintf("Resolve args interceptor returns bad request type(%T). Should be: {{goType $method.RequestType}}", req)) 69 | } 70 | ctx = ictx.Params.Context 71 | {{if $method.PayloadErrorChecker -}} 72 | res, err := {{call $method.ClientMethodCaller "c" "r" $.BodyContext}} 73 | if err != nil { 74 | return nil, err 75 | } 76 | if {{call $method.PayloadErrorChecker "res"}} { 77 | {{ if $method.PayloadErrorAccessor -}} 78 | ictx.PayloadError = {{call $method.PayloadErrorAccessor "res"}} 79 | {{ else -}} 80 | ictx.PayloadError = res 81 | {{ end -}} 82 | } 83 | return res, err 84 | {{else -}} 85 | return {{call $method.ClientMethodCaller "c" "r" $.BodyContext}} 86 | {{end -}} 87 | }) 88 | }, 89 | }, 90 | {{ end -}} 91 | } 92 | {{else -}} 93 | return nil 94 | {{end -}} 95 | } 96 | -------------------------------------------------------------------------------- /generator/plugins/proto2gql/config.go: -------------------------------------------------------------------------------- 1 | package proto2gql 2 | 3 | import ( 4 | "regexp" 5 | "time" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/EGT-Ukraine/go2gql/generator/plugins/dataloader" 10 | ) 11 | 12 | const ( 13 | RequestTypeQuery = "QUERY" 14 | RequestTypeMutation = "MUTATION" 15 | ) 16 | 17 | const ( 18 | DataLoaderType1ToN = "1-N" 19 | DataLoaderType1To1 = "1-1" 20 | ) 21 | 22 | type FieldsConfig struct { 23 | ContextKey string `mapstructure:"context_key"` 24 | } 25 | 26 | type MessageConfig struct { 27 | ErrorField string `mapstructure:"error_field"` 28 | Fields map[string]FieldsConfig `mapstructure:"fields"` 29 | DataLoaders []dataloader.FieldConfig `mapstructure:"data_loaders"` 30 | UnwrapField bool `mapstructure:"unwrap_field"` 31 | } 32 | 33 | type MethodConfig struct { 34 | Alias string `mapstructure:"alias"` 35 | RequestType string `mapstructure:"request_type"` // QUERY | MUTATION 36 | DataLoaderProvider map[string]DataLoaderConfig `mapstructure:"data_loaders"` 37 | } 38 | 39 | type DataLoaderConfig struct { 40 | RequestField string `mapstructure:"request_field"` 41 | ResultField string `mapstructure:"result_field"` 42 | MatchField string `mapstructure:"match_field"` 43 | Type string `mapstructure:"type"` 44 | WaitDuration time.Duration `mapstructure:"wait_duration"` 45 | } 46 | 47 | type ServiceConfig struct { 48 | ServiceName string `mapstructure:"service_name"` 49 | Methods map[string]MethodConfig `mapstructure:"methods"` 50 | } 51 | 52 | type Config struct { 53 | Files []*ProtoFileConfig `mapstructure:"files"` 54 | 55 | // Global configs for proto files 56 | Paths []string `mapstructure:"paths"` 57 | OutputPath string `mapstructure:"output_path"` 58 | ImportsAliases []map[string]string `mapstructure:"imports_aliases"` 59 | Messages []map[string]MessageConfig `mapstructure:"messages"` 60 | } 61 | 62 | func (c *Config) GetOutputPath() string { 63 | if c == nil { 64 | return "" 65 | } 66 | 67 | return c.OutputPath 68 | } 69 | 70 | type ProtoFileConfig struct { 71 | Name string `mapstructure:"name"` 72 | 73 | Paths []string `mapstructure:"paths"` 74 | ImportsAliases []map[string]string `mapstructure:"imports_aliases"` 75 | 76 | ProtoPath string `mapstructure:"proto_path"` 77 | 78 | OutputPkg string `mapstructure:"output_package"` 79 | OutputPath string `mapstructure:"output_path"` 80 | 81 | ProtoGoPackage string `mapstructure:"proto_go_package"` // go package of protoc generated code 82 | 83 | GQLEnumsPrefix string `mapstructure:"gql_enums_prefix"` 84 | GQLMessagePrefix string `mapstructure:"gql_messages_prefix"` 85 | 86 | Services map[string]ServiceConfig `mapstructure:"services"` 87 | Messages []map[string]MessageConfig `mapstructure:"messages"` 88 | } 89 | 90 | func (pc *ProtoFileConfig) MessageConfig(msgName string) (MessageConfig, error) { 91 | if pc == nil { 92 | return MessageConfig{}, nil 93 | } 94 | for _, cfgs := range pc.Messages { 95 | for msgNameRegex, cfg := range cfgs { 96 | r, err := regexp.Compile(msgNameRegex) 97 | if err != nil { 98 | return MessageConfig{}, errors.Wrapf(err, "failed to compile message name regex '%s'", msgNameRegex) 99 | } 100 | if r.MatchString(msgName) { 101 | return cfg, nil 102 | } 103 | } 104 | } 105 | 106 | return MessageConfig{}, nil 107 | } 108 | 109 | func (pc *ProtoFileConfig) GetName() string { 110 | if pc == nil { 111 | return "" 112 | } 113 | 114 | return pc.Name 115 | } 116 | 117 | func (pc *ProtoFileConfig) GetPaths() []string { 118 | if pc == nil { 119 | return []string{} 120 | } 121 | 122 | return pc.Paths 123 | } 124 | 125 | func (pc *ProtoFileConfig) GetProtoPath() string { 126 | if pc == nil { 127 | return "" 128 | } 129 | 130 | return pc.ProtoPath 131 | } 132 | 133 | func (pc *ProtoFileConfig) GetOutputPkg() string { 134 | if pc == nil { 135 | return "" 136 | } 137 | 138 | return pc.OutputPkg 139 | } 140 | 141 | func (pc *ProtoFileConfig) GetGoPackage() string { 142 | if pc == nil { 143 | return "" 144 | } 145 | 146 | return pc.ProtoGoPackage 147 | } 148 | 149 | func (pc *ProtoFileConfig) GetOutputPath() string { 150 | if pc == nil { 151 | return "" 152 | } 153 | 154 | return pc.OutputPath 155 | } 156 | 157 | func (pc *ProtoFileConfig) GetGQLEnumsPrefix() string { 158 | if pc == nil { 159 | return "" 160 | } 161 | 162 | return pc.GQLEnumsPrefix 163 | } 164 | 165 | func (pc *ProtoFileConfig) GetGQLMessagePrefix() string { 166 | if pc == nil { 167 | return "" 168 | } 169 | 170 | return pc.GQLMessagePrefix 171 | } 172 | 173 | func (pc *ProtoFileConfig) GetImportsAliases() []map[string]string { 174 | if pc == nil { 175 | return []map[string]string{} 176 | } 177 | 178 | return pc.ImportsAliases 179 | } 180 | 181 | func (pc *ProtoFileConfig) GetServices() map[string]ServiceConfig { 182 | if pc == nil { 183 | return map[string]ServiceConfig{} 184 | } 185 | 186 | return pc.Services 187 | } 188 | 189 | func (pc *ProtoFileConfig) GetMessages() []map[string]MessageConfig { 190 | if pc == nil { 191 | return []map[string]MessageConfig{} 192 | } 193 | 194 | return pc.Messages 195 | } 196 | 197 | type SchemaNodeConfig struct { 198 | Type string `mapstructure:"type"` // "OBJECT|SERVICE" 199 | Proto string `mapstructure:"proto"` 200 | Service string `mapstructure:"service"` 201 | ObjectName string `mapstructure:"object_name"` 202 | Field string `mapstructure:"field"` 203 | Fields []SchemaNodeConfig `mapstructure:"fields"` 204 | ExcludeMethods []string `mapstructure:"exclude_methods"` 205 | FilterMethods []string `mapstructure:"filter_methods"` 206 | } 207 | 208 | type SchemaConfig struct { 209 | Name string `mapstructure:"name"` 210 | OutputPath string `mapstructure:"output_path"` 211 | OutputPackage string `mapstructure:"output_package"` 212 | Queries *SchemaNodeConfig `mapstructure:"queries"` 213 | Mutations *SchemaNodeConfig `mapstructure:"mutations"` 214 | } 215 | 216 | type GenerateConfig struct { 217 | Tracer bool 218 | VendorPath string 219 | } 220 | -------------------------------------------------------------------------------- /generator/plugins/graphql/schemas_generator.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "reflect" 10 | "strings" 11 | "text/template" 12 | 13 | "github.com/pkg/errors" 14 | "golang.org/x/tools/imports" 15 | 16 | "github.com/EGT-Ukraine/go2gql/generator/plugins/graphql/lib/importer" 17 | ) 18 | 19 | type schemaGenerator struct { 20 | tracerEnabled bool 21 | schemaCfg SchemaConfig 22 | goPkg string 23 | parser *schemaParser 24 | imports *importer.Importer 25 | } 26 | 27 | func (g schemaGenerator) importFunc(importPath string) func() string { 28 | return func() string { 29 | return g.imports.New(importPath) 30 | } 31 | } 32 | 33 | func (g schemaGenerator) bodyTemplateContext() (interface{}, error) { 34 | schemaObjects, err := g.parser.SchemaObjects() 35 | 36 | if err != nil { 37 | return nil, errors.Wrap(err, "failed to resolve objects to generate") 38 | } 39 | return SchemaBodyContext{ 40 | File: g.schemaCfg, 41 | Importer: g.imports, 42 | SchemaName: g.schemaCfg.Name, 43 | QueryObject: schemaObjects.QueryObject, 44 | MutationObject: schemaObjects.MutationObject, 45 | Objects: schemaObjects.Objects, 46 | Services: schemaObjects.Services, 47 | TracerEnabled: g.tracerEnabled, 48 | }, nil 49 | 50 | } 51 | func (g schemaGenerator) goTypeStr(typ GoType) string { 52 | return typ.String(g.imports) 53 | } 54 | 55 | func (g schemaGenerator) goTypeForNew(typ GoType) string { 56 | switch typ.Kind { 57 | case reflect.Ptr: 58 | return g.goTypeStr(*typ.ElemType) 59 | case reflect.Struct: 60 | return g.imports.Prefix(typ.Pkg) + typ.Name 61 | } 62 | panic("type " + typ.Kind.String() + " is not supported") 63 | } 64 | 65 | func (g schemaGenerator) bodyTemplateFuncs() map[string]interface{} { 66 | return map[string]interface{}{ 67 | "ctxPkg": g.importFunc("context"), 68 | "debugPkg": g.importFunc("runtime/debug"), 69 | "fmtPkg": g.importFunc("fmt"), 70 | "errorsPkg": g.importFunc(ErrorsPkgPath), 71 | "gqlPkg": g.importFunc(GraphqlPkgPath), 72 | "scalarsPkg": g.importFunc(ScalarsPkgPath), 73 | "interceptorsPkg": g.importFunc(InterceptorsPkgPath), 74 | "opentracingPkg": g.importFunc(OpentracingPkgPath), 75 | "concat": func(st ...string) string { 76 | return strings.Join(st, "") 77 | }, 78 | "isArray": func(typ GoType) bool { 79 | return typ.Kind == reflect.Slice 80 | }, 81 | "goType": g.goTypeStr, 82 | "goTypeForNew": g.goTypeForNew, 83 | 84 | "serviceConstructor": func(filedType string, service SchemaService, ctx SchemaBodyContext) string { 85 | return ctx.Importer.Prefix(service.Pkg) + "Get" + service.Name + "Service" + filedType + "Methods" 86 | }, 87 | } 88 | } 89 | 90 | func (g schemaGenerator) headTemplateContext() map[string]interface{} { 91 | return map[string]interface{}{ 92 | "imports": g.imports.Imports(), 93 | "package": g.schemaCfg.OutputPackage, 94 | } 95 | 96 | } 97 | func (g schemaGenerator) headTemplateFuncs() map[string]interface{} { 98 | return nil 99 | } 100 | func (g schemaGenerator) generateBody() ([]byte, error) { 101 | buf := new(bytes.Buffer) 102 | tmpl, err := templatesSchemas_bodyGohtmlBytes() 103 | if err != nil { 104 | return nil, errors.Wrap(err, "failed to get head template") 105 | } 106 | bodyTpl, err := template.New("body").Funcs(g.bodyTemplateFuncs()).Parse(string(tmpl)) 107 | if err != nil { 108 | return nil, errors.Wrap(err, "failed to parse template") 109 | } 110 | bodyCtx, err := g.bodyTemplateContext() 111 | if err != nil { 112 | return nil, errors.Wrap(err, "failed to prepare body context") 113 | } 114 | err = bodyTpl.Execute(buf, bodyCtx) 115 | if err != nil { 116 | return nil, errors.Wrap(err, "failed to execute template") 117 | } 118 | return buf.Bytes(), nil 119 | } 120 | 121 | func (g schemaGenerator) generateHead() ([]byte, error) { 122 | buf := new(bytes.Buffer) 123 | tmpl, err := templatesSchemas_headGohtmlBytes() 124 | if err != nil { 125 | return nil, errors.Wrap(err, "failed to get head template") 126 | } 127 | bodyTpl, err := template.New("head").Funcs(g.headTemplateFuncs()).Parse(string(tmpl)) 128 | if err != nil { 129 | return nil, errors.Wrap(err, "failed to parse template") 130 | } 131 | err = bodyTpl.Execute(buf, g.headTemplateContext()) 132 | if err != nil { 133 | return nil, errors.Wrap(err, "failed to execute template") 134 | } 135 | return buf.Bytes(), nil 136 | } 137 | func (g schemaGenerator) generate(out io.Writer) error { 138 | body, err := g.generateBody() 139 | if err != nil { 140 | return errors.Wrap(err, "failed to generate body") 141 | } 142 | head, err := g.generateHead() 143 | if err != nil { 144 | return errors.Wrap(err, "failed to generate head") 145 | } 146 | r := bytes.Join([][]byte{ 147 | head, 148 | body, 149 | }, nil) 150 | 151 | res, err := imports.Process("file", r, &imports.Options{ 152 | Comments: true, 153 | }) 154 | // TODO: fix this 155 | if err != nil { 156 | fmt.Fprintln(os.Stderr, err.Error()) 157 | } else { 158 | r = res 159 | } 160 | _, err = out.Write(r) 161 | if err != nil { 162 | return errors.Wrap(err, "failed to write output") 163 | } 164 | return nil 165 | } 166 | 167 | func (p *Plugin) generateSchemas() error { 168 | for _, schema := range p.schemaConfigs { 169 | pkg, err := GoPackageByPath(filepath.Dir(schema.OutputPath), p.generateCfg.VendorPath) 170 | if err != nil { 171 | return errors.Wrapf(err, "failed to resolve schema %s output go package", schema.Name) 172 | } 173 | 174 | parser := newSchemaParser(schema, p.files) 175 | 176 | g := schemaGenerator{ 177 | parser: parser, 178 | tracerEnabled: p.generateCfg.GenerateTraces, 179 | schemaCfg: schema, 180 | goPkg: pkg, 181 | imports: &importer.Importer{ 182 | CurrentPackage: pkg, 183 | }, 184 | } 185 | file, err := os.OpenFile(schema.OutputPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) 186 | if err != nil { 187 | return errors.Wrapf(err, "failed to open schema %s output file for write", schema.OutputPath) 188 | } 189 | err = g.generate(file) 190 | if err != nil { 191 | if cerr := file.Close(); cerr != nil { 192 | err = errors.Wrap(err, cerr.Error()) 193 | } 194 | return errors.Wrapf(err, "failed to generate types file %s", schema.OutputPath) 195 | } 196 | if file.Close(); err != nil { 197 | return errors.Wrapf(err, "failed to close generated schema %s file", schema.OutputPath) 198 | } 199 | } 200 | return nil 201 | } 202 | --------------------------------------------------------------------------------