feat(sdlmerge): add shared types for GraphQL schema handling
Introduce types for managing fielded, enum, union, and scalar shared types. Implement functionality for comparing values and creating field sets. Enhance schema extensions by integrating new visitors for enum types.
This commit is contained in:
@@ -17,7 +17,7 @@ require (
|
|||||||
github.com/sparetimecoders/goamqp v0.3.1
|
github.com/sparetimecoders/goamqp v0.3.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/vektah/gqlparser/v2 v2.5.23
|
github.com/vektah/gqlparser/v2 v2.5.23
|
||||||
github.com/wundergraph/graphql-go-tools v1.67.4
|
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.160
|
||||||
gitlab.com/unboundsoftware/eventsourced/amqp v1.8.0
|
gitlab.com/unboundsoftware/eventsourced/amqp v1.8.0
|
||||||
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.17.0
|
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.17.0
|
||||||
gitlab.com/unboundsoftware/eventsourced/pg v1.16.0
|
gitlab.com/unboundsoftware/eventsourced/pg v1.16.0
|
||||||
@@ -33,13 +33,11 @@ require (
|
|||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.1 // indirect
|
github.com/gorilla/websocket v1.5.1 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/jensneuse/byte-template v0.0.0-20200214152254-4f3cf06e5c68 // indirect
|
|
||||||
github.com/lib/pq v1.10.9 // indirect
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
|
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
|
|
||||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||||
github.com/sosodev/duration v1.3.1 // indirect
|
github.com/sosodev/duration v1.3.1 // indirect
|
||||||
github.com/tidwall/gjson v1.17.0 // indirect
|
github.com/tidwall/gjson v1.17.0 // indirect
|
||||||
@@ -47,6 +45,7 @@ require (
|
|||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
github.com/urfave/cli/v2 v2.27.5 // indirect
|
github.com/urfave/cli/v2 v2.27.5 // indirect
|
||||||
|
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/mod v0.23.0 // indirect
|
golang.org/x/mod v0.23.0 // indirect
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7c
|
|||||||
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/evanphx/json-patch/v5 v5.1.0 h1:B0aXl1o/1cP8NbviYiBMkcHBtUjIJ1/Ccg6b+SwCLQg=
|
|
||||||
github.com/evanphx/json-patch/v5 v5.1.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/getsentry/sentry-go v0.31.1 h1:ELVc0h7gwyhnXHDouXkhqTFSO5oslsRDk0++eyE0KJ4=
|
github.com/getsentry/sentry-go v0.31.1 h1:ELVc0h7gwyhnXHDouXkhqTFSO5oslsRDk0++eyE0KJ4=
|
||||||
@@ -59,8 +57,6 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx
|
|||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
@@ -75,8 +71,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf
|
|||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/jensneuse/byte-template v0.0.0-20200214152254-4f3cf06e5c68 h1:E80wOd3IFQcoBxLkAUpUQ3BoGrZ4DxhQdP21+HH1s6A=
|
|
||||||
github.com/jensneuse/byte-template v0.0.0-20200214152254-4f3cf06e5c68/go.mod h1:0D5r/VSW6D/o65rKLL9xk7sZxL2+oku2HvFPYeIMFr4=
|
|
||||||
github.com/jensneuse/diffview v1.0.0 h1:4b6FQJ7y3295JUHU3tRko6euyEboL825ZsXeZZM47Z4=
|
github.com/jensneuse/diffview v1.0.0 h1:4b6FQJ7y3295JUHU3tRko6euyEboL825ZsXeZZM47Z4=
|
||||||
github.com/jensneuse/diffview v1.0.0/go.mod h1:i6IacuD8LnEaPuiyzMHA+Wfz5mAuycMOf3R/orUY9y4=
|
github.com/jensneuse/diffview v1.0.0/go.mod h1:i6IacuD8LnEaPuiyzMHA+Wfz5mAuycMOf3R/orUY9y4=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
@@ -129,8 +123,6 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
|
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
|
||||||
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
|
|
||||||
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
|
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
|
||||||
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
@@ -171,8 +163,10 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
|||||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||||
github.com/vektah/gqlparser/v2 v2.5.23 h1:PurJ9wpgEVB7tty1seRUwkIDa/QH5RzkzraiKIjKLfA=
|
github.com/vektah/gqlparser/v2 v2.5.23 h1:PurJ9wpgEVB7tty1seRUwkIDa/QH5RzkzraiKIjKLfA=
|
||||||
github.com/vektah/gqlparser/v2 v2.5.23/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
github.com/vektah/gqlparser/v2 v2.5.23/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||||
github.com/wundergraph/graphql-go-tools v1.67.4 h1:1QtoftaZz5sScV/J6XLZ/oTfi1lMHp6UmFkYRQfY2/g=
|
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTBjW+SZK4mhxTTBVpxcqeBgWF1Rfmltbfk=
|
||||||
github.com/wundergraph/graphql-go-tools v1.67.4/go.mod h1:UFvflYjB/qnSCdgcHQuE6dTfwZ6viJB7yPnGOtBuibo=
|
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
|
||||||
|
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.160 h1:whZcpumOJNwJg71M56VGZ/Ex4TnYeGjmzhWa6+1X+uI=
|
||||||
|
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.160/go.mod h1:B7eV0Qh8Lop9QzIOQcsvKp3S0ejfC6mgyWoJnI917yQ=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
gitlab.com/unboundsoftware/eventsourced/amqp v1.8.0 h1:Ez3UuXvveyV/HlphqWUWZU/05DuhqKfgF9r6tCJigEI=
|
gitlab.com/unboundsoftware/eventsourced/amqp v1.8.0 h1:Ez3UuXvveyV/HlphqWUWZU/05DuhqKfgF9r6tCJigEI=
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/wundergraph/graphql-go-tools/pkg/federation/sdlmerge"
|
|
||||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||||
|
|
||||||
"gitlab.com/unboundsoftware/schemas/domain"
|
"gitlab.com/unboundsoftware/schemas/domain"
|
||||||
@@ -17,6 +16,7 @@ import (
|
|||||||
"gitlab.com/unboundsoftware/schemas/graph/model"
|
"gitlab.com/unboundsoftware/schemas/graph/model"
|
||||||
"gitlab.com/unboundsoftware/schemas/middleware"
|
"gitlab.com/unboundsoftware/schemas/middleware"
|
||||||
"gitlab.com/unboundsoftware/schemas/rand"
|
"gitlab.com/unboundsoftware/schemas/rand"
|
||||||
|
"gitlab.com/unboundsoftware/schemas/sdlmerge"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddOrganization is the resolver for the addOrganization field.
|
// AddOrganization is the resolver for the addOrganization field.
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
type collectEntitiesVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
collectedEntities entitySet
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCollectEntitiesVisitor(collectedEntities entitySet) *collectEntitiesVisitor {
|
||||||
|
return &collectEntitiesVisitor{
|
||||||
|
collectedEntities: collectedEntities,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collectEntitiesVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
c.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(c)
|
||||||
|
walker.RegisterEnterInterfaceTypeDefinitionVisitor(c)
|
||||||
|
walker.RegisterEnterObjectTypeDefinitionVisitor(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collectEntitiesVisitor) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
c.document = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collectEntitiesVisitor) EnterInterfaceTypeDefinition(ref int) {
|
||||||
|
interfaceType := c.document.InterfaceTypeDefinitions[ref]
|
||||||
|
name := c.document.InterfaceTypeDefinitionNameString(ref)
|
||||||
|
if err := c.resolvePotentialEntity(name, interfaceType.Directives.Refs); err != nil {
|
||||||
|
c.StopWithExternalErr(*err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collectEntitiesVisitor) EnterObjectTypeDefinition(ref int) {
|
||||||
|
objectType := c.document.ObjectTypeDefinitions[ref]
|
||||||
|
name := c.document.ObjectTypeDefinitionNameString(ref)
|
||||||
|
if err := c.resolvePotentialEntity(name, objectType.Directives.Refs); err != nil {
|
||||||
|
c.StopWithExternalErr(*err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collectEntitiesVisitor) resolvePotentialEntity(name string, directiveRefs []int) *operationreport.ExternalError {
|
||||||
|
if _, exists := c.collectedEntities[name]; exists {
|
||||||
|
err := operationreport.ErrEntitiesMustNotBeDuplicated(name)
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
for _, directiveRef := range directiveRefs {
|
||||||
|
if c.document.DirectiveNameString(directiveRef) != "key" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.collectedEntities[name] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
type extendEnumTypeDefinitionVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExtendEnumTypeDefinition() *extendEnumTypeDefinitionVisitor {
|
||||||
|
return &extendEnumTypeDefinitionVisitor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendEnumTypeDefinitionVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
e.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(e)
|
||||||
|
walker.RegisterEnterEnumTypeExtensionVisitor(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendEnumTypeDefinitionVisitor) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
e.document = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendEnumTypeDefinitionVisitor) EnterEnumTypeExtension(ref int) {
|
||||||
|
nodes, exists := e.document.Index.NodesByNameBytes(e.document.EnumTypeExtensionNameBytes(ref))
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasExtended := false
|
||||||
|
for i := range nodes {
|
||||||
|
if nodes[i].Kind != ast.NodeKindEnumTypeDefinition {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasExtended {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrSharedTypesMustNotBeExtended(e.document.EnumTypeExtensionNameString(ref)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.document.ExtendEnumTypeDefinitionByEnumTypeExtension(nodes[i].Ref, ref)
|
||||||
|
hasExtended = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasExtended {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrExtensionOrphansMustResolveInSupergraph(e.document.EnumTypeExtensionNameBytes(ref)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newExtendInputObjectTypeDefinition() *extendInputObjectTypeDefinitionVisitor {
|
||||||
|
return &extendInputObjectTypeDefinitionVisitor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type extendInputObjectTypeDefinitionVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendInputObjectTypeDefinitionVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
e.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(e)
|
||||||
|
walker.RegisterEnterInputObjectTypeExtensionVisitor(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendInputObjectTypeDefinitionVisitor) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
e.document = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendInputObjectTypeDefinitionVisitor) EnterInputObjectTypeExtension(ref int) {
|
||||||
|
nodes, exists := e.document.Index.NodesByNameBytes(e.document.InputObjectTypeExtensionNameBytes(ref))
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasExtended := false
|
||||||
|
for i := range nodes {
|
||||||
|
if nodes[i].Kind != ast.NodeKindInputObjectTypeDefinition {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasExtended {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrSharedTypesMustNotBeExtended(e.document.InputObjectTypeExtensionNameString(ref)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.document.ExtendInputObjectTypeDefinitionByInputObjectTypeExtension(nodes[i].Ref, ref)
|
||||||
|
hasExtended = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasExtended {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrExtensionOrphansMustResolveInSupergraph(e.document.InputObjectTypeExtensionNameBytes(ref)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newExtendInterfaceTypeDefinition(collectedEntities entitySet) *extendInterfaceTypeDefinitionVisitor {
|
||||||
|
return &extendInterfaceTypeDefinitionVisitor{
|
||||||
|
collectedEntities: collectedEntities,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type extendInterfaceTypeDefinitionVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
collectedEntities entitySet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendInterfaceTypeDefinitionVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
e.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(e)
|
||||||
|
walker.RegisterEnterInterfaceTypeExtensionVisitor(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendInterfaceTypeDefinitionVisitor) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
e.document = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendInterfaceTypeDefinitionVisitor) EnterInterfaceTypeExtension(ref int) {
|
||||||
|
nameBytes := e.document.InterfaceTypeExtensionNameBytes(ref)
|
||||||
|
nodes, exists := e.document.Index.NodesByNameBytes(nameBytes)
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeToExtend *ast.Node
|
||||||
|
isEntity := false
|
||||||
|
for i := range nodes {
|
||||||
|
if nodes[i].Kind != ast.NodeKindInterfaceTypeDefinition {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nodeToExtend != nil {
|
||||||
|
e.StopWithExternalErr(*multipleExtensionError(isEntity, nameBytes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var err *operationreport.ExternalError
|
||||||
|
extension := e.document.InterfaceTypeExtensions[ref]
|
||||||
|
if isEntity, err = e.collectedEntities.isExtensionForEntity(nameBytes, extension.Directives.Refs, e.document); err != nil {
|
||||||
|
e.StopWithExternalErr(*err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodeToExtend = &nodes[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeToExtend == nil {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrExtensionOrphansMustResolveInSupergraph(e.document.InterfaceTypeExtensionNameBytes(ref)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.document.ExtendInterfaceTypeDefinitionByInterfaceTypeExtension(nodeToExtend.Ref, ref)
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mergeDuplicatedFieldsVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMergeDuplicatedFieldsVisitor() *mergeDuplicatedFieldsVisitor {
|
||||||
|
return &mergeDuplicatedFieldsVisitor{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mergeDuplicatedFieldsVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
m.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(m)
|
||||||
|
walker.RegisterLeaveObjectTypeDefinitionVisitor(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mergeDuplicatedFieldsVisitor) EnterDocument(document, _ *ast.Document) {
|
||||||
|
m.document = document
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mergeDuplicatedFieldsVisitor) LeaveObjectTypeDefinition(ref int) {
|
||||||
|
var refsForDeletion []int
|
||||||
|
fieldByTypeRefSet := make(map[string]int)
|
||||||
|
for _, fieldRef := range m.document.ObjectTypeDefinitions[ref].FieldsDefinition.Refs {
|
||||||
|
fieldName := m.document.FieldDefinitionNameString(fieldRef)
|
||||||
|
newTypeRef := m.document.FieldDefinitions[fieldRef].Type
|
||||||
|
if oldTypeRef, ok := fieldByTypeRefSet[fieldName]; ok {
|
||||||
|
if m.document.TypesAreEqualDeep(oldTypeRef, newTypeRef) {
|
||||||
|
refsForDeletion = append(refsForDeletion, fieldRef)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
oldFieldTypeNameBytes, err := m.document.PrintTypeBytes(oldTypeRef, nil)
|
||||||
|
if err != nil {
|
||||||
|
m.Walker.StopWithInternalErr(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newFieldTypeNameBytes, err := m.document.PrintTypeBytes(newTypeRef, nil)
|
||||||
|
if err != nil {
|
||||||
|
m.Walker.StopWithInternalErr(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Walker.StopWithExternalErr(operationreport.ErrDuplicateFieldsMustBeIdentical(
|
||||||
|
fieldName, m.document.ObjectTypeDefinitionNameString(ref), string(oldFieldTypeNameBytes), string(newFieldTypeNameBytes),
|
||||||
|
))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldByTypeRefSet[fieldName] = newTypeRef
|
||||||
|
}
|
||||||
|
|
||||||
|
m.document.RemoveFieldDefinitionsFromObjectTypeDefinition(refsForDeletion, ref)
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newExtendObjectTypeDefinition(collectedEntities entitySet) *extendObjectTypeDefinitionVisitor {
|
||||||
|
return &extendObjectTypeDefinitionVisitor{
|
||||||
|
collectedEntities: collectedEntities,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type extendObjectTypeDefinitionVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
collectedEntities entitySet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendObjectTypeDefinitionVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
e.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(e)
|
||||||
|
walker.RegisterEnterObjectTypeExtensionVisitor(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendObjectTypeDefinitionVisitor) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
e.document = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendObjectTypeDefinitionVisitor) EnterObjectTypeExtension(ref int) {
|
||||||
|
nameBytes := e.document.ObjectTypeExtensionNameBytes(ref)
|
||||||
|
nodes, exists := e.document.Index.NodesByNameBytes(nameBytes)
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeToExtend *ast.Node
|
||||||
|
isEntity := false
|
||||||
|
for i := range nodes {
|
||||||
|
if nodes[i].Kind != ast.NodeKindObjectTypeDefinition {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nodeToExtend != nil {
|
||||||
|
e.StopWithExternalErr(*multipleExtensionError(isEntity, nameBytes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var err *operationreport.ExternalError
|
||||||
|
extension := e.document.ObjectTypeExtensions[ref]
|
||||||
|
if isEntity, err = e.collectedEntities.isExtensionForEntity(nameBytes, extension.Directives.Refs, e.document); err != nil {
|
||||||
|
e.StopWithExternalErr(*err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodeToExtend = &nodes[i]
|
||||||
|
if ast.IsRootType(nameBytes) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeToExtend == nil {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrExtensionOrphansMustResolveInSupergraph(nameBytes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.document.ExtendObjectTypeDefinitionByObjectTypeExtension(nodeToExtend.Ref, ref)
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
type removeDuplicateFieldedSharedTypesVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
sharedTypeSet map[string]fieldedSharedType
|
||||||
|
rootNodesToRemove []ast.Node
|
||||||
|
lastInputRef int
|
||||||
|
lastInterfaceRef int
|
||||||
|
lastObjectRef int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoveDuplicateFieldedSharedTypesVisitor() *removeDuplicateFieldedSharedTypesVisitor {
|
||||||
|
return &removeDuplicateFieldedSharedTypesVisitor{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
make(map[string]fieldedSharedType),
|
||||||
|
nil,
|
||||||
|
ast.InvalidRef,
|
||||||
|
ast.InvalidRef,
|
||||||
|
ast.InvalidRef,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldedSharedTypesVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
r.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(r)
|
||||||
|
walker.RegisterEnterInputObjectTypeDefinitionVisitor(r)
|
||||||
|
walker.RegisterEnterInterfaceTypeDefinitionVisitor(r)
|
||||||
|
walker.RegisterEnterObjectTypeDefinitionVisitor(r)
|
||||||
|
walker.RegisterLeaveDocumentVisitor(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldedSharedTypesVisitor) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
r.document = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldedSharedTypesVisitor) EnterInputObjectTypeDefinition(ref int) {
|
||||||
|
if ref <= r.lastInputRef {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := r.document.InputObjectTypeDefinitionNameString(ref)
|
||||||
|
refs := r.document.InputObjectTypeDefinitions[ref].InputFieldsDefinition.Refs
|
||||||
|
input, exists := r.sharedTypeSet[name]
|
||||||
|
if exists {
|
||||||
|
if !input.areFieldsIdentical(refs) {
|
||||||
|
r.StopWithExternalErr(operationreport.ErrSharedTypesMustBeIdenticalToFederate(name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.rootNodesToRemove = append(r.rootNodesToRemove, ast.Node{Kind: ast.NodeKindInputObjectTypeDefinition, Ref: ref})
|
||||||
|
} else {
|
||||||
|
r.sharedTypeSet[name] = newFieldedSharedType(r.document, ast.NodeKindInputValueDefinition, refs)
|
||||||
|
}
|
||||||
|
r.lastInputRef = ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldedSharedTypesVisitor) EnterInterfaceTypeDefinition(ref int) {
|
||||||
|
if ref <= r.lastInterfaceRef {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := r.document.InterfaceTypeDefinitionNameString(ref)
|
||||||
|
interfaceType := r.document.InterfaceTypeDefinitions[ref]
|
||||||
|
refs := interfaceType.FieldsDefinition.Refs
|
||||||
|
iFace, exists := r.sharedTypeSet[name]
|
||||||
|
if exists {
|
||||||
|
if !iFace.areFieldsIdentical(refs) {
|
||||||
|
r.StopWithExternalErr(operationreport.ErrSharedTypesMustBeIdenticalToFederate(name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.rootNodesToRemove = append(r.rootNodesToRemove, ast.Node{Kind: ast.NodeKindInterfaceTypeDefinition, Ref: ref})
|
||||||
|
} else {
|
||||||
|
r.sharedTypeSet[name] = newFieldedSharedType(r.document, ast.NodeKindFieldDefinition, refs)
|
||||||
|
}
|
||||||
|
r.lastInterfaceRef = ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldedSharedTypesVisitor) EnterObjectTypeDefinition(ref int) {
|
||||||
|
if ref <= r.lastObjectRef {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := r.document.ObjectTypeDefinitionNameString(ref)
|
||||||
|
objectType := r.document.ObjectTypeDefinitions[ref]
|
||||||
|
refs := objectType.FieldsDefinition.Refs
|
||||||
|
object, exists := r.sharedTypeSet[name]
|
||||||
|
if exists {
|
||||||
|
if !object.areFieldsIdentical(refs) {
|
||||||
|
r.StopWithExternalErr(operationreport.ErrSharedTypesMustBeIdenticalToFederate(name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.rootNodesToRemove = append(r.rootNodesToRemove, ast.Node{Kind: ast.NodeKindObjectTypeDefinition, Ref: ref})
|
||||||
|
} else {
|
||||||
|
r.sharedTypeSet[name] = newFieldedSharedType(r.document, ast.NodeKindFieldDefinition, refs)
|
||||||
|
}
|
||||||
|
r.lastObjectRef = ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldedSharedTypesVisitor) LeaveDocument(_, _ *ast.Document) {
|
||||||
|
if r.rootNodesToRemove != nil {
|
||||||
|
r.document.DeleteRootNodes(r.rootNodesToRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
type removeDuplicateFieldlessSharedTypesVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
sharedTypeSet map[string]fieldlessSharedType
|
||||||
|
rootNodesToRemove []ast.Node
|
||||||
|
lastEnumRef int
|
||||||
|
lastUnionRef int
|
||||||
|
lastScalarRef int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoveDuplicateFieldlessSharedTypesVisitor() *removeDuplicateFieldlessSharedTypesVisitor {
|
||||||
|
return &removeDuplicateFieldlessSharedTypesVisitor{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
make(map[string]fieldlessSharedType),
|
||||||
|
nil,
|
||||||
|
ast.InvalidRef,
|
||||||
|
ast.InvalidRef,
|
||||||
|
ast.InvalidRef,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldlessSharedTypesVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
r.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(r)
|
||||||
|
walker.RegisterEnterEnumTypeDefinitionVisitor(r)
|
||||||
|
walker.RegisterEnterScalarTypeDefinitionVisitor(r)
|
||||||
|
walker.RegisterEnterUnionTypeDefinitionVisitor(r)
|
||||||
|
walker.RegisterLeaveDocumentVisitor(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldlessSharedTypesVisitor) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
r.document = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldlessSharedTypesVisitor) EnterEnumTypeDefinition(ref int) {
|
||||||
|
if ref <= r.lastEnumRef {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := r.document.EnumTypeDefinitionNameString(ref)
|
||||||
|
enum, exists := r.sharedTypeSet[name]
|
||||||
|
if exists {
|
||||||
|
if !enum.areValuesIdentical(r.document.EnumTypeDefinitions[ref].EnumValuesDefinition.Refs) {
|
||||||
|
r.StopWithExternalErr(operationreport.ErrSharedTypesMustBeIdenticalToFederate(name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.rootNodesToRemove = append(r.rootNodesToRemove, ast.Node{Kind: ast.NodeKindEnumTypeDefinition, Ref: ref})
|
||||||
|
} else {
|
||||||
|
r.sharedTypeSet[name] = newEnumSharedType(r.document, ref)
|
||||||
|
}
|
||||||
|
r.lastEnumRef = ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldlessSharedTypesVisitor) EnterScalarTypeDefinition(ref int) {
|
||||||
|
if ref <= r.lastScalarRef {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := r.document.ScalarTypeDefinitionNameString(ref)
|
||||||
|
_, exists := r.sharedTypeSet[name]
|
||||||
|
if exists {
|
||||||
|
r.rootNodesToRemove = append(r.rootNodesToRemove, ast.Node{Kind: ast.NodeKindScalarTypeDefinition, Ref: ref})
|
||||||
|
} else {
|
||||||
|
r.sharedTypeSet[name] = scalarSharedType{}
|
||||||
|
}
|
||||||
|
r.lastScalarRef = ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldlessSharedTypesVisitor) EnterUnionTypeDefinition(ref int) {
|
||||||
|
if ref <= r.lastUnionRef {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := r.document.UnionTypeDefinitionNameString(ref)
|
||||||
|
union, exists := r.sharedTypeSet[name]
|
||||||
|
if exists {
|
||||||
|
if !union.areValuesIdentical(r.document.UnionTypeDefinitions[ref].UnionMemberTypes.Refs) {
|
||||||
|
r.StopWithExternalErr(operationreport.ErrSharedTypesMustBeIdenticalToFederate(name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.rootNodesToRemove = append(r.rootNodesToRemove, ast.Node{Kind: ast.NodeKindUnionTypeDefinition, Ref: ref})
|
||||||
|
} else {
|
||||||
|
r.sharedTypeSet[name] = newUnionSharedType(r.document, ref)
|
||||||
|
}
|
||||||
|
r.lastUnionRef = ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeDuplicateFieldlessSharedTypesVisitor) LeaveDocument(_, _ *ast.Document) {
|
||||||
|
if r.rootNodesToRemove != nil {
|
||||||
|
r.document.DeleteRootNodes(r.rootNodesToRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRemoveEmptyObjectTypeDefinition() *removeEmptyObjectTypeDefinition {
|
||||||
|
return &removeEmptyObjectTypeDefinition{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeEmptyObjectTypeDefinition struct{}
|
||||||
|
|
||||||
|
func (r *removeEmptyObjectTypeDefinition) Register(walker *astvisitor.Walker) {
|
||||||
|
walker.RegisterLeaveDocumentVisitor(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeEmptyObjectTypeDefinition) LeaveDocument(operation, _ *ast.Document) {
|
||||||
|
for ref := range operation.ObjectTypeDefinitions {
|
||||||
|
if operation.ObjectTypeDefinitions[ref].HasFieldDefinitions {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := operation.ObjectTypeDefinitionNameString(ref)
|
||||||
|
node, ok := operation.Index.FirstNodeByNameStr(name)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.RemoveRootNode(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRemoveFieldDefinitions(directives ...string) *removeFieldDefinitionByDirective {
|
||||||
|
directivesSet := make(map[string]struct{}, len(directives))
|
||||||
|
for _, directive := range directives {
|
||||||
|
directivesSet[directive] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &removeFieldDefinitionByDirective{
|
||||||
|
directives: directivesSet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeFieldDefinitionByDirective struct {
|
||||||
|
operation *ast.Document
|
||||||
|
directives map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeFieldDefinitionByDirective) Register(walker *astvisitor.Walker) {
|
||||||
|
walker.RegisterEnterDocumentVisitor(r)
|
||||||
|
walker.RegisterLeaveObjectTypeDefinitionVisitor(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeFieldDefinitionByDirective) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
r.operation = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeFieldDefinitionByDirective) LeaveObjectTypeDefinition(ref int) {
|
||||||
|
var refsForDeletion []int
|
||||||
|
// select fields for deletion
|
||||||
|
for _, fieldRef := range r.operation.ObjectTypeDefinitions[ref].FieldsDefinition.Refs {
|
||||||
|
for _, directiveRef := range r.operation.FieldDefinitions[fieldRef].Directives.Refs {
|
||||||
|
directiveName := r.operation.DirectiveNameString(directiveRef)
|
||||||
|
if _, ok := r.directives[directiveName]; ok {
|
||||||
|
refsForDeletion = append(refsForDeletion, fieldRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete fields
|
||||||
|
r.operation.RemoveFieldDefinitionsFromObjectTypeDefinition(refsForDeletion, ref)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRemoveFieldDefinitionDirective(directives ...string) *removeFieldDefinitionDirective {
|
||||||
|
directivesSet := make(map[string]struct{}, len(directives))
|
||||||
|
for _, directive := range directives {
|
||||||
|
directivesSet[directive] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &removeFieldDefinitionDirective{
|
||||||
|
directives: directivesSet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeFieldDefinitionDirective struct {
|
||||||
|
operation *ast.Document
|
||||||
|
directives map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeFieldDefinitionDirective) Register(walker *astvisitor.Walker) {
|
||||||
|
walker.RegisterEnterDocumentVisitor(r)
|
||||||
|
walker.RegisterEnterFieldDefinitionVisitor(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeFieldDefinitionDirective) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
r.operation = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeFieldDefinitionDirective) EnterFieldDefinition(ref int) {
|
||||||
|
var refsForDeletion []int
|
||||||
|
// select directives for deletion
|
||||||
|
for _, directiveRef := range r.operation.FieldDefinitions[ref].Directives.Refs {
|
||||||
|
directiveName := r.operation.DirectiveNameString(directiveRef)
|
||||||
|
if _, ok := r.directives[directiveName]; ok {
|
||||||
|
refsForDeletion = append(refsForDeletion, directiveRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete directives
|
||||||
|
r.operation.RemoveDirectivesFromNode(ast.Node{Kind: ast.NodeKindFieldDefinition, Ref: ref}, refsForDeletion)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRemoveInterfaceDefinitionDirective(directives ...string) *removeInterfaceDefinitionDirective {
|
||||||
|
directivesSet := make(map[string]struct{}, len(directives))
|
||||||
|
for _, directive := range directives {
|
||||||
|
directivesSet[directive] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &removeInterfaceDefinitionDirective{
|
||||||
|
directives: directivesSet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeInterfaceDefinitionDirective struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
operation *ast.Document
|
||||||
|
directives map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeInterfaceDefinitionDirective) Register(walker *astvisitor.Walker) {
|
||||||
|
walker.RegisterEnterDocumentVisitor(r)
|
||||||
|
walker.RegisterEnterInterfaceTypeDefinitionVisitor(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeInterfaceDefinitionDirective) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
r.operation = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeInterfaceDefinitionDirective) EnterInterfaceTypeDefinition(ref int) {
|
||||||
|
var refsForDeletion []int
|
||||||
|
// select fields for deletion
|
||||||
|
for _, directiveRef := range r.operation.InterfaceTypeDefinitions[ref].Directives.Refs {
|
||||||
|
directiveName := r.operation.DirectiveNameString(directiveRef)
|
||||||
|
if _, ok := r.directives[directiveName]; ok {
|
||||||
|
refsForDeletion = append(refsForDeletion, directiveRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete directives
|
||||||
|
r.operation.RemoveDirectivesFromNode(ast.Node{Kind: ast.NodeKindInterfaceTypeDefinition, Ref: ref}, refsForDeletion)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRemoveObjectTypeDefinitionDirective(directives ...string) *removeObjectTypeDefinitionDirective {
|
||||||
|
directivesSet := make(map[string]struct{}, len(directives))
|
||||||
|
for _, directive := range directives {
|
||||||
|
directivesSet[directive] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &removeObjectTypeDefinitionDirective{
|
||||||
|
directives: directivesSet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeObjectTypeDefinitionDirective struct {
|
||||||
|
operation *ast.Document
|
||||||
|
directives map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeObjectTypeDefinitionDirective) Register(walker *astvisitor.Walker) {
|
||||||
|
walker.RegisterEnterDocumentVisitor(r)
|
||||||
|
walker.RegisterEnterObjectTypeDefinitionVisitor(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeObjectTypeDefinitionDirective) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
r.operation = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeObjectTypeDefinitionDirective) EnterObjectTypeDefinition(ref int) {
|
||||||
|
var refsForDeletion []int
|
||||||
|
// select fields for deletion
|
||||||
|
for _, directiveRef := range r.operation.ObjectTypeDefinitions[ref].Directives.Refs {
|
||||||
|
directiveName := r.operation.DirectiveNameString(directiveRef)
|
||||||
|
if _, ok := r.directives[directiveName]; ok {
|
||||||
|
refsForDeletion = append(refsForDeletion, directiveRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete directives
|
||||||
|
r.operation.RemoveDirectivesFromNode(ast.Node{Kind: ast.NodeKindObjectTypeDefinition, Ref: ref}, refsForDeletion)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRemoveMergedTypeExtensions() *removeMergedTypeExtensionsVisitor {
|
||||||
|
return &removeMergedTypeExtensionsVisitor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeMergedTypeExtensionsVisitor struct{}
|
||||||
|
|
||||||
|
func (r *removeMergedTypeExtensionsVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
walker.RegisterLeaveDocumentVisitor(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *removeMergedTypeExtensionsVisitor) LeaveDocument(operation, definition *ast.Document) {
|
||||||
|
operation.RemoveMergedTypeExtensions()
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newExtendScalarTypeDefinition() *extendScalarTypeDefinitionVisitor {
|
||||||
|
return &extendScalarTypeDefinitionVisitor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type extendScalarTypeDefinitionVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendScalarTypeDefinitionVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
e.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(e)
|
||||||
|
walker.RegisterEnterScalarTypeExtensionVisitor(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendScalarTypeDefinitionVisitor) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
e.document = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendScalarTypeDefinitionVisitor) EnterScalarTypeExtension(ref int) {
|
||||||
|
nodes, exists := e.document.Index.NodesByNameBytes(e.document.ScalarTypeExtensionNameBytes(ref))
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasExtended := false
|
||||||
|
for i := range nodes {
|
||||||
|
if nodes[i].Kind != ast.NodeKindScalarTypeDefinition {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasExtended {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrSharedTypesMustNotBeExtended(e.document.ScalarTypeExtensionNameString(ref)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.document.ExtendScalarTypeDefinitionByScalarTypeExtension(nodes[i].Ref, ref)
|
||||||
|
hasExtended = true
|
||||||
|
}
|
||||||
|
if !hasExtended {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrExtensionOrphansMustResolveInSupergraph(e.document.ScalarTypeExtensionNameBytes(ref)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/asttransform"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvalidation"
|
||||||
|
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astnormalization"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astparser"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astprinter"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rootOperationTypeDefinitions = `
|
||||||
|
type Query {}
|
||||||
|
type Mutation {}
|
||||||
|
type Subscription {}
|
||||||
|
`
|
||||||
|
|
||||||
|
parseDocumentError = "parse graphql document string: %w"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Visitor interface {
|
||||||
|
Register(walker *astvisitor.Walker)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeAST(ast *ast.Document) error {
|
||||||
|
normalizer := normalizer{}
|
||||||
|
normalizer.setupWalkers()
|
||||||
|
|
||||||
|
return normalizer.normalize(ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeSDLs(SDLs ...string) (string, error) {
|
||||||
|
rawDocs := make([]string, 0, len(SDLs)+1)
|
||||||
|
rawDocs = append(rawDocs, rootOperationTypeDefinitions)
|
||||||
|
rawDocs = append(rawDocs, SDLs...)
|
||||||
|
if validationError := validateSubgraphs(rawDocs[1:]); validationError != nil {
|
||||||
|
return "", validationError
|
||||||
|
}
|
||||||
|
if normalizationError := normalizeSubgraphs(rawDocs[1:]); normalizationError != nil {
|
||||||
|
return "", normalizationError
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, report := astparser.ParseGraphqlDocumentString(strings.Join(rawDocs, "\n"))
|
||||||
|
if report.HasErrors() {
|
||||||
|
return "", fmt.Errorf("parse graphql document string: %w", report)
|
||||||
|
}
|
||||||
|
|
||||||
|
astnormalization.NormalizeSubgraphSDL(&doc, &report)
|
||||||
|
if report.HasErrors() {
|
||||||
|
return "", fmt.Errorf("merge ast: %w", report)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := MergeAST(&doc); err != nil {
|
||||||
|
return "", fmt.Errorf("merge ast: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := astprinter.PrintString(&doc)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("stringify schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSubgraphs(subgraphs []string) error {
|
||||||
|
validator := astvalidation.NewDefinitionValidator(
|
||||||
|
astvalidation.PopulatedTypeBodies(), astvalidation.KnownTypeNames(),
|
||||||
|
)
|
||||||
|
for _, subgraph := range subgraphs {
|
||||||
|
doc, report := astparser.ParseGraphqlDocumentString(subgraph)
|
||||||
|
if err := asttransform.MergeDefinitionWithBaseSchema(&doc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if report.HasErrors() {
|
||||||
|
return fmt.Errorf(parseDocumentError, report)
|
||||||
|
}
|
||||||
|
validator.Validate(&doc, &report)
|
||||||
|
if report.HasErrors() {
|
||||||
|
return fmt.Errorf("validate schema: %w", report)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeSubgraphs(subgraphs []string) error {
|
||||||
|
subgraphNormalizer := astnormalization.NewSubgraphDefinitionNormalizer()
|
||||||
|
for i, subgraph := range subgraphs {
|
||||||
|
doc, report := astparser.ParseGraphqlDocumentString(subgraph)
|
||||||
|
if report.HasErrors() {
|
||||||
|
return fmt.Errorf(parseDocumentError, report)
|
||||||
|
}
|
||||||
|
subgraphNormalizer.NormalizeDefinition(&doc, &report)
|
||||||
|
if report.HasErrors() {
|
||||||
|
return fmt.Errorf("normalize schema: %w", report)
|
||||||
|
}
|
||||||
|
out, err := astprinter.PrintString(&doc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stringify schema: %w", err)
|
||||||
|
}
|
||||||
|
subgraphs[i] = out
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type normalizer struct {
|
||||||
|
walkers []*astvisitor.Walker
|
||||||
|
}
|
||||||
|
|
||||||
|
type entitySet map[string]struct{}
|
||||||
|
|
||||||
|
func (m *normalizer) setupWalkers() {
|
||||||
|
collectedEntities := make(entitySet)
|
||||||
|
visitorGroups := [][]Visitor{
|
||||||
|
{
|
||||||
|
newCollectEntitiesVisitor(collectedEntities),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
newExtendEnumTypeDefinition(),
|
||||||
|
newExtendInputObjectTypeDefinition(),
|
||||||
|
newExtendInterfaceTypeDefinition(collectedEntities),
|
||||||
|
newExtendScalarTypeDefinition(),
|
||||||
|
newExtendUnionTypeDefinition(),
|
||||||
|
newExtendObjectTypeDefinition(collectedEntities),
|
||||||
|
newRemoveEmptyObjectTypeDefinition(),
|
||||||
|
newRemoveMergedTypeExtensions(),
|
||||||
|
},
|
||||||
|
// visitors for cleaning up federated duplicated fields and directives
|
||||||
|
{
|
||||||
|
newRemoveFieldDefinitions("external"),
|
||||||
|
newRemoveDuplicateFieldedSharedTypesVisitor(),
|
||||||
|
newRemoveDuplicateFieldlessSharedTypesVisitor(),
|
||||||
|
newMergeDuplicatedFieldsVisitor(),
|
||||||
|
newRemoveInterfaceDefinitionDirective("key"),
|
||||||
|
newRemoveObjectTypeDefinitionDirective("key"),
|
||||||
|
newRemoveFieldDefinitionDirective("provides", "requires"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, visitorGroup := range visitorGroups {
|
||||||
|
walker := astvisitor.NewWalker(48)
|
||||||
|
for _, visitor := range visitorGroup {
|
||||||
|
visitor.Register(&walker)
|
||||||
|
m.walkers = append(m.walkers, &walker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *normalizer) normalize(operation *ast.Document) error {
|
||||||
|
report := operationreport.Report{}
|
||||||
|
|
||||||
|
for _, walker := range m.walkers {
|
||||||
|
walker.Walk(operation, nil, &report)
|
||||||
|
if report.HasErrors() {
|
||||||
|
return fmt.Errorf("walk: %w", report)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e entitySet) isExtensionForEntity(nameBytes []byte, directiveRefs []int, document *ast.Document) (bool, *operationreport.ExternalError) {
|
||||||
|
name := string(nameBytes)
|
||||||
|
hasDirectives := len(directiveRefs) > 0
|
||||||
|
if _, exists := e[name]; !exists {
|
||||||
|
if !hasDirectives || !isEntityExtension(directiveRefs, document) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
err := operationreport.ErrExtensionWithKeyDirectiveMustExtendEntity(name)
|
||||||
|
return false, &err
|
||||||
|
}
|
||||||
|
if !hasDirectives {
|
||||||
|
err := operationreport.ErrEntityExtensionMustHaveKeyDirective(name)
|
||||||
|
return false, &err
|
||||||
|
}
|
||||||
|
if isEntityExtension(directiveRefs, document) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
err := operationreport.ErrEntityExtensionMustHaveKeyDirective(name)
|
||||||
|
return false, &err
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEntityExtension(directiveRefs []int, document *ast.Document) bool {
|
||||||
|
for _, directiveRef := range directiveRefs {
|
||||||
|
if document.DirectiveNameString(directiveRef) == "key" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func multipleExtensionError(isEntity bool, nameBytes []byte) *operationreport.ExternalError {
|
||||||
|
if isEntity {
|
||||||
|
err := operationreport.ErrEntitiesMustNotBeDuplicated(string(nameBytes))
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
err := operationreport.ErrSharedTypesMustNotBeExtended(string(nameBytes))
|
||||||
|
return &err
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import "github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
|
||||||
|
type fieldlessSharedType interface {
|
||||||
|
areValuesIdentical(valueRefsToCompare []int) bool
|
||||||
|
valueRefs() []int
|
||||||
|
valueName(ref int) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func createValueSet(f fieldlessSharedType) map[string]bool {
|
||||||
|
valueSet := make(map[string]bool)
|
||||||
|
for _, valueRef := range f.valueRefs() {
|
||||||
|
valueSet[f.valueName(valueRef)] = true
|
||||||
|
}
|
||||||
|
return valueSet
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldedSharedType struct {
|
||||||
|
document *ast.Document
|
||||||
|
fieldKind ast.NodeKind
|
||||||
|
fieldRefs []int
|
||||||
|
fieldSet map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFieldedSharedType(document *ast.Document, fieldKind ast.NodeKind, fieldRefs []int) fieldedSharedType {
|
||||||
|
f := fieldedSharedType{
|
||||||
|
document,
|
||||||
|
fieldKind,
|
||||||
|
fieldRefs,
|
||||||
|
nil,
|
||||||
|
}
|
||||||
|
f.createFieldSet()
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fieldedSharedType) areFieldsIdentical(fieldRefsToCompare []int) bool {
|
||||||
|
if len(f.fieldRefs) != len(fieldRefsToCompare) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, fieldRef := range fieldRefsToCompare {
|
||||||
|
actualFieldName := f.fieldName(fieldRef)
|
||||||
|
expectedTypeRef, exists := f.fieldSet[actualFieldName]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
actualTypeRef := f.fieldTypeRef(fieldRef)
|
||||||
|
if !f.document.TypesAreCompatibleDeep(expectedTypeRef, actualTypeRef) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fieldedSharedType) createFieldSet() {
|
||||||
|
fieldSet := make(map[string]int)
|
||||||
|
for _, fieldRef := range f.fieldRefs {
|
||||||
|
fieldSet[f.fieldName(fieldRef)] = f.fieldTypeRef(fieldRef)
|
||||||
|
}
|
||||||
|
f.fieldSet = fieldSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fieldedSharedType) fieldName(ref int) string {
|
||||||
|
switch f.fieldKind {
|
||||||
|
case ast.NodeKindInputValueDefinition:
|
||||||
|
return f.document.InputValueDefinitionNameString(ref)
|
||||||
|
default:
|
||||||
|
return f.document.FieldDefinitionNameString(ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fieldedSharedType) fieldTypeRef(ref int) int {
|
||||||
|
switch f.fieldKind {
|
||||||
|
case ast.NodeKindInputValueDefinition:
|
||||||
|
return f.document.InputValueDefinitions[ref].Type
|
||||||
|
default:
|
||||||
|
return f.document.FieldDefinitions[ref].Type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type enumSharedType struct {
|
||||||
|
*ast.EnumTypeDefinition
|
||||||
|
document *ast.Document
|
||||||
|
valueSet map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEnumSharedType(document *ast.Document, ref int) enumSharedType {
|
||||||
|
e := enumSharedType{
|
||||||
|
&document.EnumTypeDefinitions[ref],
|
||||||
|
document,
|
||||||
|
nil,
|
||||||
|
}
|
||||||
|
e.valueSet = createValueSet(e)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e enumSharedType) areValuesIdentical(valueRefsToCompare []int) bool {
|
||||||
|
if len(e.valueRefs()) != len(valueRefsToCompare) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, valueRefToCompare := range valueRefsToCompare {
|
||||||
|
name := e.valueName(valueRefToCompare)
|
||||||
|
if !e.valueSet[name] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e enumSharedType) valueRefs() []int {
|
||||||
|
return e.EnumValuesDefinition.Refs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e enumSharedType) valueName(ref int) string {
|
||||||
|
return e.document.EnumValueDefinitionNameString(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unionSharedType struct {
|
||||||
|
*ast.UnionTypeDefinition
|
||||||
|
document *ast.Document
|
||||||
|
valueSet map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnionSharedType(document *ast.Document, ref int) unionSharedType {
|
||||||
|
u := unionSharedType{
|
||||||
|
&document.UnionTypeDefinitions[ref],
|
||||||
|
document,
|
||||||
|
nil,
|
||||||
|
}
|
||||||
|
u.valueSet = createValueSet(u)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u unionSharedType) areValuesIdentical(valueRefsToCompare []int) bool {
|
||||||
|
if len(u.valueRefs()) != len(valueRefsToCompare) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, refToCompare := range valueRefsToCompare {
|
||||||
|
name := u.valueName(refToCompare)
|
||||||
|
if !u.valueSet[name] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u unionSharedType) valueRefs() []int {
|
||||||
|
return u.UnionMemberTypes.Refs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u unionSharedType) valueName(ref int) string {
|
||||||
|
return u.document.TypeNameString(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
type scalarSharedType struct{}
|
||||||
|
|
||||||
|
func (_ scalarSharedType) areValuesIdentical(_ []int) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ scalarSharedType) valueRefs() []int {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ scalarSharedType) valueName(_ int) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package sdlmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
|
||||||
|
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newExtendUnionTypeDefinition() *extendUnionTypeDefinitionVisitor {
|
||||||
|
return &extendUnionTypeDefinitionVisitor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type extendUnionTypeDefinitionVisitor struct {
|
||||||
|
*astvisitor.Walker
|
||||||
|
document *ast.Document
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendUnionTypeDefinitionVisitor) Register(walker *astvisitor.Walker) {
|
||||||
|
e.Walker = walker
|
||||||
|
walker.RegisterEnterDocumentVisitor(e)
|
||||||
|
walker.RegisterEnterUnionTypeExtensionVisitor(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendUnionTypeDefinitionVisitor) EnterDocument(operation, _ *ast.Document) {
|
||||||
|
e.document = operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extendUnionTypeDefinitionVisitor) EnterUnionTypeExtension(ref int) {
|
||||||
|
nodes, exists := e.document.Index.NodesByNameBytes(e.document.UnionTypeExtensionNameBytes(ref))
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasExtended := false
|
||||||
|
for i := range nodes {
|
||||||
|
if nodes[i].Kind != ast.NodeKindUnionTypeDefinition {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasExtended {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrSharedTypesMustNotBeExtended(e.document.UnionTypeExtensionNameString(ref)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.document.ExtendUnionTypeDefinitionByUnionTypeExtension(nodes[i].Ref, ref)
|
||||||
|
hasExtended = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasExtended {
|
||||||
|
e.StopWithExternalErr(operationreport.ErrExtensionOrphansMustResolveInSupergraph(e.document.UnionTypeExtensionNameBytes(ref)))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user