From b1124d635022da8c0c1a6d5305779ab34d2bef9b Mon Sep 17 00:00:00 2001 From: Joakim Olsson Date: Fri, 14 Oct 2022 22:41:56 +0200 Subject: [PATCH] chore: handle push of unchanged schema --- .gitignore | 1 + cache/cache.go | 27 +- ctl/SubGraphs.graphql | 16 +- ctl/ctl.go | 23 +- ctl/generated.go | 203 +++++++++++-- graph/generated/generated.go | 561 ++++++++++++++++++++++++++++++++++- graph/model/models_gen.go | 19 ++ graph/schema.graphqls | 16 +- graph/schema.helpers.go | 13 + graph/schema.resolvers.go | 48 ++- 10 files changed, 866 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index 0626e33..1b3cf73 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ coverage.html /exported /release +/schemactl diff --git a/cache/cache.go b/cache/cache.go index 076a7b5..6de4796 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -2,6 +2,7 @@ package cache import ( "fmt" + "time" "github.com/apex/log" "github.com/sparetimecoders/goamqp" @@ -12,17 +13,20 @@ import ( const subGraphKey = "%s<->%s" type Cache struct { - services map[string]map[string]struct{} - subGraphs map[string]string - logger log.Interface + services map[string]map[string]struct{} + subGraphs map[string]string + lastUpdate map[string]string + logger log.Interface } -func (c *Cache) Services(ref string) []string { +func (c *Cache) Services(ref, lastUpdate string) ([]string, string) { var services []string - for k := range c.services[ref] { - services = append(services, k) + if lastUpdate == "" || c.lastUpdate[ref] > lastUpdate { + for k := range c.services[ref] { + services = append(services, k) + } } - return services + return services, c.lastUpdate[ref] } func (c *Cache) SubGraphId(ref, service string) string { @@ -37,12 +41,14 @@ func (c *Cache) Update(msg any, _ goamqp.Headers) (any, error) { } c.services[m.Ref][m.ID.String()] = struct{}{} c.subGraphs[fmt.Sprintf(subGraphKey, m.Ref, m.Service)] = m.ID.String() + c.lastUpdate[m.Ref] = m.Time.Format(time.RFC3339Nano) case *domain.SubGraph: if _, exists := c.services[m.Ref]; !exists { c.services[m.Ref] = make(map[string]struct{}) } c.services[m.Ref][m.ID.String()] = struct{}{} c.subGraphs[fmt.Sprintf(subGraphKey, m.Ref, m.Service)] = m.ID.String() + c.lastUpdate[m.Ref] = m.ChangedAt.Format(time.RFC3339Nano) default: c.logger.Warnf("unexpected message received: %+v", msg) } @@ -51,8 +57,9 @@ func (c *Cache) Update(msg any, _ goamqp.Headers) (any, error) { func New(logger log.Interface) *Cache { return &Cache{ - subGraphs: make(map[string]string), - services: make(map[string]map[string]struct{}), - logger: logger, + subGraphs: make(map[string]string), + services: make(map[string]map[string]struct{}), + lastUpdate: make(map[string]string), + logger: logger, } } diff --git a/ctl/SubGraphs.graphql b/ctl/SubGraphs.graphql index e5e53fd..8be050e 100644 --- a/ctl/SubGraphs.graphql +++ b/ctl/SubGraphs.graphql @@ -1,9 +1,13 @@ query SubGraphs($ref: String!) { - subGraphs(ref: $ref) { - service - url - wsUrl - changedBy - changedAt + supergraph(ref: $ref) { + ... on SubGraphs { + subGraphs { + service + url + wsUrl + changedBy + changedAt + } + } } } diff --git a/ctl/ctl.go b/ctl/ctl.go index 60c0ca1..6aa6af0 100644 --- a/ctl/ctl.go +++ b/ctl/ctl.go @@ -2,6 +2,7 @@ package ctl import ( "context" + "fmt" "net/http" "net/url" @@ -47,15 +48,19 @@ func List(apiKey, schemaRef, service string, schemasUrl url.URL) ([]*SubGraph, e if err != nil { return nil, err } - subGraphs := make([]*SubGraph, len(response.SubGraphs)) - for i, subGraph := range response.SubGraphs { - subGraphs[i] = &SubGraph{ - Service: subGraph.Service, - URL: subGraph.Url, - WSUrl: subGraph.WsUrl, - ChangedBy: subGraph.ChangedBy, - ChangedAt: subGraph.ChangedAt, + var subGraphs []*SubGraph + if s, ok := response.Supergraph.(*SubGraphsSupergraphSubGraphs); ok { + subGraphs = make([]*SubGraph, len(s.SubGraphs)) + for i, subGraph := range s.SubGraphs { + subGraphs[i] = &SubGraph{ + Service: subGraph.Service, + URL: subGraph.Url, + WSUrl: subGraph.WsUrl, + ChangedBy: subGraph.ChangedBy, + ChangedAt: subGraph.ChangedAt, + } } + return subGraphs, nil } - return subGraphs, nil + return nil, fmt.Errorf("unexpected response type") } diff --git a/ctl/generated.go b/ctl/generated.go index 60c3386..14f5dfc 100644 --- a/ctl/generated.go +++ b/ctl/generated.go @@ -4,6 +4,8 @@ package ctl import ( "context" + "encoding/json" + "fmt" "time" "github.com/Khan/genqlient/graphql" @@ -34,14 +36,160 @@ func (v *InputSubGraph) GetSdl() string { return v.Sdl } // SubGraphsResponse is returned by SubGraphs on success. type SubGraphsResponse struct { - SubGraphs []*SubGraphsSubGraphsSubGraph `json:"subGraphs"` + Supergraph SubGraphsSupergraph `json:"-"` } -// GetSubGraphs returns SubGraphsResponse.SubGraphs, and is useful for accessing the field via an interface. -func (v *SubGraphsResponse) GetSubGraphs() []*SubGraphsSubGraphsSubGraph { return v.SubGraphs } +// GetSupergraph returns SubGraphsResponse.Supergraph, and is useful for accessing the field via an interface. +func (v *SubGraphsResponse) GetSupergraph() SubGraphsSupergraph { return v.Supergraph } -// SubGraphsSubGraphsSubGraph includes the requested fields of the GraphQL type SubGraph. -type SubGraphsSubGraphsSubGraph struct { +func (v *SubGraphsResponse) UnmarshalJSON(b []byte) error { + if string(b) == "null" { + return nil + } + + var firstPass struct { + *SubGraphsResponse + Supergraph json.RawMessage `json:"supergraph"` + graphql.NoUnmarshalJSON + } + firstPass.SubGraphsResponse = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + dst := &v.Supergraph + src := firstPass.Supergraph + if len(src) != 0 && string(src) != "null" { + err = __unmarshalSubGraphsSupergraph( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal SubGraphsResponse.Supergraph: %w", err) + } + } + } + return nil +} + +type __premarshalSubGraphsResponse struct { + Supergraph json.RawMessage `json:"supergraph"` +} + +func (v *SubGraphsResponse) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *SubGraphsResponse) __premarshalJSON() (*__premarshalSubGraphsResponse, error) { + var retval __premarshalSubGraphsResponse + + { + + dst := &retval.Supergraph + src := v.Supergraph + var err error + *dst, err = __marshalSubGraphsSupergraph( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal SubGraphsResponse.Supergraph: %w", err) + } + } + return &retval, nil +} + +// SubGraphsSupergraph includes the requested fields of the GraphQL interface Supergraph. +// +// SubGraphsSupergraph is implemented by the following types: +// SubGraphsSupergraphSubGraphs +// SubGraphsSupergraphUnchanged +type SubGraphsSupergraph interface { + implementsGraphQLInterfaceSubGraphsSupergraph() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() *string +} + +func (v *SubGraphsSupergraphSubGraphs) implementsGraphQLInterfaceSubGraphsSupergraph() {} +func (v *SubGraphsSupergraphUnchanged) implementsGraphQLInterfaceSubGraphsSupergraph() {} + +func __unmarshalSubGraphsSupergraph(b []byte, v *SubGraphsSupergraph) error { + if string(b) == "null" { + return nil + } + + var tn struct { + TypeName string `json:"__typename"` + } + err := json.Unmarshal(b, &tn) + if err != nil { + return err + } + + switch tn.TypeName { + case "SubGraphs": + *v = new(SubGraphsSupergraphSubGraphs) + return json.Unmarshal(b, *v) + case "Unchanged": + *v = new(SubGraphsSupergraphUnchanged) + return json.Unmarshal(b, *v) + case "": + return fmt.Errorf( + "response was missing Supergraph.__typename") + default: + return fmt.Errorf( + `unexpected concrete type for SubGraphsSupergraph: "%v"`, tn.TypeName) + } +} + +func __marshalSubGraphsSupergraph(v *SubGraphsSupergraph) ([]byte, error) { + var typename string + switch v := (*v).(type) { + case *SubGraphsSupergraphSubGraphs: + typename = "SubGraphs" + + result := struct { + TypeName string `json:"__typename"` + *SubGraphsSupergraphSubGraphs + }{typename, v} + return json.Marshal(result) + case *SubGraphsSupergraphUnchanged: + typename = "Unchanged" + + result := struct { + TypeName string `json:"__typename"` + *SubGraphsSupergraphUnchanged + }{typename, v} + return json.Marshal(result) + case nil: + return []byte("null"), nil + default: + return nil, fmt.Errorf( + `unexpected concrete type for SubGraphsSupergraph: "%T"`, v) + } +} + +// SubGraphsSupergraphSubGraphs includes the requested fields of the GraphQL type SubGraphs. +type SubGraphsSupergraphSubGraphs struct { + Typename *string `json:"__typename"` + SubGraphs []*SubGraphsSupergraphSubGraphsSubGraphsSubGraph `json:"subGraphs"` +} + +// GetTypename returns SubGraphsSupergraphSubGraphs.Typename, and is useful for accessing the field via an interface. +func (v *SubGraphsSupergraphSubGraphs) GetTypename() *string { return v.Typename } + +// GetSubGraphs returns SubGraphsSupergraphSubGraphs.SubGraphs, and is useful for accessing the field via an interface. +func (v *SubGraphsSupergraphSubGraphs) GetSubGraphs() []*SubGraphsSupergraphSubGraphsSubGraphsSubGraph { + return v.SubGraphs +} + +// SubGraphsSupergraphSubGraphsSubGraphsSubGraph includes the requested fields of the GraphQL type SubGraph. +type SubGraphsSupergraphSubGraphsSubGraphsSubGraph struct { Service string `json:"service"` Url *string `json:"url"` WsUrl *string `json:"wsUrl"` @@ -49,20 +197,28 @@ type SubGraphsSubGraphsSubGraph struct { ChangedAt time.Time `json:"changedAt"` } -// GetService returns SubGraphsSubGraphsSubGraph.Service, and is useful for accessing the field via an interface. -func (v *SubGraphsSubGraphsSubGraph) GetService() string { return v.Service } +// GetService returns SubGraphsSupergraphSubGraphsSubGraphsSubGraph.Service, and is useful for accessing the field via an interface. +func (v *SubGraphsSupergraphSubGraphsSubGraphsSubGraph) GetService() string { return v.Service } -// GetUrl returns SubGraphsSubGraphsSubGraph.Url, and is useful for accessing the field via an interface. -func (v *SubGraphsSubGraphsSubGraph) GetUrl() *string { return v.Url } +// GetUrl returns SubGraphsSupergraphSubGraphsSubGraphsSubGraph.Url, and is useful for accessing the field via an interface. +func (v *SubGraphsSupergraphSubGraphsSubGraphsSubGraph) GetUrl() *string { return v.Url } -// GetWsUrl returns SubGraphsSubGraphsSubGraph.WsUrl, and is useful for accessing the field via an interface. -func (v *SubGraphsSubGraphsSubGraph) GetWsUrl() *string { return v.WsUrl } +// GetWsUrl returns SubGraphsSupergraphSubGraphsSubGraphsSubGraph.WsUrl, and is useful for accessing the field via an interface. +func (v *SubGraphsSupergraphSubGraphsSubGraphsSubGraph) GetWsUrl() *string { return v.WsUrl } -// GetChangedBy returns SubGraphsSubGraphsSubGraph.ChangedBy, and is useful for accessing the field via an interface. -func (v *SubGraphsSubGraphsSubGraph) GetChangedBy() string { return v.ChangedBy } +// GetChangedBy returns SubGraphsSupergraphSubGraphsSubGraphsSubGraph.ChangedBy, and is useful for accessing the field via an interface. +func (v *SubGraphsSupergraphSubGraphsSubGraphsSubGraph) GetChangedBy() string { return v.ChangedBy } -// GetChangedAt returns SubGraphsSubGraphsSubGraph.ChangedAt, and is useful for accessing the field via an interface. -func (v *SubGraphsSubGraphsSubGraph) GetChangedAt() time.Time { return v.ChangedAt } +// GetChangedAt returns SubGraphsSupergraphSubGraphsSubGraphsSubGraph.ChangedAt, and is useful for accessing the field via an interface. +func (v *SubGraphsSupergraphSubGraphsSubGraphsSubGraph) GetChangedAt() time.Time { return v.ChangedAt } + +// SubGraphsSupergraphUnchanged includes the requested fields of the GraphQL type Unchanged. +type SubGraphsSupergraphUnchanged struct { + Typename *string `json:"__typename"` +} + +// GetTypename returns SubGraphsSupergraphUnchanged.Typename, and is useful for accessing the field via an interface. +func (v *SubGraphsSupergraphUnchanged) GetTypename() *string { return v.Typename } // UpdateSubGraphResponse is returned by UpdateSubGraph on success. type UpdateSubGraphResponse struct { @@ -123,12 +279,17 @@ func SubGraphs( OpName: "SubGraphs", Query: ` query SubGraphs ($ref: String!) { - subGraphs(ref: $ref) { - service - url - wsUrl - changedBy - changedAt + supergraph(ref: $ref) { + __typename + ... on SubGraphs { + subGraphs { + service + url + wsUrl + changedBy + changedAt + } + } } } `, diff --git a/graph/generated/generated.go b/graph/generated/generated.go index ae4c331..56bb665 100644 --- a/graph/generated/generated.go +++ b/graph/generated/generated.go @@ -51,7 +51,8 @@ type ComplexityRoot struct { } Query struct { - SubGraphs func(childComplexity int, ref string) int + SubGraphs func(childComplexity int, ref string) int + Supergraph func(childComplexity int, ref string, isAfter *string) int } SubGraph struct { @@ -63,6 +64,17 @@ type ComplexityRoot struct { URL func(childComplexity int) int WsURL func(childComplexity int) int } + + SubGraphs struct { + ID func(childComplexity int) int + MinDelaySeconds func(childComplexity int) int + SubGraphs func(childComplexity int) int + } + + Unchanged struct { + ID func(childComplexity int) int + MinDelaySeconds func(childComplexity int) int + } } type MutationResolver interface { @@ -70,6 +82,7 @@ type MutationResolver interface { } type QueryResolver interface { SubGraphs(ctx context.Context, ref string) ([]*model.SubGraph, error) + Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error) } type executableSchema struct { @@ -111,6 +124,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.SubGraphs(childComplexity, args["ref"].(string)), true + case "Query.supergraph": + if e.complexity.Query.Supergraph == nil { + break + } + + args, err := ec.field_Query_supergraph_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.Supergraph(childComplexity, args["ref"].(string), args["isAfter"].(*string)), true + case "SubGraph.changedAt": if e.complexity.SubGraph.ChangedAt == nil { break @@ -160,6 +185,41 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SubGraph.WsURL(childComplexity), true + case "SubGraphs.id": + if e.complexity.SubGraphs.ID == nil { + break + } + + return e.complexity.SubGraphs.ID(childComplexity), true + + case "SubGraphs.minDelaySeconds": + if e.complexity.SubGraphs.MinDelaySeconds == nil { + break + } + + return e.complexity.SubGraphs.MinDelaySeconds(childComplexity), true + + case "SubGraphs.subGraphs": + if e.complexity.SubGraphs.SubGraphs == nil { + break + } + + return e.complexity.SubGraphs.SubGraphs(childComplexity), true + + case "Unchanged.id": + if e.complexity.Unchanged.ID == nil { + break + } + + return e.complexity.Unchanged.ID(childComplexity), true + + case "Unchanged.minDelaySeconds": + if e.complexity.Unchanged.MinDelaySeconds == nil { + break + } + + return e.complexity.Unchanged.MinDelaySeconds(childComplexity), true + } return 0, false } @@ -230,13 +290,27 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er var sources = []*ast.Source{ {Name: "../schema.graphqls", Input: `type Query { - subGraphs(ref: String!): [SubGraph!]! @hasApiKey + subGraphs(ref: String!): [SubGraph!]! @hasApiKey @deprecated(reason: "Use supergraph instead") + supergraph(ref: String!, isAfter: String): Supergraph! @hasApiKey } type Mutation { updateSubGraph(input: InputSubGraph!): SubGraph! @hasApiKey } +union Supergraph = Unchanged | SubGraphs + +type Unchanged { + id: ID! + minDelaySeconds: Int! +} + +type SubGraphs { + id: ID! + minDelaySeconds: Int! + subGraphs: [SubGraph!]! +} + type SubGraph { id: ID! service: String! @@ -311,6 +385,30 @@ func (ec *executionContext) field_Query_subGraphs_args(ctx context.Context, rawA return args, nil } +func (ec *executionContext) field_Query_supergraph_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["ref"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ref")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["ref"] = arg0 + var arg1 *string + if tmp, ok := rawArgs["isAfter"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("isAfter")) + arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["isAfter"] = arg1 + return args, nil +} + func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -531,6 +629,81 @@ func (ec *executionContext) fieldContext_Query_subGraphs(ctx context.Context, fi return fc, nil } +func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_supergraph(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Supergraph(rctx, fc.Args["ref"].(string), fc.Args["isAfter"].(*string)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.HasApiKey == nil { + return nil, errors.New("directive hasApiKey is not implemented") + } + return ec.directives.HasApiKey(ctx, nil, directive0) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(model.Supergraph); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be gitlab.com/unboundsoftware/schemas/graph/model.Supergraph`, tmp) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(model.Supergraph) + fc.Result = res + return ec.marshalNSupergraph2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSupergraph(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_supergraph(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Supergraph does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_supergraph_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return + } + return fc, nil +} + func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query___type(ctx, field) if err != nil { @@ -962,6 +1135,242 @@ func (ec *executionContext) fieldContext_SubGraph_changedAt(ctx context.Context, return fc, nil } +func (ec *executionContext) _SubGraphs_id(ctx context.Context, field graphql.CollectedField, obj *model.SubGraphs) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SubGraphs_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SubGraphs_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SubGraphs", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _SubGraphs_minDelaySeconds(ctx context.Context, field graphql.CollectedField, obj *model.SubGraphs) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SubGraphs_minDelaySeconds(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.MinDelaySeconds, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int) + fc.Result = res + return ec.marshalNInt2int(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SubGraphs_minDelaySeconds(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SubGraphs", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _SubGraphs_subGraphs(ctx context.Context, field graphql.CollectedField, obj *model.SubGraphs) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SubGraphs_subGraphs(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SubGraphs, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.SubGraph) + fc.Result = res + return ec.marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SubGraphs_subGraphs(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SubGraphs", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_SubGraph_id(ctx, field) + case "service": + return ec.fieldContext_SubGraph_service(ctx, field) + case "url": + return ec.fieldContext_SubGraph_url(ctx, field) + case "wsUrl": + return ec.fieldContext_SubGraph_wsUrl(ctx, field) + case "sdl": + return ec.fieldContext_SubGraph_sdl(ctx, field) + case "changedBy": + return ec.fieldContext_SubGraph_changedBy(ctx, field) + case "changedAt": + return ec.fieldContext_SubGraph_changedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type SubGraph", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Unchanged_id(ctx context.Context, field graphql.CollectedField, obj *model.Unchanged) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Unchanged_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Unchanged_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Unchanged", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Unchanged_minDelaySeconds(ctx context.Context, field graphql.CollectedField, obj *model.Unchanged) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Unchanged_minDelaySeconds(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.MinDelaySeconds, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int) + fc.Result = res + return ec.marshalNInt2int(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Unchanged_minDelaySeconds(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Unchanged", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { fc, err := ec.fieldContext___Directive_name(ctx, field) if err != nil { @@ -2799,6 +3208,29 @@ func (ec *executionContext) unmarshalInputInputSubGraph(ctx context.Context, obj // region ************************** interface.gotpl *************************** +func (ec *executionContext) _Supergraph(ctx context.Context, sel ast.SelectionSet, obj model.Supergraph) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null + case model.Unchanged: + return ec._Unchanged(ctx, sel, &obj) + case *model.Unchanged: + if obj == nil { + return graphql.Null + } + return ec._Unchanged(ctx, sel, obj) + case model.SubGraphs: + return ec._SubGraphs(ctx, sel, &obj) + case *model.SubGraphs: + if obj == nil { + return graphql.Null + } + return ec._SubGraphs(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + // endregion ************************** interface.gotpl *************************** // region **************************** object.gotpl **************************** @@ -2881,6 +3313,29 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) } + out.Concurrently(i, func() graphql.Marshaler { + return rrm(innerCtx) + }) + case "supergraph": + field := field + + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_supergraph(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) + } + out.Concurrently(i, func() graphql.Marshaler { return rrm(innerCtx) }) @@ -2971,6 +3426,83 @@ func (ec *executionContext) _SubGraph(ctx context.Context, sel ast.SelectionSet, return out } +var subGraphsImplementors = []string{"SubGraphs", "Supergraph"} + +func (ec *executionContext) _SubGraphs(ctx context.Context, sel ast.SelectionSet, obj *model.SubGraphs) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, subGraphsImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("SubGraphs") + case "id": + + out.Values[i] = ec._SubGraphs_id(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "minDelaySeconds": + + out.Values[i] = ec._SubGraphs_minDelaySeconds(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "subGraphs": + + out.Values[i] = ec._SubGraphs_subGraphs(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var unchangedImplementors = []string{"Unchanged", "Supergraph"} + +func (ec *executionContext) _Unchanged(ctx context.Context, sel ast.SelectionSet, obj *model.Unchanged) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, unchangedImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Unchanged") + case "id": + + out.Values[i] = ec._Unchanged_id(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "minDelaySeconds": + + out.Values[i] = ec._Unchanged_minDelaySeconds(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var __DirectiveImplementors = []string{"__Directive"} func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler { @@ -3324,6 +3856,21 @@ func (ec *executionContext) unmarshalNInputSubGraph2gitlabᚗcomᚋunboundsoftwa return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) { + res, err := graphql.UnmarshalInt(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler { + res := graphql.MarshalInt(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -3397,6 +3944,16 @@ func (ec *executionContext) marshalNSubGraph2ᚖgitlabᚗcomᚋunboundsoftware return ec._SubGraph(ctx, sel, v) } +func (ec *executionContext) marshalNSupergraph2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSupergraph(ctx context.Context, sel ast.SelectionSet, v model.Supergraph) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Supergraph(ctx, sel, v) +} + func (ec *executionContext) unmarshalNTime2timeᚐTime(ctx context.Context, v interface{}) (time.Time, error) { res, err := graphql.UnmarshalTime(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/graph/model/models_gen.go b/graph/model/models_gen.go index afcc75b..670bdab 100644 --- a/graph/model/models_gen.go +++ b/graph/model/models_gen.go @@ -6,6 +6,10 @@ import ( "time" ) +type Supergraph interface { + IsSupergraph() +} + type InputSubGraph struct { Ref string `json:"ref"` Service string `json:"service"` @@ -23,3 +27,18 @@ type SubGraph struct { ChangedBy string `json:"changedBy"` ChangedAt time.Time `json:"changedAt"` } + +type SubGraphs struct { + ID string `json:"id"` + MinDelaySeconds int `json:"minDelaySeconds"` + SubGraphs []*SubGraph `json:"subGraphs"` +} + +func (SubGraphs) IsSupergraph() {} + +type Unchanged struct { + ID string `json:"id"` + MinDelaySeconds int `json:"minDelaySeconds"` +} + +func (Unchanged) IsSupergraph() {} diff --git a/graph/schema.graphqls b/graph/schema.graphqls index 1ea12d4..a980dda 100644 --- a/graph/schema.graphqls +++ b/graph/schema.graphqls @@ -1,11 +1,25 @@ type Query { - subGraphs(ref: String!): [SubGraph!]! @hasApiKey + subGraphs(ref: String!): [SubGraph!]! @hasApiKey @deprecated(reason: "Use supergraph instead") + supergraph(ref: String!, isAfter: String): Supergraph! @hasApiKey } type Mutation { updateSubGraph(input: InputSubGraph!): SubGraph! @hasApiKey } +union Supergraph = Unchanged | SubGraphs + +type Unchanged { + id: ID! + minDelaySeconds: Int! +} + +type SubGraphs { + id: ID! + minDelaySeconds: Int! + subGraphs: [SubGraph!]! +} + type SubGraph { id: ID! service: String! diff --git a/graph/schema.helpers.go b/graph/schema.helpers.go index 1e4da5f..f9b77cf 100644 --- a/graph/schema.helpers.go +++ b/graph/schema.helpers.go @@ -4,6 +4,7 @@ import ( "gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/schemas/domain" + "gitlab.com/unboundsoftware/schemas/graph/model" ) func (r *Resolver) fetchSubGraph(subGraphId string) (*domain.SubGraph, error) { @@ -14,3 +15,15 @@ func (r *Resolver) fetchSubGraph(subGraphId string) (*domain.SubGraph, error) { } return subGraph, nil } + +func (r *Resolver) toGqlSubGraph(subGraph *domain.SubGraph) *model.SubGraph { + return &model.SubGraph{ + ID: subGraph.ID.String(), + Service: subGraph.Service, + URL: subGraph.Url, + WsURL: subGraph.WSUrl, + Sdl: subGraph.Sdl, + ChangedBy: subGraph.ChangedBy, + ChangedAt: subGraph.ChangedAt, + } +} diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go index 38ff154..7b9e5d3 100644 --- a/graph/schema.resolvers.go +++ b/graph/schema.resolvers.go @@ -5,6 +5,8 @@ package graph import ( "context" + "fmt" + "strings" "github.com/wundergraph/graphql-go-tools/pkg/federation/sdlmerge" "gitlab.com/unboundsoftware/eventsourced/eventsourced" @@ -25,8 +27,12 @@ func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.Input if err != nil { return nil, err } + if strings.TrimSpace(input.Sdl) == strings.TrimSpace(subGraph.Sdl) { + return r.toGqlSubGraph(subGraph), nil + } serviceSDLs := []string{input.Sdl} - for _, id := range r.Cache.Services(input.Ref) { + services, _ := r.Cache.Services(input.Ref, "") + for _, id := range services { sg, err := r.fetchSubGraph(id) if err != nil { return nil, err @@ -50,20 +56,34 @@ func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.Input if err != nil { return nil, err } - return &model.SubGraph{ - ID: subGraph.ID.String(), - Service: subGraph.Service, - URL: subGraph.Url, - WsURL: subGraph.WSUrl, - Sdl: subGraph.Sdl, - ChangedBy: subGraph.ChangedBy, - ChangedAt: subGraph.ChangedAt, - }, nil + return r.toGqlSubGraph(subGraph), nil } // SubGraphs is the resolver for the subGraphs field. func (r *queryResolver) SubGraphs(ctx context.Context, ref string) ([]*model.SubGraph, error) { - services := r.Cache.Services(ref) + res, err := r.Supergraph(ctx, ref, nil) + if err != nil { + return nil, err + } + if s, ok := res.(*model.SubGraphs); ok { + return s.SubGraphs, nil + } + return nil, fmt.Errorf("unexpected response") +} + +// Supergraph is the resolver for the supergraph field. +func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error) { + after := "" + if isAfter != nil { + after = *isAfter + } + services, lastUpdate := r.Cache.Services(ref, after) + if after == lastUpdate { + return &model.Unchanged{ + ID: lastUpdate, + MinDelaySeconds: 10, + }, nil + } subGraphs := make([]*model.SubGraph, len(services)) for i, id := range services { sg, err := r.fetchSubGraph(id) @@ -80,7 +100,11 @@ func (r *queryResolver) SubGraphs(ctx context.Context, ref string) ([]*model.Sub ChangedAt: sg.ChangedAt, } } - return subGraphs, nil + return &model.SubGraphs{ + ID: lastUpdate, + SubGraphs: subGraphs, + MinDelaySeconds: 10, + }, nil } // Mutation returns generated.MutationResolver implementation.