func TestConnectionFromArray_HandlesPagination_RespectsLastAndAfterAndBefore_TooFew(t *testing.T) { filter := map[string]interface{}{ "last": 2, "after": "YXJyYXljb25uZWN0aW9uOjA=", "before": "YXJyYXljb25uZWN0aW9uOjQ=", } args := relay.NewConnectionArguments(filter) expected := &relay.Connection{ Edges: []*relay.Edge{ &relay.Edge{ Node: "C", Cursor: "YXJyYXljb25uZWN0aW9uOjI=", }, &relay.Edge{ Node: "D", Cursor: "YXJyYXljb25uZWN0aW9uOjM=", }, }, PageInfo: relay.PageInfo{ StartCursor: "YXJyYXljb25uZWN0aW9uOjI=", EndCursor: "YXJyYXljb25uZWN0aW9uOjM=", HasPreviousPage: true, HasNextPage: false, }, } result := relay.ConnectionFromArray(arrayConnectionTestLetters, args) if !reflect.DeepEqual(result, expected) { t.Fatalf("wrong result, connection result diff: %v", testutil.Diff(expected, result)) } }
func TestConnectionFromArray_HandlesCursorEdgeCases_ReturnsNoElementsIfCursorsCross(t *testing.T) { filter := map[string]interface{}{ "before": "YXJyYXljb25uZWN0aW9uOjI=", // ==> offset: int(2) "after": "YXJyYXljb25uZWN0aW9uOjQ=", // ==> offset: int(4) } args := relay.NewConnectionArguments(filter) expected := &relay.Connection{ Edges: []*relay.Edge{}, PageInfo: relay.PageInfo{}, } result := relay.ConnectionFromArray(arrayConnectionTestLetters, args) if !reflect.DeepEqual(result, expected) { t.Fatalf("wrong result, connection result diff: %v", testutil.Diff(expected, result)) } }
func TestConnectionFromArray_HandlesBasicSlicing_RespectsAnOverlyLargeFirst(t *testing.T) { filter := map[string]interface{}{ "first": 10, } args := relay.NewConnectionArguments(filter) expected := &relay.Connection{ Edges: []*relay.Edge{ &relay.Edge{ Node: "A", Cursor: "YXJyYXljb25uZWN0aW9uOjA=", }, &relay.Edge{ Node: "B", Cursor: "YXJyYXljb25uZWN0aW9uOjE=", }, &relay.Edge{ Node: "C", Cursor: "YXJyYXljb25uZWN0aW9uOjI=", }, &relay.Edge{ Node: "D", Cursor: "YXJyYXljb25uZWN0aW9uOjM=", }, &relay.Edge{ Node: "E", Cursor: "YXJyYXljb25uZWN0aW9uOjQ=", }, }, PageInfo: relay.PageInfo{ StartCursor: "YXJyYXljb25uZWN0aW9uOjA=", EndCursor: "YXJyYXljb25uZWN0aW9uOjQ=", HasPreviousPage: false, HasNextPage: false, }, } result := relay.ConnectionFromArray(arrayConnectionTestLetters, args) if !reflect.DeepEqual(result, expected) { t.Fatalf("wrong result, connection result diff: %v", testutil.Diff(expected, result)) } }
func TestConnectionFromArray_HandlesCursorEdgeCases_ReturnsAllElementsIfCursorsAreOnTheOutside(t *testing.T) { filter := map[string]interface{}{ "before": "YXJyYXljb25uZWN0aW9uOjYK", // ==> offset: int(6) "after": "YXJyYXljb25uZWN0aW9uOi0xCg==", // ==> offset: int(-1) } args := relay.NewConnectionArguments(filter) expected := &relay.Connection{ Edges: []*relay.Edge{ &relay.Edge{ Node: "A", Cursor: "YXJyYXljb25uZWN0aW9uOjA=", }, &relay.Edge{ Node: "B", Cursor: "YXJyYXljb25uZWN0aW9uOjE=", }, &relay.Edge{ Node: "C", Cursor: "YXJyYXljb25uZWN0aW9uOjI=", }, &relay.Edge{ Node: "D", Cursor: "YXJyYXljb25uZWN0aW9uOjM=", }, &relay.Edge{ Node: "E", Cursor: "YXJyYXljb25uZWN0aW9uOjQ=", }, }, PageInfo: relay.PageInfo{ StartCursor: "YXJyYXljb25uZWN0aW9uOjA=", EndCursor: "YXJyYXljb25uZWN0aW9uOjQ=", HasPreviousPage: false, HasNextPage: false, }, } result := relay.ConnectionFromArray(arrayConnectionTestLetters, args) if !reflect.DeepEqual(result, expected) { t.Fatalf("wrong result, connection result diff: %v", testutil.Diff(expected, result)) } }
func TestConnectionFromArray_HandlesCursorEdgeCases_ReturnsNoElementsIfFirstIsZero(t *testing.T) { filter := map[string]interface{}{ "first": 0, } args := relay.NewConnectionArguments(filter) expected := &relay.Connection{ Edges: []*relay.Edge{}, PageInfo: relay.PageInfo{ HasPreviousPage: false, HasNextPage: true, }, } result := relay.ConnectionFromArray(arrayConnectionTestLetters, args) if !reflect.DeepEqual(result, expected) { t.Fatalf("wrong result, connection result diff: %v", testutil.Diff(expected, result)) } }
func TestConnectionFromArray_HandlesBasicSlicing_ReturnsAllElementsWithoutFilters(t *testing.T) { args := relay.NewConnectionArguments(nil) expected := &relay.Connection{ Edges: []*relay.Edge{ &relay.Edge{ Node: "A", Cursor: "YXJyYXljb25uZWN0aW9uOjA=", }, &relay.Edge{ Node: "B", Cursor: "YXJyYXljb25uZWN0aW9uOjE=", }, &relay.Edge{ Node: "C", Cursor: "YXJyYXljb25uZWN0aW9uOjI=", }, &relay.Edge{ Node: "D", Cursor: "YXJyYXljb25uZWN0aW9uOjM=", }, &relay.Edge{ Node: "E", Cursor: "YXJyYXljb25uZWN0aW9uOjQ=", }, }, PageInfo: relay.PageInfo{ StartCursor: "YXJyYXljb25uZWN0aW9uOjA=", EndCursor: "YXJyYXljb25uZWN0aW9uOjQ=", HasPreviousPage: false, HasNextPage: false, }, } result := relay.ConnectionFromArray(arrayConnectionTestLetters, args) if !reflect.DeepEqual(result, expected) { t.Fatalf("wrong result, connection result diff: %v", testutil.Diff(expected, result)) } }
func TestConnectionFromArray_HandlesBasicSlicing_RespectsASmallerFirst(t *testing.T) { // Create connection arguments from map[string]interface{}, // which you usually get from types.GQLParams.Args filter := map[string]interface{}{ "first": 2, } args := relay.NewConnectionArguments(filter) // Alternatively, you can create connection arg the following way. // args := relay.NewConnectionArguments(filter) // args.First = 2 expected := &relay.Connection{ Edges: []*relay.Edge{ &relay.Edge{ Node: "A", Cursor: "YXJyYXljb25uZWN0aW9uOjA=", }, &relay.Edge{ Node: "B", Cursor: "YXJyYXljb25uZWN0aW9uOjE=", }, }, PageInfo: relay.PageInfo{ StartCursor: "YXJyYXljb25uZWN0aW9uOjA=", EndCursor: "YXJyYXljb25uZWN0aW9uOjE=", HasPreviousPage: false, HasNextPage: true, }, } result := relay.ConnectionFromArray(arrayConnectionTestLetters, args) if !reflect.DeepEqual(result, expected) { t.Fatalf("wrong result, connection result diff: %v", testutil.Diff(expected, result)) } }
func init() { /** * We get the node interface and field from the relay library. * * The first method is the way we resolve an ID to its object. The second is the * way we resolve an object that implements node to its type. */ nodeDefinitions = relay.NewNodeDefinitions(relay.NodeDefinitionsConfig{ IDFetcher: func(id string, info graphql.ResolveInfo, ctx context.Context) (interface{}, error) { // resolve id from global id resolvedID := relay.FromGlobalID(id) // based on id and its type, return the object switch resolvedID.Type { case "Faction": return GetFaction(resolvedID.ID), nil case "Ship": return GetShip(resolvedID.ID), nil default: return nil, errors.New("Unknown node type") } }, TypeResolve: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { // based on the type of the value, return GraphQLObjectType switch value.(type) { case *Faction: return factionType default: return shipType } }, }) /** * We define our basic ship type. * * This implements the following type system shorthand: * type Ship : Node { * id: String! * name: String * } */ shipType = graphql.NewObject(graphql.ObjectConfig{ Name: "Ship", Description: "A ship in the Star Wars saga", Fields: graphql.Fields{ "id": relay.GlobalIDField("Ship", nil), "name": &graphql.Field{ Type: graphql.String, Description: "The name of the ship.", }, }, Interfaces: []*graphql.Interface{ nodeDefinitions.NodeInterface, }, }) /** * We define a connection between a faction and its ships. * * connectionType implements the following type system shorthand: * type ShipConnection { * edges: [ShipEdge] * pageInfo: PageInfo! * } * * connectionType has an edges field - a list of edgeTypes that implement the * following type system shorthand: * type ShipEdge { * cursor: String! * node: Ship * } */ shipConnectionDefinition := relay.ConnectionDefinitions(relay.ConnectionConfig{ Name: "Ship", NodeType: shipType, }) /** * We define our faction type, which implements the node interface. * * This implements the following type system shorthand: * type Faction : Node { * id: String! * name: String * ships: ShipConnection * } */ factionType = graphql.NewObject(graphql.ObjectConfig{ Name: "Faction", Description: "A faction in the Star Wars saga", Fields: graphql.Fields{ "id": relay.GlobalIDField("Faction", nil), "name": &graphql.Field{ Type: graphql.String, Description: "The name of the faction.", }, "ships": &graphql.Field{ Type: shipConnectionDefinition.ConnectionType, Args: relay.ConnectionArgs, Resolve: func(p graphql.ResolveParams) (interface{}, error) { // convert args map[string]interface into ConnectionArguments args := relay.NewConnectionArguments(p.Args) // get ship objects from current faction ships := []interface{}{} if faction, ok := p.Source.(*Faction); ok { for _, shipId := range faction.Ships { ships = append(ships, GetShip(shipId)) } } // let relay library figure out the result, given // - the list of ships for this faction // - and the filter arguments (i.e. first, last, after, before) return relay.ConnectionFromArray(ships, args), nil }, }, }, Interfaces: []*graphql.Interface{ nodeDefinitions.NodeInterface, }, }) /** * This is the type that will be the root of our query, and the * entry point into our schema. * * This implements the following type system shorthand: * type Query { * rebels: Faction * empire: Faction * node(id: String!): Node * } */ queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "rebels": &graphql.Field{ Type: factionType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return GetRebels(), nil }, }, "empire": &graphql.Field{ Type: factionType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return GetEmpire(), nil }, }, "node": nodeDefinitions.NodeField, }, }) /** * This will return a GraphQLField for our ship * mutation. * * It creates these two types implicitly: * input IntroduceShipInput { * clientMutationID: string! * shipName: string! * factionId: ID! * } * * input IntroduceShipPayload { * clientMutationID: string! * ship: Ship * faction: Faction * } */ shipMutation := relay.MutationWithClientMutationID(relay.MutationConfig{ Name: "IntroduceShip", InputFields: graphql.InputObjectConfigFieldMap{ "shipName": &graphql.InputObjectFieldConfig{ Type: graphql.NewNonNull(graphql.String), }, "factionId": &graphql.InputObjectFieldConfig{ Type: graphql.NewNonNull(graphql.ID), }, }, OutputFields: graphql.Fields{ "ship": &graphql.Field{ Type: shipType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if payload, ok := p.Source.(map[string]interface{}); ok { return GetShip(payload["shipId"].(string)), nil } return nil, nil }, }, "faction": &graphql.Field{ Type: factionType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if payload, ok := p.Source.(map[string]interface{}); ok { return GetFaction(payload["factionId"].(string)), nil } return nil, nil }, }, }, MutateAndGetPayload: func(inputMap map[string]interface{}, info graphql.ResolveInfo, ctx context.Context) (map[string]interface{}, error) { // `inputMap` is a map with keys/fields as specified in `InputFields` // Note, that these fields were specified as non-nullables, so we can assume that it exists. shipName := inputMap["shipName"].(string) factionId := inputMap["factionId"].(string) // This mutation involves us creating (introducing) a new ship newShip := CreateShip(shipName, factionId) // return payload return map[string]interface{}{ "shipId": newShip.ID, "factionId": factionId, }, nil }, }) /** * This is the type that will be the root of our mutations, and the * entry point into performing writes in our schema. * * This implements the following type system shorthand: * type Mutation { * introduceShip(input IntroduceShipInput!): IntroduceShipPayload * } */ mutationType := graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "introduceShip": shipMutation, }, }) /** * Finally, we construct our schema (whose starting query type is the query * type we defined above) and export it. */ var err error Schema, err = graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, Mutation: mutationType, }) if err != nil { // panic if there is an error in schema panic(err) } }
func init() { connectionTestUserType = graphql.NewObject(graphql.ObjectConfig{ Name: "User", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, // re-define `friends` field later because `connectionTestUserType` has `connectionTestConnectionDef` has `connectionTestUserType` (cyclic-reference) "friends": &graphql.Field{}, }, }) connectionTestConnectionDef = relay.ConnectionDefinitions(relay.ConnectionConfig{ Name: "Friend", NodeType: connectionTestUserType, EdgeFields: graphql.Fields{ "friendshipTime": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "Yesterday", nil }, }, }, ConnectionFields: graphql.Fields{ "totalCount": &graphql.Field{ Type: graphql.Int, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return len(connectionTestAllUsers), nil }, }, }, }) // define `friends` field here after getting connection definition connectionTestUserType.AddFieldConfig("friends", &graphql.Field{ Type: connectionTestConnectionDef.ConnectionType, Args: relay.ConnectionArgs, Resolve: func(p graphql.ResolveParams) (interface{}, error) { arg := relay.NewConnectionArguments(p.Args) res := relay.ConnectionFromArray(connectionTestAllUsers, arg) return res, nil }, }) connectionTestQueryType = graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "user": &graphql.Field{ Type: connectionTestUserType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return connectionTestAllUsers[0], nil }, }, }, }) var err error connectionTestSchema, err = graphql.NewSchema(graphql.SchemaConfig{ Query: connectionTestQueryType, }) if err != nil { panic(err) } }