Skip to content

Commit

Permalink
Support the object extension (#49)
Browse files Browse the repository at this point in the history
* parse object directives

* support object extension

* add change log
  • Loading branch information
mununki authored Mar 5, 2024
1 parent b52b375 commit 50dcb22
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 12 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## v0.2.13

- Support the object extension https://github.com/mununki/gqlmerge/pull/49

## v0.2.12

- Fix where the undefined operation type is generated https://github.com/mununki/gqlmerge/pull/47
Expand Down
1 change: 1 addition & 0 deletions lib/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type Type struct {
Fields []*Field
Directives []*Directive
Descriptions *[]string
Extend bool
}

type Arg struct {
Expand Down
5 changes: 5 additions & 0 deletions lib/lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const (
tokEnum // enum
tokScalar // scalar
tokUnion // union
tokExtend // extend
tokSchema // schema
)

Expand Down Expand Up @@ -134,6 +135,8 @@ func (typ tokenType) String() string {
return "scalar"
case tokUnion:
return "union"
case tokExtend:
return "extend"
case tokSchema:
return "schema"
default:
Expand Down Expand Up @@ -328,6 +331,8 @@ func (l *lexer) next() *token {
return mkToken(tokScalar, "scalar")
case "union":
return mkToken(tokUnion, "union")
case "extend":
return mkToken(tokExtend, "extend")
case "schema":
return mkToken(tokSchema, "schema")
default:
Expand Down
41 changes: 29 additions & 12 deletions lib/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func (sc *Schema) ReadSchema(path string) {
}

func (s *Schema) Parse(p *Parser) {
isExtended := false
for {
tok := p.lex.next()
if tok.typ == tokEOF {
Expand Down Expand Up @@ -133,6 +134,9 @@ func (s *Schema) Parse(p *Parser) {
}
s.DirectiveDefinitions = append(s.DirectiveDefinitions, &d)

case tokExtend:
isExtended = true

case tokScalar:
c := Scalar{}
c.Filename = p.lex.filename
Expand Down Expand Up @@ -346,16 +350,22 @@ func (s *Schema) Parse(p *Parser) {

case tokType:
t := Type{}
t.Extend = isExtended
isExtended = false
t.Filename = p.lex.filename
t.Line = p.lex.line
t.Column = p.lex.col
t.Descriptions = p.bufString()
name, _ := p.lex.consumeIdent()
t.Name = name.String()
t.Descriptions = p.bufString()
t.Directives = p.parseDirectives()

next := p.lex.next()
switch next.typ {
case tokImplements:
if len(t.Directives) > 0 {
errorf(`%s:%d:%d: directives cann't be placed in front of implements`, p.lex.filename, p.lex.line, p.lex.col)
}
t.Impl = true
name, _ := p.lex.consumeIdent()
t.ImplTypes = append(t.ImplTypes, name.String())
Expand All @@ -364,6 +374,7 @@ func (s *Schema) Parse(p *Parser) {
name, _ = p.lex.consumeIdent()
t.ImplTypes = append(t.ImplTypes, name.String())
}
t.Directives = p.parseDirectives()
p.lex.consumeToken(tokLBrace)
fallthrough
case tokLBrace:
Expand Down Expand Up @@ -531,22 +542,28 @@ func (s *Schema) MergeTypeName(wg *sync.WaitGroup) {
if _, ok := seen[v.Name]; ok {
for i := 0; i < j; i++ {
if s.Types[i].Name == v.Name {
if reflect.DeepEqual(s.Types[i].ImplTypes, v.ImplTypes) && IsEqualWithoutDescriptions(s.Types[i].Directives, v.Directives) {
if v.Extend {
s.Types[i].Fields = mergeFields(s.Types[i].Fields, v.Fields)
mergeDescriptionsAndComments(s.Types[i].Directives, v.Directives)
s.Types[i].Directives = mergeDirectives(s.Types[i].Directives, v.Directives)
break
} else {
if reflect.DeepEqual(s.Types[i].ImplTypes, v.ImplTypes) && IsEqualWithoutDescriptions(s.Types[i].Directives, v.Directives) {
s.Types[i].Fields = mergeFields(s.Types[i].Fields, v.Fields)
mergeDescriptionsAndComments(s.Types[i].Directives, v.Directives)
break
} else {

rel1, err := GetRelPath(s.Types[i].Filename)
if err != nil {
panic(err)
}
rel2, err := GetRelPath(v.Filename)
if err != nil {
panic(err)
}
rel1, err := GetRelPath(s.Types[i].Filename)
if err != nil {
panic(err)
}
rel2, err := GetRelPath(v.Filename)
if err != nil {
panic(err)
}

errorf("Duplicated Types: %s(%s:%v:%v) and (%s:%v:%v)", s.Types[i].Name, *rel1, s.Types[i].Line, s.Types[i].Column, *rel2, v.Line, v.Column)
errorf("Duplicated Types: %s(%s:%v:%v) and (%s:%v:%v)", s.Types[i].Name, *rel1, s.Types[i].Line, s.Types[i].Column, *rel2, v.Line, v.Column)
}
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions lib/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,39 @@ func TestGetSchema(t *testing.T) {
}

}

func TestMergeDirectives(t *testing.T) {
str := make([]string, 0)
a := []*Directive{
{
Name: "talkable",
DirectiveArgs: []*DirectiveArg{},
Descriptions: &str,
},
}
b := []*Directive{
{
Name: "talkable",
DirectiveArgs: []*DirectiveArg{},
Descriptions: &str,
},
}
c := []*Directive{
{
Name: "walkable",
DirectiveArgs: []*DirectiveArg{},
Descriptions: &str,
},
}
ds := mergeDirectives(a, b)

if len(ds) == 0 {
t.Fatal("should be more than 0")
}

ds = mergeDirectives(a, c)

if len(ds) == 0 || len(ds) == 1 {
t.Fatal("should be more than 1")
}
}
75 changes: 75 additions & 0 deletions lib/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,78 @@ func mergeDescriptionsAndComments(a, b interface{}) {
}
}
}

func mergeDirectiveArgs(a, b []*DirectiveArg) []*DirectiveArg {
merged := make([]*DirectiveArg, len(a))
copy(merged, a)

for _, bArg := range b {
found := false
for i, mArg := range merged {
if mArg.Name == bArg.Name && compareValuesAndIsList(mArg, bArg) {
merged[i].Descriptions = mergeDescriptions(mArg.Descriptions, bArg.Descriptions)
found = true
break
}
}
if !found {
merged = append(merged, bArg)
}
}
return merged
}

func compareValuesAndIsList(a, b *DirectiveArg) bool {
if a.IsList != b.IsList {
return false
}
if len(a.Value) != len(b.Value) {
return false
}
for i := range a.Value {
if a.Value[i] != b.Value[i] {
return false
}
}
return true
}

func mergeDescriptions(a, b *[]string) *[]string {
if a == nil && b == nil {
return nil
}
if a == nil {
return b
}
if b == nil {
return a
}
merged := make([]string, len(*a)+len(*b))
copy(merged, *a)
merged = append(merged, *b...)
return &merged
}

func mergeDirectives(a, b []*Directive) []*Directive {
directiveMap := make(map[string]*Directive)

for _, dir := range a {
directiveMap[dir.Name] = dir
}

for _, dirB := range b {
if dirA, exists := directiveMap[dirB.Name]; exists {
dirA.DirectiveArgs = mergeDirectiveArgs(dirA.DirectiveArgs, dirB.DirectiveArgs)
dirA.Descriptions = mergeDescriptions(dirA.Descriptions, dirB.Descriptions)
} else {
directiveMap[dirB.Name] = dirB
}
}

merged := make([]*Directive, 0, len(directiveMap))
for _, dir := range directiveMap {
merged = append(merged, dir)
}

return merged
}
1 change: 1 addition & 0 deletions lib/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func (ms *MergedSchema) WriteSchema(s *Schema) string {
if len(t.ImplTypes) > 0 {
ms.buf.WriteString(" implements " + strings.Join(t.ImplTypes, " & "))
}
ms.stitchDirectives(t.Directives)
ms.buf.WriteString(" {\n")
for _, p := range t.Fields {
ms.writeDescriptions(p.Descriptions, 1, false)
Expand Down
14 changes: 14 additions & 0 deletions test/directives/generated.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type Person @paint {
name: String
age: Int
picture: Url
}

type ExampleType implements Node @deprecated {
id: ID
oldField: String
}




10 changes: 10 additions & 0 deletions test/directives/schema/object.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type Person @paint {
name: String
age: Int
picture: Url
}

type ExampleType implements Node @deprecated {
id: ID
oldField: String
}
11 changes: 11 additions & 0 deletions test/object_extension/generated.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type Person implements Node @talkable @walkable {
id: ID!
createTime: Time!
updateTime: Time!
name: String!
hasCar: Boolean!
}




6 changes: 6 additions & 0 deletions test/object_extension/schema/a.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type Person implements Node @talkable{
id: ID!
createTime: Time!
updateTime: Time!
name: String!
}
3 changes: 3 additions & 0 deletions test/object_extension/schema/b.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extend type Person @walkable {
hasCar: Boolean!
}

0 comments on commit 50dcb22

Please sign in to comment.