From 4fdbb218baf420fd350abd49e6d3b1f53bb9e74f Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Wed, 11 Sep 2019 11:54:17 -0400 Subject: [PATCH 1/3] Support class-based schemas and types --- lib/graphql/relay/walker/query_builder.rb | 24 +- spec/client_ext_spec.rb | 36 +- spec/fixtures/swapi_query.graphql | 60 +- spec/fixtures/swapi_schema.graphql | 1575 +++++++++++++++++++++ spec/query_builder_spec.rb | 96 +- 5 files changed, 1689 insertions(+), 102 deletions(-) create mode 100644 spec/fixtures/swapi_schema.graphql diff --git a/lib/graphql/relay/walker/query_builder.rb b/lib/graphql/relay/walker/query_builder.rb index 1271617..cea2399 100644 --- a/lib/graphql/relay/walker/query_builder.rb +++ b/lib/graphql/relay/walker/query_builder.rb @@ -70,7 +70,10 @@ module GraphQL::Relay::Walker def inline_fragment_ast(type, with_children: true) selections = [] if with_children - type.all_fields.each do |field| + # Class-based types return all fields in `.fields` + all_fields = type.respond_to?(:all_fields) ? type.all_fields : type.fields.values + all_fields = all_fields.sort_by(&:graphql_name) + all_fields.each do |field| field_type = field.type.unwrap if node_field?(field) && include?(field_type) selections << node_field_ast(field) @@ -88,7 +91,7 @@ module GraphQL::Relay::Walker nil else GraphQL::Language::Nodes::InlineFragment.new( - type: make_type_name_node(type.name), + type: make_type_name_node(type.graphql_name), selections: selections, ) end @@ -114,12 +117,12 @@ module GraphQL::Relay::Walker if !required_args_are_present nil else - f_alias = field.name == 'id' ? nil : random_alias + f_alias = field.graphql_name == 'id' ? nil : random_alias f_args = arguments.map do |name, value| GraphQL::Language::Nodes::Argument.new(name: name, value: value) end - GraphQL::Language::Nodes::Field.new(name: field.name, alias: f_alias, arguments: f_args) + GraphQL::Language::Nodes::Field.new(name: field.graphql_name, alias: f_alias, arguments: f_args) end end @@ -213,11 +216,12 @@ module GraphQL::Relay::Walker # `node` field that is a relay node. Returns false otherwise. def connection_field?(field) type = field.type.unwrap - - if edges_field = type.get_field('edges') - edges = edges_field.type.unwrap - if node_field = edges.get_field('node') - return node_field?(node_field) + if type.kind.fields? + if edges_field = type.get_field('edges') + edges = edges_field.type.unwrap + if node_field = edges.get_field('node') + return node_field?(node_field) + end end end @@ -268,7 +272,7 @@ module GraphQL::Relay::Walker end def valid_input?(type, input) - type.valid_isolated_input?(input) + type.valid_input?(input, GraphQL::Query::NullContext) end def make_type_name_node(type_name) diff --git a/spec/client_ext_spec.rb b/spec/client_ext_spec.rb index a8a3ed6..72bb2c3 100644 --- a/spec/client_ext_spec.rb +++ b/spec/client_ext_spec.rb @@ -1,25 +1,29 @@ +require "pry" require 'graphql/client' require 'graphql/relay/walker' require 'graphql/relay/walker/client_ext' describe GraphQL::Relay::Walker::ClientExt do - let(:schema_path) { 'spec/fixtures/swapi_schema.json' } - let(:query_path) { 'spec/fixtures/swapi_query.graphql' } - let(:schema) { GraphQL::Client.load_schema(schema_path) } - let(:client) { GraphQL::Client.new(schema: schema) } + [ + GraphQL::Client.load_schema('spec/fixtures/swapi_schema.json'), + GraphQL::Schema.from_definition("spec/fixtures/swapi_schema.graphql") + ].each do |schema| + describe "with #{schema.class} schema" do + describe '#walk' do + let(:client) { GraphQL::Client.new(schema: schema) } + it 'allows passing additional variables through to GraphQLClient#query' do + expected = { variables: { 'foo' => 'bar', 'id' => '12345' }, context: {} } + expect(client).to receive(:query).with(anything, expected).and_return({}) + client.walk(from_id: '12345', variables: { 'foo' => 'bar' }) + end - describe '#walk' do - it 'allows passing additional variables through to GraphQLClient#query' do - expected = { variables: { 'foo' => 'bar', 'id' => '12345' }, context: {} } - expect(client).to receive(:query).with(anything, expected).and_return({}) - client.walk(from_id: '12345', variables: { 'foo' => 'bar' }) - end - - it 'allows passing additional context through to GraphQLClient#query' do - viewer = Object.new - expected = { variables: { 'id' => '12345' }, context: { viewer: viewer } } - expect(client).to receive(:query).with(anything, expected).and_return({}) - client.walk(from_id: '12345', context: { viewer: viewer }) + it 'allows passing additional context through to GraphQLClient#query' do + viewer = Object.new + expected = { variables: { 'id' => '12345' }, context: { viewer: viewer } } + expect(client).to receive(:query).with(anything, expected).and_return({}) + client.walk(from_id: '12345', context: { viewer: viewer }) + end + end end end end diff --git a/spec/fixtures/swapi_query.graphql b/spec/fixtures/swapi_query.graphql index 513e04d..8b4d49e 100644 --- a/spec/fixtures/swapi_query.graphql +++ b/spec/fixtures/swapi_query.graphql @@ -2,6 +2,20 @@ query($id: ID!) { node(id: $id) { id ... on Film { + characterConnection(first: 5) { + edges { + node { + id + } + } + } + planetConnection(first: 5) { + edges { + node { + id + } + } + } speciesConnection(first: 5) { edges { node { @@ -23,25 +37,8 @@ query($id: ID!) { } } } - characterConnection(first: 5) { - edges { - node { - id - } - } - } - planetConnection(first: 5) { - edges { - node { - id - } - } - } } ... on Person { - homeworld { - id - } filmConnection(first: 5) { edges { node { @@ -49,6 +46,9 @@ query($id: ID!) { } } } + homeworld { + id + } species { id } @@ -68,14 +68,14 @@ query($id: ID!) { } } ... on Planet { - residentConnection(first: 5) { + filmConnection(first: 5) { edges { node { id } } } - filmConnection(first: 5) { + residentConnection(first: 5) { edges { node { id @@ -84,6 +84,13 @@ query($id: ID!) { } } ... on Species { + filmConnection(first: 5) { + edges { + node { + id + } + } + } homeworld { id } @@ -94,23 +101,16 @@ query($id: ID!) { } } } - filmConnection(first: 5) { - edges { - node { - id - } - } - } } ... on Starship { - pilotConnection(first: 5) { + filmConnection(first: 5) { edges { node { id } } } - filmConnection(first: 5) { + pilotConnection(first: 5) { edges { node { id @@ -119,14 +119,14 @@ query($id: ID!) { } } ... on Vehicle { - pilotConnection(first: 5) { + filmConnection(first: 5) { edges { node { id } } } - filmConnection(first: 5) { + pilotConnection(first: 5) { edges { node { id diff --git a/spec/fixtures/swapi_schema.graphql b/spec/fixtures/swapi_schema.graphql new file mode 100644 index 0000000..98aebbd --- /dev/null +++ b/spec/fixtures/swapi_schema.graphql @@ -0,0 +1,1575 @@ +schema { + query: Root +} + +""" +A single film. +""" +type Film implements Node { + characterConnection(after: String, before: String, first: Int, last: Int): FilmCharactersConnection + + """ + The ISO 8601 date format of the time that this resource was created. + """ + created: String + + """ + The name of the director of this film. + """ + director: String + + """ + The ISO 8601 date format of the time that this resource was edited. + """ + edited: String + + """ + The episode number of this film. + """ + episodeID: Int + + """ + The ID of an object + """ + id: ID! + + """ + The opening paragraphs at the beginning of this film. + """ + openingCrawl: String + planetConnection(after: String, before: String, first: Int, last: Int): FilmPlanetsConnection + + """ + The name(s) of the producer(s) of this film. + """ + producers: [String] + + """ + The ISO 8601 date format of film release at original creator country. + """ + releaseDate: String + speciesConnection(after: String, before: String, first: Int, last: Int): FilmSpeciesConnection + starshipConnection(after: String, before: String, first: Int, last: Int): FilmStarshipsConnection + + """ + The title of this film. + """ + title: String + vehicleConnection(after: String, before: String, first: Int, last: Int): FilmVehiclesConnection +} + +""" +A connection to a list of items. +""" +type FilmCharactersConnection { + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + characters: [Person] + + """ + Information to aid in pagination. + """ + edges: [FilmCharactersEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type FilmCharactersEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Person +} + +""" +A connection to a list of items. +""" +type FilmPlanetsConnection { + """ + Information to aid in pagination. + """ + edges: [FilmPlanetsEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + planets: [Planet] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type FilmPlanetsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Planet +} + +""" +A connection to a list of items. +""" +type FilmSpeciesConnection { + """ + Information to aid in pagination. + """ + edges: [FilmSpeciesEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + species: [Species] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type FilmSpeciesEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Species +} + +""" +A connection to a list of items. +""" +type FilmStarshipsConnection { + """ + Information to aid in pagination. + """ + edges: [FilmStarshipsEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + starships: [Starship] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type FilmStarshipsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Starship +} + +""" +A connection to a list of items. +""" +type FilmVehiclesConnection { + """ + Information to aid in pagination. + """ + edges: [FilmVehiclesEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + vehicles: [Vehicle] +} + +""" +An edge in a connection. +""" +type FilmVehiclesEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Vehicle +} + +""" +A connection to a list of items. +""" +type FilmsConnection { + """ + Information to aid in pagination. + """ + edges: [FilmsEdge] + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type FilmsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Film +} + +""" +An object with an ID +""" +interface Node { + """ + The id of the object. + """ + id: ID! +} + +""" +Information about pagination in a connection. +""" +type PageInfo { + """ + When paginating forwards, the cursor to continue. + """ + endCursor: String + + """ + When paginating forwards, are there more items? + """ + hasNextPage: Boolean! + + """ + When paginating backwards, are there more items? + """ + hasPreviousPage: Boolean! + + """ + When paginating backwards, the cursor to continue. + """ + startCursor: String +} + +""" +A connection to a list of items. +""" +type PeopleConnection { + """ + Information to aid in pagination. + """ + edges: [PeopleEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + people: [Person] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type PeopleEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Person +} + +""" +An individual person or character within the Star Wars universe. +""" +type Person implements Node { + """ + The birth year of the person, using the in-universe standard of BBY or ABY - + Before the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is + a battle that occurs at the end of Star Wars episode IV: A New Hope. + """ + birthYear: String + + """ + The ISO 8601 date format of the time that this resource was created. + """ + created: String + + """ + The ISO 8601 date format of the time that this resource was edited. + """ + edited: String + + """ + The eye color of this person. Will be "unknown" if not known or "n/a" if the + person does not have an eye. + """ + eyeColor: String + filmConnection(after: String, before: String, first: Int, last: Int): PersonFilmsConnection + + """ + The gender of this person. Either "Male", "Female" or "unknown", + "n/a" if the person does not have a gender. + """ + gender: String + + """ + The hair color of this person. Will be "unknown" if not known or "n/a" if the + person does not have hair. + """ + hairColor: String + + """ + The height of the person in centimeters. + """ + height: Int + + """ + A planet that this person was born on or inhabits. + """ + homeworld: Planet + + """ + The ID of an object + """ + id: ID! + + """ + The mass of the person in kilograms. + """ + mass: Int + + """ + The name of this person. + """ + name: String + + """ + The skin color of this person. + """ + skinColor: String + + """ + The species that this person belongs to, or null if unknown. + """ + species: Species + starshipConnection(after: String, before: String, first: Int, last: Int): PersonStarshipsConnection + vehicleConnection(after: String, before: String, first: Int, last: Int): PersonVehiclesConnection +} + +""" +A connection to a list of items. +""" +type PersonFilmsConnection { + """ + Information to aid in pagination. + """ + edges: [PersonFilmsEdge] + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type PersonFilmsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Film +} + +""" +A connection to a list of items. +""" +type PersonStarshipsConnection { + """ + Information to aid in pagination. + """ + edges: [PersonStarshipsEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + starships: [Starship] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type PersonStarshipsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Starship +} + +""" +A connection to a list of items. +""" +type PersonVehiclesConnection { + """ + Information to aid in pagination. + """ + edges: [PersonVehiclesEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + vehicles: [Vehicle] +} + +""" +An edge in a connection. +""" +type PersonVehiclesEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Vehicle +} + +""" +A large mass, planet or planetoid in the Star Wars Universe, at the time of +0 ABY. +""" +type Planet implements Node { + """ + The climates of this planet. + """ + climates: [String] + + """ + The ISO 8601 date format of the time that this resource was created. + """ + created: String + + """ + The diameter of this planet in kilometers. + """ + diameter: Int + + """ + The ISO 8601 date format of the time that this resource was edited. + """ + edited: String + filmConnection(after: String, before: String, first: Int, last: Int): PlanetFilmsConnection + + """ + A number denoting the gravity of this planet, where "1" is normal or 1 standard + G. "2" is twice or 2 standard Gs. "0.5" is half or 0.5 standard Gs. + """ + gravity: String + + """ + The ID of an object + """ + id: ID! + + """ + The name of this planet. + """ + name: String + + """ + The number of standard days it takes for this planet to complete a single orbit + of its local star. + """ + orbitalPeriod: Int + + """ + The average population of sentient beings inhabiting this planet. + """ + population: Int + residentConnection(after: String, before: String, first: Int, last: Int): PlanetResidentsConnection + + """ + The number of standard hours it takes for this planet to complete a single + rotation on its axis. + """ + rotationPeriod: Int + + """ + The percentage of the planet surface that is naturally occuring water or bodies + of water. + """ + surfaceWater: Float + + """ + The terrains of this planet. + """ + terrains: [String] +} + +""" +A connection to a list of items. +""" +type PlanetFilmsConnection { + """ + Information to aid in pagination. + """ + edges: [PlanetFilmsEdge] + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type PlanetFilmsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Film +} + +""" +A connection to a list of items. +""" +type PlanetResidentsConnection { + """ + Information to aid in pagination. + """ + edges: [PlanetResidentsEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + residents: [Person] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type PlanetResidentsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Person +} + +""" +A connection to a list of items. +""" +type PlanetsConnection { + """ + Information to aid in pagination. + """ + edges: [PlanetsEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + planets: [Planet] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type PlanetsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Planet +} + +type Root { + allFilms(after: String, before: String, first: Int, last: Int): FilmsConnection + allPeople(after: String, before: String, first: Int, last: Int): PeopleConnection + allPlanets(after: String, before: String, first: Int, last: Int): PlanetsConnection + allSpecies(after: String, before: String, first: Int, last: Int): SpeciesConnection + allStarships(after: String, before: String, first: Int, last: Int): StarshipsConnection + allVehicles(after: String, before: String, first: Int, last: Int): VehiclesConnection + film(filmID: ID, id: ID): Film + + """ + Fetches an object given its ID + """ + node( + """ + The ID of an object + """ + id: ID! + ): Node + person(id: ID, personID: ID): Person + planet(id: ID, planetID: ID): Planet + species(id: ID, speciesID: ID): Species + starship(id: ID, starshipID: ID): Starship + vehicle(id: ID, vehicleID: ID): Vehicle +} + +""" +A type of person or character within the Star Wars Universe. +""" +type Species implements Node { + """ + The average height of this species in centimeters. + """ + averageHeight: Float + + """ + The average lifespan of this species in years. + """ + averageLifespan: Int + + """ + The classification of this species, such as "mammal" or "reptile". + """ + classification: String + + """ + The ISO 8601 date format of the time that this resource was created. + """ + created: String + + """ + The designation of this species, such as "sentient". + """ + designation: String + + """ + The ISO 8601 date format of the time that this resource was edited. + """ + edited: String + + """ + Common eye colors for this species, null if this species does not typically + have eyes. + """ + eyeColors: [String] + filmConnection(after: String, before: String, first: Int, last: Int): SpeciesFilmsConnection + + """ + Common hair colors for this species, null if this species does not typically + have hair. + """ + hairColors: [String] + + """ + A planet that this species originates from. + """ + homeworld: Planet + + """ + The ID of an object + """ + id: ID! + + """ + The language commonly spoken by this species. + """ + language: String + + """ + The name of this species. + """ + name: String + personConnection(after: String, before: String, first: Int, last: Int): SpeciesPeopleConnection + + """ + Common skin colors for this species, null if this species does not typically + have skin. + """ + skinColors: [String] +} + +""" +A connection to a list of items. +""" +type SpeciesConnection { + """ + Information to aid in pagination. + """ + edges: [SpeciesEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + species: [Species] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type SpeciesEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Species +} + +""" +A connection to a list of items. +""" +type SpeciesFilmsConnection { + """ + Information to aid in pagination. + """ + edges: [SpeciesFilmsEdge] + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type SpeciesFilmsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Film +} + +""" +A connection to a list of items. +""" +type SpeciesPeopleConnection { + """ + Information to aid in pagination. + """ + edges: [SpeciesPeopleEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + people: [Person] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type SpeciesPeopleEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Person +} + +""" +A single transport craft that has hyperdrive capability. +""" +type Starship implements Node { + """ + The Maximum number of Megalights this starship can travel in a standard hour. + A "Megalight" is a standard unit of distance and has never been defined before + within the Star Wars universe. This figure is only really useful for measuring + the difference in speed of starships. We can assume it is similar to AU, the + distance between our Sun (Sol) and Earth. + """ + MGLT: Int + + """ + The maximum number of kilograms that this starship can transport. + """ + cargoCapacity: Float + + """ + The maximum length of time that this starship can provide consumables for its + entire crew without having to resupply. + """ + consumables: String + + """ + The cost of this starship new, in galactic credits. + """ + costInCredits: Float + + """ + The ISO 8601 date format of the time that this resource was created. + """ + created: String + + """ + The number of personnel needed to run or pilot this starship. + """ + crew: String + + """ + The ISO 8601 date format of the time that this resource was edited. + """ + edited: String + filmConnection(after: String, before: String, first: Int, last: Int): StarshipFilmsConnection + + """ + The class of this starships hyperdrive. + """ + hyperdriveRating: Float + + """ + The ID of an object + """ + id: ID! + + """ + The length of this starship in meters. + """ + length: Float + + """ + The manufacturers of this starship. + """ + manufacturers: [String] + + """ + The maximum speed of this starship in atmosphere. null if this starship is + incapable of atmosphering flight. + """ + maxAtmospheringSpeed: Int + + """ + The model or official name of this starship. Such as "T-65 X-wing" or "DS-1 + Orbital Battle Station". + """ + model: String + + """ + The name of this starship. The common name, such as "Death Star". + """ + name: String + + """ + The number of non-essential people this starship can transport. + """ + passengers: String + pilotConnection(after: String, before: String, first: Int, last: Int): StarshipPilotsConnection + + """ + The class of this starship, such as "Starfighter" or "Deep Space Mobile + Battlestation" + """ + starshipClass: String +} + +""" +A connection to a list of items. +""" +type StarshipFilmsConnection { + """ + Information to aid in pagination. + """ + edges: [StarshipFilmsEdge] + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type StarshipFilmsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Film +} + +""" +A connection to a list of items. +""" +type StarshipPilotsConnection { + """ + Information to aid in pagination. + """ + edges: [StarshipPilotsEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + pilots: [Person] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type StarshipPilotsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Person +} + +""" +A connection to a list of items. +""" +type StarshipsConnection { + """ + Information to aid in pagination. + """ + edges: [StarshipsEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + starships: [Starship] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type StarshipsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Starship +} + +""" +A single transport craft that does not have hyperdrive capability +""" +type Vehicle implements Node { + """ + The maximum number of kilograms that this vehicle can transport. + """ + cargoCapacity: Int + + """ + The maximum length of time that this vehicle can provide consumables for its + entire crew without having to resupply. + """ + consumables: String + + """ + The cost of this vehicle new, in Galactic Credits. + """ + costInCredits: Int + + """ + The ISO 8601 date format of the time that this resource was created. + """ + created: String + + """ + The number of personnel needed to run or pilot this vehicle. + """ + crew: String + + """ + The ISO 8601 date format of the time that this resource was edited. + """ + edited: String + filmConnection(after: String, before: String, first: Int, last: Int): VehicleFilmsConnection + + """ + The ID of an object + """ + id: ID! + + """ + The length of this vehicle in meters. + """ + length: Float + + """ + The manufacturers of this vehicle. + """ + manufacturers: [String] + + """ + The maximum speed of this vehicle in atmosphere. + """ + maxAtmospheringSpeed: Int + + """ + The model or official name of this vehicle. Such as "All-Terrain Attack + Transport". + """ + model: String + + """ + The name of this vehicle. The common name, such as "Sand Crawler" or "Speeder + bike". + """ + name: String + + """ + The number of non-essential people this vehicle can transport. + """ + passengers: String + pilotConnection(after: String, before: String, first: Int, last: Int): VehiclePilotsConnection + + """ + The class of this vehicle, such as "Wheeled" or "Repulsorcraft". + """ + vehicleClass: String +} + +""" +A connection to a list of items. +""" +type VehicleFilmsConnection { + """ + Information to aid in pagination. + """ + edges: [VehicleFilmsEdge] + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type VehicleFilmsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Film +} + +""" +A connection to a list of items. +""" +type VehiclePilotsConnection { + """ + Information to aid in pagination. + """ + edges: [VehiclePilotsEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + pilots: [Person] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int +} + +""" +An edge in a connection. +""" +type VehiclePilotsEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Person +} + +""" +A connection to a list of items. +""" +type VehiclesConnection { + """ + Information to aid in pagination. + """ + edges: [VehiclesEdge] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + vehicles: [Vehicle] +} + +""" +An edge in a connection. +""" +type VehiclesEdge { + """ + A cursor for use in pagination + """ + cursor: String! + + """ + The item at the end of the edge + """ + node: Vehicle +} \ No newline at end of file diff --git a/spec/query_builder_spec.rb b/spec/query_builder_spec.rb index c55a727..c9c5c8a 100644 --- a/spec/query_builder_spec.rb +++ b/spec/query_builder_spec.rb @@ -2,55 +2,59 @@ require 'graphql/relay/walker' require 'graphql/client' describe GraphQL::Relay::Walker::QueryBuilder do - let(:schema_path) { 'spec/fixtures/swapi_schema.json' } - let(:query_path) { 'spec/fixtures/swapi_query.graphql' } - let(:schema) { GraphQL::Client.load_schema(schema_path) } - let(:client) { GraphQL::Client.new(schema: schema) } - let(:query_builder) { described_class.new(schema) } - let(:ast) { query_builder.ast } - let(:query_string) { query_builder.query_string } + [ + GraphQL::Client.load_schema('spec/fixtures/swapi_schema.json'), + GraphQL::Schema.from_definition("spec/fixtures/swapi_schema.graphql") + ].each do |schema| + describe "With #{schema.class} schema" do + let(:query_path) { 'spec/fixtures/swapi_query.graphql' } + let(:client) { GraphQL::Client.new(schema: schema) } + let(:query_builder) { described_class.new(schema) } + let(:ast) { query_builder.ast } + let(:query_string) { query_builder.query_string } + describe 'ast' do + subject { ast } - describe 'ast' do - subject { ast } + it 'adds an alias to all fields except id and node' do + fields(ast).reject do |node| + %w[node id].include?(node.name) + end.each do |field| + expect(field.alias).not_to be_nil + end + end + end - it 'adds an alias to all fields except id and node' do - fields(ast).reject do |node| - %w[node id].include?(node.name) - end.each do |field| - expect(field.alias).not_to be_nil + describe 'query_string' do + subject { query_string } + + it 'generates a valid query for the schema' do + expect { client.parse(query_string) }.not_to raise_error + end + + describe 'with aliases removed' do + it 'matches the expected query string' do + # Replace the aliases, leaving the leading whitespace in place + string_without_aliases = subject.gsub(/ [a-z]{12}: /, " ") + expect(string_without_aliases).to eq(File.read(query_path).strip) + end + end + end + + def fields(ast) + nodes(ast).select { |node| node.is_a?(GraphQL::Language::Nodes::Field) } + end + + def nodes(ast) + children = if ast.respond_to?(:selections) + ast.selections + elsif ast.respond_to?(:definitions) + ast.definitions + else + [] + end + + children + children.map { |child| nodes(child) }.flatten end end end - - describe 'query_string' do - subject { query_string } - - it 'generates a valid query for the schema' do - expect { client.parse(query_string) }.not_to raise_error - end - - describe 'with aliases removed' do - it 'matches the expected query string' do - # Replace the aliases, leaving the leading whitespace in place - string_without_aliases = subject.gsub(/ [a-z]{12}: /, " ") - expect(string_without_aliases).to eq(File.read(query_path).strip) - end - end - end - - def fields(ast) - nodes(ast).select { |node| node.is_a?(GraphQL::Language::Nodes::Field) } - end - - def nodes(ast) - children = if ast.respond_to?(:selections) - ast.selections - elsif ast.respond_to?(:definitions) - ast.definitions - else - [] - end - - children + children.map { |child| nodes(child) }.flatten - end end From 17d9348c15f12c5ac0e09051fc5f68ee7fec1bdc Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Wed, 11 Sep 2019 12:03:02 -0400 Subject: [PATCH 2/3] remove debug code; update deps --- graphql-relay-walker.gemspec | 4 ++-- spec/client_ext_spec.rb | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/graphql-relay-walker.gemspec b/graphql-relay-walker.gemspec index 110b548..7042503 100644 --- a/graphql-relay-walker.gemspec +++ b/graphql-relay-walker.gemspec @@ -9,9 +9,9 @@ Gem::Specification.new do |s| s.files += Dir.glob('lib/**/*.rb') s.homepage = 'https://github.com/github/graphql-relay-walker' - s.add_dependency 'graphql', '>= 1.5.6' + s.add_dependency 'graphql', '~> 1.8' - s.add_development_dependency 'graphql-client', '~> 0.2' + s.add_development_dependency 'graphql-client', '~> 0.15' s.add_development_dependency 'rake', '~> 11.3' s.add_development_dependency 'rspec', '~> 3.5' end diff --git a/spec/client_ext_spec.rb b/spec/client_ext_spec.rb index 72bb2c3..d9b6adf 100644 --- a/spec/client_ext_spec.rb +++ b/spec/client_ext_spec.rb @@ -1,4 +1,3 @@ -require "pry" require 'graphql/client' require 'graphql/relay/walker' require 'graphql/relay/walker/client_ext' From ec8402752d4d12cd2f438d10b79f6dd71b5fa6e5 Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Wed, 11 Sep 2019 12:07:18 -0400 Subject: [PATCH 3/3] Use a more recent Ruby which is required by graphql-client's use of String#-@ --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e8f55cb..9bf8c4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: ruby rvm: - - 2.2.2 + - 2.3.8 - ruby-head branches: only: