From 60f675a8874fb3978af6594b686a13fbe60f082a Mon Sep 17 00:00:00 2001 From: alberttjin Date: Wed, 4 Jan 2023 12:45:59 -0800 Subject: [PATCH 1/3] federation: add obj passing support Sometimes its easier to define an object as a federation key rather than re-fetching the object from the db. It can also be more cost efficient if its difficult to fetch it from the database. However, to support this, the selection set needs to recursively list all fields under all objects that are selected as keys. This commit adds that support and also a comment explanation --- federation/executor_test.go | 174 +++++++++++++++++++++++++++++++++- federation/planner.go | 14 +-- federation/planner_helpers.go | 165 ++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+), 13 deletions(-) create mode 100644 federation/planner_helpers.go diff --git a/federation/executor_test.go b/federation/executor_test.go index 8e33ece8..ce8f6312 100644 --- a/federation/executor_test.go +++ b/federation/executor_test.go @@ -51,6 +51,22 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem } } */ + + type Abilities struct { + Teleportation bool + BreathingFire []int + Thunder *bool + } + + type Skills struct { + Sports []string + Plumbing *bool + Eating int + Abilities Abilities + AbilitiesPointer *Abilities + AllAbilities []*Abilities + } + type User struct { Id int64 OrgId int64 @@ -62,6 +78,7 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem user := s1.Object("User", User{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*User }) []*User { return args.Keys })) + user.Key("id") type UserIds struct { Id int64 @@ -156,6 +173,15 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem Name string Email string PhoneNumber string + Skills Skills + } + + type UserWithContactInfoKeys struct { + Id int64 + OrgId int64 + Name string + Email string + PhoneNumber string } type UserKeysWithOrgId struct { @@ -163,14 +189,42 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem OrgId int64 } s2 := schemabuilder.NewSchemaWithName("s2") - userWithContactInfo := s2.Object("User", UserWithContactInfo{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*UserWithContactInfo }) []*UserWithContactInfo { + userWithContactInfo := s2.Object("User", UserWithContactInfo{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*UserWithContactInfoKeys }) []*UserWithContactInfo { + help := []*UserWithContactInfo{} + plumbing := true + thunder := true + sampleAbilities := Abilities{Teleportation: true, BreathingFire: []int{1, 2, 3}} + sampleAbilities2 := Abilities{Teleportation: true, BreathingFire: []int{4, 5, 6}, Thunder: &thunder} + sampleSkills := Skills{Eating: 1, Plumbing: &plumbing, Sports: nil, Abilities: sampleAbilities, AbilitiesPointer: &sampleAbilities2, AllAbilities: []*Abilities{&sampleAbilities, &sampleAbilities2}} + for _, i := range args.Keys { + new := &UserWithContactInfo{ + Id: i.Id, + OrgId: i.OrgId, + Name: i.Name, + Email: i.Email, + PhoneNumber: i.PhoneNumber, + Skills: sampleSkills, + } + help = append(help, new) + } + return help + })) + s2.Object("Skills", Skills{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Skills }) []*Skills { return args.Keys })) + s2.Object("Abilities", Abilities{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Abilities }) []*Abilities { + return args.Keys + })) + userWithContactInfo.Key("id") userWithContactInfo.FieldFunc("secret", func(ctx context.Context, user *UserWithContactInfo) (string, error) { return "shhhhh", nil }) + userWithContactInfo.FieldFunc("skills", func(ctx context.Context, user *UserWithContactInfo) (Skills, error) { + return user.Skills, nil + }) + /* ---------------------------- Pagination Endpoints/Objects @@ -453,6 +507,124 @@ func TestExecutorQueriesBasic(t *testing.T) { } } +// This function checks that we are able to query for and receive an object that was passed between gql servers +// Specifically, it tests the functions in planner_helpers.go +func TestExecutorQueriesWithObjectKey(t *testing.T) { + e, _, _, _, err := createExecutorWithFederatedUser() + require.NoError(t, err) + testCases := []struct { + Name string + Query string + Output string + }{ + { + Name: "query fields on multiple fields with object passthrough as well as nested object", + Query: ` + query Foo { + users { + id + email + phoneNumber + isAdmin + secret + skills { + sports + plumbing + eating + abilities { + teleportation + breathingFire + } + abilitiesPointer{ + teleportation + breathingFire + thunder + } + allAbilities { + teleportation + breathingFire + } + } + } + }`, + Output: ` + { + "users":[ + { + "__key":1, + "id":1, + "email": "email@gmail.com", + "phoneNumber": "555-5555", + "isAdmin":true, + "secret": "shhhhh", + "skills": { + "sports": [], + "plumbing": true, + "eating": 1, + "abilities": { + "teleportation": true, + "breathingFire": [1, 2, 3] + }, + "abilitiesPointer": { + "teleportation": true, + "breathingFire": [4, 5, 6], + "thunder": true + }, + "allAbilities": [ + { + "teleportation": true, + "breathingFire": [1, 2, 3] + }, + { + "teleportation": true, + "breathingFire": [4, 5, 6] + } + ] + } + },{ + "__key":2, + "id":2, + "email": "email@gmail.com", + "phoneNumber": "555-5555", + "isAdmin":true, + "secret": "shhhhh", + "skills": { + "sports": [], + "plumbing": true, + "eating": 1, + "abilities": { + "teleportation": true, + "breathingFire": [1, 2, 3] + }, + "abilitiesPointer": { + "teleportation": true, + "breathingFire": [4, 5, 6], + "thunder": true + }, + "allAbilities": [ + { + "teleportation": true, + "breathingFire": [1, 2, 3] + }, + { + "teleportation": true, + "breathingFire": [4, 5, 6] + } + ] + } + } + ] + }`, + }, + } + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + ctx := context.Background() + runAndValidateQueryResults(t, ctx, e, testCase.Query, testCase.Output) + }) + } +} + func TestExecutorQueriesPagination(t *testing.T) { e, _, _, _, err := createExecutorWithFederatedUser() require.NoError(t, err) diff --git a/federation/planner.go b/federation/planner.go index d2f7d081..a33f5902 100644 --- a/federation/planner.go +++ b/federation/planner.go @@ -298,18 +298,8 @@ func (e *Planner) planObject(typ *graphql.Object, selectionSet *graphql.Selectio // id (federatedKey) // } selections := make([]*graphql.Selection, 0, len(typ.Fields)) - for name, field := range typ.Fields { - for service := range field.FederatedKey { - if len(selectionsByService[service]) > 0 { - selections = append(selections, &graphql.Selection{ - Name: name, - Alias: name, - UnparsedArgs: map[string]interface{}{}, - }) - break - } - } - } + ssResult := getFederatedSelectionsForObject(typ, service, selectionsByService) + selections = ssResult.Selections federatedSelection := &graphql.Selection{ Name: federationField, diff --git a/federation/planner_helpers.go b/federation/planner_helpers.go new file mode 100644 index 00000000..aed6e563 --- /dev/null +++ b/federation/planner_helpers.go @@ -0,0 +1,165 @@ +package federation + +import "github.com/samsarahq/thunder/graphql" + +/* +----------------------------------------- +Begin: Recursive Selection Set Generation +----------------------------------------- +This code is used to recursively generate a selection set for an object. The purpose is to allow for passing entire object (not scalar) +fields from team gql service -> team gql service. This can be used when the object fetch from db is too complicated/expensive and its easier +to simply pass the object rather than refetch it from keys. +Example: +Let's say we have object Devices that has been federated: + type Devices struct + { + Id int | key + Location Location | key + Optional str + } + type Location struct { + lat str | key + lng str | key + altitude AltitudeInput + } +And in fetchObjectFromKeys, we have defined keys as stated above. Location is an object, so for it to be a key the +selection set needs to recursively have its fields. +This is a simplified plan for a query that queries for Id and Location on devices. The selection set is generated by the code below + Plan { + Type: Devices + Service: alpha + SelectionSet: { + Id + Location + lat + lng + } + } + +NOTE: In this example, Location also needs to be a federated object. In other words, it needs to be defined on this server with a corresponding +fetchObjectFromKeys function +*/ +func getFederatedSelectionsForObject(typ *graphql.Object, service string, selectionsByService map[string][]*graphql.Selection) *graphql.SelectionSet { + selections := []*graphql.Selection{} + + for name, field := range typ.Fields { + if name == "_federation" { + continue + } + + serviceFound := false + for service := range field.FederatedKey { + if len(selectionsByService[service]) > 0 { + serviceFound = true + break + } + } + + if !serviceFound { + continue + } + + switch fieldType := field.Type.(type) { + case *graphql.Object: + ssResult := getFederatedSelectionsForObject(fieldType, service, selectionsByService) + selections = append(selections, &graphql.Selection{ + Name: name, + Alias: name, + UnparsedArgs: map[string]interface{}{}, + SelectionSet: ssResult, + }) + case *graphql.NonNull: + ssResult := getFederatedSelectionsForNonNull(fieldType, name, service, selectionsByService) + selections = append(selections, ssResult) + case *graphql.List: + ssResult := getFederatedSelectionsForList(fieldType, name, service, selectionsByService) + selections = append(selections, ssResult) + case *graphql.Scalar: + selections = append(selections, &graphql.Selection{ + Name: name, + Alias: name, + UnparsedArgs: map[string]interface{}{}, + }) + default: + selections = append(selections, &graphql.Selection{ + Name: name, + Alias: name, + UnparsedArgs: map[string]interface{}{}, + }) + } + } + + return &graphql.SelectionSet{ + Selections: selections, + } +} + +func getFederatedSelectionsForList(typ *graphql.List, name string, service string, selectionsByService map[string][]*graphql.Selection) *graphql.Selection { + var selectionResult *graphql.Selection + switch fieldType := typ.Type.(type) { + case *graphql.Object: + ssResult := getFederatedSelectionsForObject(fieldType, service, selectionsByService) + selectionResult = &graphql.Selection{ + Name: name, + Alias: name, + UnparsedArgs: map[string]interface{}{}, + SelectionSet: ssResult, + } + case *graphql.NonNull: + selectionResult = getFederatedSelectionsForNonNull(fieldType, name, service, selectionsByService) + case *graphql.List: + selectionResult = getFederatedSelectionsForList(fieldType, name, service, selectionsByService) + case *graphql.Scalar: + selectionResult = &graphql.Selection{ + Name: name, + Alias: name, + UnparsedArgs: map[string]interface{}{}, + } + default: + selectionResult = &graphql.Selection{ + Name: name, + Alias: name, + UnparsedArgs: map[string]interface{}{}, + } + } + + return selectionResult +} + +func getFederatedSelectionsForNonNull(typ *graphql.NonNull, name string, service string, selectionsByService map[string][]*graphql.Selection) *graphql.Selection { + var selectionResult *graphql.Selection + switch fieldType := typ.Type.(type) { + case *graphql.Object: + ssResult := getFederatedSelectionsForObject(fieldType, service, selectionsByService) + selectionResult = &graphql.Selection{ + Name: name, + Alias: name, + UnparsedArgs: map[string]interface{}{}, + SelectionSet: ssResult, + } + case *graphql.NonNull: + selectionResult = getFederatedSelectionsForNonNull(fieldType, name, service, selectionsByService) + case *graphql.List: + selectionResult = getFederatedSelectionsForList(fieldType, name, service, selectionsByService) + case *graphql.Scalar: + selectionResult = &graphql.Selection{ + Name: name, + Alias: name, + UnparsedArgs: map[string]interface{}{}, + } + default: + selectionResult = &graphql.Selection{ + Name: name, + Alias: name, + UnparsedArgs: map[string]interface{}{}, + } + } + + return selectionResult +} + +/* +--------------------------------------- +End: Recursive Selection Set Generation +--------------------------------------- +*/ \ No newline at end of file From d301c77aed079579c68d5c66d1be2ac36fc5f6b6 Mon Sep 17 00:00:00 2001 From: alberttjin Date: Thu, 12 Jan 2023 17:03:06 -0800 Subject: [PATCH 2/3] r --- federation/executor_test.go | 93 +++++++++++++++---------------------- 1 file changed, 38 insertions(+), 55 deletions(-) diff --git a/federation/executor_test.go b/federation/executor_test.go index ce8f6312..615039dc 100644 --- a/federation/executor_test.go +++ b/federation/executor_test.go @@ -73,12 +73,20 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem Name string Email string PhoneNumber string + UserSkills Skills } s1 := schemabuilder.NewSchemaWithName("s1") user := s1.Object("User", User{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*User }) []*User { return args.Keys })) + s1.Object("UserSkills", Skills{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Skills }) []*Skills { + return args.Keys + })) + s1.Object("Abilities", Abilities{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Abilities }) []*Abilities { + return args.Keys + })) + user.Key("id") type UserIds struct { Id int64 @@ -86,8 +94,14 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem } s1.Query().FieldFunc("users", func(ctx context.Context) ([]*User, error) { users := make([]*User, 0, 1) - users = append(users, &User{Id: int64(1), OrgId: int64(1), Name: "testUser", Email: "email@gmail.com", PhoneNumber: "555-5555"}) - users = append(users, &User{Id: int64(2), OrgId: int64(2), Name: "testUser2", Email: "email@gmail.com", PhoneNumber: "555-5555"}) + plumbing := true + thunder := true + sampleAbilities := Abilities{Teleportation: true, BreathingFire: []int{1, 2, 3}} + sampleAbilities2 := Abilities{Teleportation: true, BreathingFire: []int{4, 5, 6}, Thunder: &thunder} + sampleSkills := Skills{Eating: 1, Plumbing: &plumbing, Sports: nil, Abilities: sampleAbilities, AllAbilities: []*Abilities{&sampleAbilities, &sampleAbilities2}} + sampleSkills2 := Skills{Eating: 2, Plumbing: &plumbing, Sports: []string{"basketball", "swimming", "tennis"}, AbilitiesPointer: &sampleAbilities2} + users = append(users, &User{Id: int64(1), OrgId: int64(1), Name: "testUser", Email: "email@gmail.com", PhoneNumber: "555-5555", UserSkills: sampleSkills}) + users = append(users, &User{Id: int64(2), OrgId: int64(2), Name: "testUser2", Email: "email@gmail.com", PhoneNumber: "555-5555", UserSkills: sampleSkills2}) return users, nil }) s1.Query().FieldFunc("emptyusers", func(ctx context.Context) ([]*User, error) { @@ -173,15 +187,7 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem Name string Email string PhoneNumber string - Skills Skills - } - - type UserWithContactInfoKeys struct { - Id int64 - OrgId int64 - Name string - Email string - PhoneNumber string + UserSkills Skills } type UserKeysWithOrgId struct { @@ -189,27 +195,10 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem OrgId int64 } s2 := schemabuilder.NewSchemaWithName("s2") - userWithContactInfo := s2.Object("User", UserWithContactInfo{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*UserWithContactInfoKeys }) []*UserWithContactInfo { - help := []*UserWithContactInfo{} - plumbing := true - thunder := true - sampleAbilities := Abilities{Teleportation: true, BreathingFire: []int{1, 2, 3}} - sampleAbilities2 := Abilities{Teleportation: true, BreathingFire: []int{4, 5, 6}, Thunder: &thunder} - sampleSkills := Skills{Eating: 1, Plumbing: &plumbing, Sports: nil, Abilities: sampleAbilities, AbilitiesPointer: &sampleAbilities2, AllAbilities: []*Abilities{&sampleAbilities, &sampleAbilities2}} - for _, i := range args.Keys { - new := &UserWithContactInfo{ - Id: i.Id, - OrgId: i.OrgId, - Name: i.Name, - Email: i.Email, - PhoneNumber: i.PhoneNumber, - Skills: sampleSkills, - } - help = append(help, new) - } - return help + userWithContactInfo := s2.Object("User", UserWithContactInfo{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*UserWithContactInfo }) []*UserWithContactInfo { + return args.Keys })) - s2.Object("Skills", Skills{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Skills }) []*Skills { + s2.Object("UserSkills", Skills{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Skills }) []*Skills { return args.Keys })) s2.Object("Abilities", Abilities{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Abilities }) []*Abilities { @@ -221,8 +210,8 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem return "shhhhh", nil }) - userWithContactInfo.FieldFunc("skills", func(ctx context.Context, user *UserWithContactInfo) (Skills, error) { - return user.Skills, nil + userWithContactInfo.FieldFunc("getSkills", func(ctx context.Context, user *UserWithContactInfo) (Skills, error) { + return user.UserSkills, nil }) /* @@ -347,6 +336,7 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem Name string Email string PhoneNumber string + UserSkills Skills } type UserKeys struct { Id int64 @@ -359,6 +349,12 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem } return users })) + s3.Object("UserSkills", Skills{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Skills }) []*Skills { + return args.Keys + })) + s3.Object("Abilities", Abilities{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Abilities }) []*Abilities { + return args.Keys + })) userWithAdminPrivelages.Key("id") userWithAdminPrivelages.FieldFunc("isAdmin", func(ctx context.Context, user *UserWithAdminPrivelages) (bool, error) { return true, nil @@ -527,7 +523,7 @@ func TestExecutorQueriesWithObjectKey(t *testing.T) { phoneNumber isAdmin secret - skills { + getSkills { sports plumbing eating @@ -557,7 +553,7 @@ func TestExecutorQueriesWithObjectKey(t *testing.T) { "phoneNumber": "555-5555", "isAdmin":true, "secret": "shhhhh", - "skills": { + "getSkills": { "sports": [], "plumbing": true, "eating": 1, @@ -565,11 +561,7 @@ func TestExecutorQueriesWithObjectKey(t *testing.T) { "teleportation": true, "breathingFire": [1, 2, 3] }, - "abilitiesPointer": { - "teleportation": true, - "breathingFire": [4, 5, 6], - "thunder": true - }, + "abilitiesPointer": null, "allAbilities": [ { "teleportation": true, @@ -588,29 +580,20 @@ func TestExecutorQueriesWithObjectKey(t *testing.T) { "phoneNumber": "555-5555", "isAdmin":true, "secret": "shhhhh", - "skills": { - "sports": [], + "getSkills": { + "sports": ["basketball", "swimming", "tennis"], "plumbing": true, - "eating": 1, + "eating": 2, "abilities": { - "teleportation": true, - "breathingFire": [1, 2, 3] + "teleportation": false, + "breathingFire": [] }, "abilitiesPointer": { "teleportation": true, "breathingFire": [4, 5, 6], "thunder": true }, - "allAbilities": [ - { - "teleportation": true, - "breathingFire": [1, 2, 3] - }, - { - "teleportation": true, - "breathingFire": [4, 5, 6] - } - ] + "allAbilities": [] } } ] From 453231c1c00e066484ca6693dd33c6bfcf775e18 Mon Sep 17 00:00:00 2001 From: alberttjin Date: Thu, 12 Jan 2023 17:03:58 -0800 Subject: [PATCH 3/3] r --- federation/executor_test.go | 93 ++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/federation/executor_test.go b/federation/executor_test.go index 615039dc..ce8f6312 100644 --- a/federation/executor_test.go +++ b/federation/executor_test.go @@ -73,20 +73,12 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem Name string Email string PhoneNumber string - UserSkills Skills } s1 := schemabuilder.NewSchemaWithName("s1") user := s1.Object("User", User{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*User }) []*User { return args.Keys })) - s1.Object("UserSkills", Skills{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Skills }) []*Skills { - return args.Keys - })) - s1.Object("Abilities", Abilities{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Abilities }) []*Abilities { - return args.Keys - })) - user.Key("id") type UserIds struct { Id int64 @@ -94,14 +86,8 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem } s1.Query().FieldFunc("users", func(ctx context.Context) ([]*User, error) { users := make([]*User, 0, 1) - plumbing := true - thunder := true - sampleAbilities := Abilities{Teleportation: true, BreathingFire: []int{1, 2, 3}} - sampleAbilities2 := Abilities{Teleportation: true, BreathingFire: []int{4, 5, 6}, Thunder: &thunder} - sampleSkills := Skills{Eating: 1, Plumbing: &plumbing, Sports: nil, Abilities: sampleAbilities, AllAbilities: []*Abilities{&sampleAbilities, &sampleAbilities2}} - sampleSkills2 := Skills{Eating: 2, Plumbing: &plumbing, Sports: []string{"basketball", "swimming", "tennis"}, AbilitiesPointer: &sampleAbilities2} - users = append(users, &User{Id: int64(1), OrgId: int64(1), Name: "testUser", Email: "email@gmail.com", PhoneNumber: "555-5555", UserSkills: sampleSkills}) - users = append(users, &User{Id: int64(2), OrgId: int64(2), Name: "testUser2", Email: "email@gmail.com", PhoneNumber: "555-5555", UserSkills: sampleSkills2}) + users = append(users, &User{Id: int64(1), OrgId: int64(1), Name: "testUser", Email: "email@gmail.com", PhoneNumber: "555-5555"}) + users = append(users, &User{Id: int64(2), OrgId: int64(2), Name: "testUser2", Email: "email@gmail.com", PhoneNumber: "555-5555"}) return users, nil }) s1.Query().FieldFunc("emptyusers", func(ctx context.Context) ([]*User, error) { @@ -187,7 +173,15 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem Name string Email string PhoneNumber string - UserSkills Skills + Skills Skills + } + + type UserWithContactInfoKeys struct { + Id int64 + OrgId int64 + Name string + Email string + PhoneNumber string } type UserKeysWithOrgId struct { @@ -195,10 +189,27 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem OrgId int64 } s2 := schemabuilder.NewSchemaWithName("s2") - userWithContactInfo := s2.Object("User", UserWithContactInfo{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*UserWithContactInfo }) []*UserWithContactInfo { - return args.Keys + userWithContactInfo := s2.Object("User", UserWithContactInfo{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*UserWithContactInfoKeys }) []*UserWithContactInfo { + help := []*UserWithContactInfo{} + plumbing := true + thunder := true + sampleAbilities := Abilities{Teleportation: true, BreathingFire: []int{1, 2, 3}} + sampleAbilities2 := Abilities{Teleportation: true, BreathingFire: []int{4, 5, 6}, Thunder: &thunder} + sampleSkills := Skills{Eating: 1, Plumbing: &plumbing, Sports: nil, Abilities: sampleAbilities, AbilitiesPointer: &sampleAbilities2, AllAbilities: []*Abilities{&sampleAbilities, &sampleAbilities2}} + for _, i := range args.Keys { + new := &UserWithContactInfo{ + Id: i.Id, + OrgId: i.OrgId, + Name: i.Name, + Email: i.Email, + PhoneNumber: i.PhoneNumber, + Skills: sampleSkills, + } + help = append(help, new) + } + return help })) - s2.Object("UserSkills", Skills{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Skills }) []*Skills { + s2.Object("Skills", Skills{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Skills }) []*Skills { return args.Keys })) s2.Object("Abilities", Abilities{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Abilities }) []*Abilities { @@ -210,8 +221,8 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem return "shhhhh", nil }) - userWithContactInfo.FieldFunc("getSkills", func(ctx context.Context, user *UserWithContactInfo) (Skills, error) { - return user.UserSkills, nil + userWithContactInfo.FieldFunc("skills", func(ctx context.Context, user *UserWithContactInfo) (Skills, error) { + return user.Skills, nil }) /* @@ -336,7 +347,6 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem Name string Email string PhoneNumber string - UserSkills Skills } type UserKeys struct { Id int64 @@ -349,12 +359,6 @@ func createExecutorWithFederatedUser() (*Executor, *schemabuilder.Schema, *schem } return users })) - s3.Object("UserSkills", Skills{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Skills }) []*Skills { - return args.Keys - })) - s3.Object("Abilities", Abilities{}, schemabuilder.FetchObjectFromKeys(func(args struct{ Keys []*Abilities }) []*Abilities { - return args.Keys - })) userWithAdminPrivelages.Key("id") userWithAdminPrivelages.FieldFunc("isAdmin", func(ctx context.Context, user *UserWithAdminPrivelages) (bool, error) { return true, nil @@ -523,7 +527,7 @@ func TestExecutorQueriesWithObjectKey(t *testing.T) { phoneNumber isAdmin secret - getSkills { + skills { sports plumbing eating @@ -553,7 +557,7 @@ func TestExecutorQueriesWithObjectKey(t *testing.T) { "phoneNumber": "555-5555", "isAdmin":true, "secret": "shhhhh", - "getSkills": { + "skills": { "sports": [], "plumbing": true, "eating": 1, @@ -561,7 +565,11 @@ func TestExecutorQueriesWithObjectKey(t *testing.T) { "teleportation": true, "breathingFire": [1, 2, 3] }, - "abilitiesPointer": null, + "abilitiesPointer": { + "teleportation": true, + "breathingFire": [4, 5, 6], + "thunder": true + }, "allAbilities": [ { "teleportation": true, @@ -580,20 +588,29 @@ func TestExecutorQueriesWithObjectKey(t *testing.T) { "phoneNumber": "555-5555", "isAdmin":true, "secret": "shhhhh", - "getSkills": { - "sports": ["basketball", "swimming", "tennis"], + "skills": { + "sports": [], "plumbing": true, - "eating": 2, + "eating": 1, "abilities": { - "teleportation": false, - "breathingFire": [] + "teleportation": true, + "breathingFire": [1, 2, 3] }, "abilitiesPointer": { "teleportation": true, "breathingFire": [4, 5, 6], "thunder": true }, - "allAbilities": [] + "allAbilities": [ + { + "teleportation": true, + "breathingFire": [1, 2, 3] + }, + { + "teleportation": true, + "breathingFire": [4, 5, 6] + } + ] } } ]