Merge pull request #17 from github/support-class-based-types

Support class-based schemas and types
This commit is contained in:
Robert Mosolgo 2019-10-09 11:11:10 -04:00 коммит произвёл GitHub
Родитель a37a69ed94 ec8402752d
Коммит 0e9385ecf7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 1691 добавлений и 105 удалений

Просмотреть файл

@ -1,6 +1,6 @@
language: ruby language: ruby
rvm: rvm:
- 2.2.2 - 2.3.8
- ruby-head - ruby-head
branches: branches:
only: only:

Просмотреть файл

@ -9,9 +9,9 @@ Gem::Specification.new do |s|
s.files += Dir.glob('lib/**/*.rb') s.files += Dir.glob('lib/**/*.rb')
s.homepage = 'https://github.com/github/graphql-relay-walker' 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 'rake', '~> 11.3'
s.add_development_dependency 'rspec', '~> 3.5' s.add_development_dependency 'rspec', '~> 3.5'
end end

Просмотреть файл

@ -70,7 +70,10 @@ module GraphQL::Relay::Walker
def inline_fragment_ast(type, with_children: true) def inline_fragment_ast(type, with_children: true)
selections = [] selections = []
if with_children 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 field_type = field.type.unwrap
if node_field?(field) && include?(field_type) if node_field?(field) && include?(field_type)
selections << node_field_ast(field) selections << node_field_ast(field)
@ -88,7 +91,7 @@ module GraphQL::Relay::Walker
nil nil
else else
GraphQL::Language::Nodes::InlineFragment.new( GraphQL::Language::Nodes::InlineFragment.new(
type: make_type_name_node(type.name), type: make_type_name_node(type.graphql_name),
selections: selections, selections: selections,
) )
end end
@ -114,12 +117,12 @@ module GraphQL::Relay::Walker
if !required_args_are_present if !required_args_are_present
nil nil
else 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| f_args = arguments.map do |name, value|
GraphQL::Language::Nodes::Argument.new(name: name, value: value) GraphQL::Language::Nodes::Argument.new(name: name, value: value)
end 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
end end
@ -213,11 +216,12 @@ module GraphQL::Relay::Walker
# `node` field that is a relay node. Returns false otherwise. # `node` field that is a relay node. Returns false otherwise.
def connection_field?(field) def connection_field?(field)
type = field.type.unwrap type = field.type.unwrap
if type.kind.fields?
if edges_field = type.get_field('edges') if edges_field = type.get_field('edges')
edges = edges_field.type.unwrap edges = edges_field.type.unwrap
if node_field = edges.get_field('node') if node_field = edges.get_field('node')
return node_field?(node_field) return node_field?(node_field)
end
end end
end end
@ -268,7 +272,7 @@ module GraphQL::Relay::Walker
end end
def valid_input?(type, input) def valid_input?(type, input)
type.valid_isolated_input?(input) type.valid_input?(input, GraphQL::Query::NullContext)
end end
def make_type_name_node(type_name) def make_type_name_node(type_name)

Просмотреть файл

@ -3,23 +3,26 @@ require 'graphql/relay/walker'
require 'graphql/relay/walker/client_ext' require 'graphql/relay/walker/client_ext'
describe GraphQL::Relay::Walker::ClientExt do describe GraphQL::Relay::Walker::ClientExt do
let(:schema_path) { 'spec/fixtures/swapi_schema.json' } [
let(:query_path) { 'spec/fixtures/swapi_query.graphql' } GraphQL::Client.load_schema('spec/fixtures/swapi_schema.json'),
let(:schema) { GraphQL::Client.load_schema(schema_path) } GraphQL::Schema.from_definition("spec/fixtures/swapi_schema.graphql")
let(:client) { GraphQL::Client.new(schema: schema) } ].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 context through to GraphQLClient#query' do
it 'allows passing additional variables through to GraphQLClient#query' do viewer = Object.new
expected = { variables: { 'foo' => 'bar', 'id' => '12345' }, context: {} } expected = { variables: { 'id' => '12345' }, context: { viewer: viewer } }
expect(client).to receive(:query).with(anything, expected).and_return({}) expect(client).to receive(:query).with(anything, expected).and_return({})
client.walk(from_id: '12345', variables: { 'foo' => 'bar' }) client.walk(from_id: '12345', context: { viewer: viewer })
end end
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 })
end end
end end
end end

60
spec/fixtures/swapi_query.graphql поставляемый
Просмотреть файл

@ -2,6 +2,20 @@ query($id: ID!) {
node(id: $id) { node(id: $id) {
id id
... on Film { ... on Film {
characterConnection(first: 5) {
edges {
node {
id
}
}
}
planetConnection(first: 5) {
edges {
node {
id
}
}
}
speciesConnection(first: 5) { speciesConnection(first: 5) {
edges { edges {
node { node {
@ -23,25 +37,8 @@ query($id: ID!) {
} }
} }
} }
characterConnection(first: 5) {
edges {
node {
id
}
}
}
planetConnection(first: 5) {
edges {
node {
id
}
}
}
} }
... on Person { ... on Person {
homeworld {
id
}
filmConnection(first: 5) { filmConnection(first: 5) {
edges { edges {
node { node {
@ -49,6 +46,9 @@ query($id: ID!) {
} }
} }
} }
homeworld {
id
}
species { species {
id id
} }
@ -68,14 +68,14 @@ query($id: ID!) {
} }
} }
... on Planet { ... on Planet {
residentConnection(first: 5) { filmConnection(first: 5) {
edges { edges {
node { node {
id id
} }
} }
} }
filmConnection(first: 5) { residentConnection(first: 5) {
edges { edges {
node { node {
id id
@ -84,6 +84,13 @@ query($id: ID!) {
} }
} }
... on Species { ... on Species {
filmConnection(first: 5) {
edges {
node {
id
}
}
}
homeworld { homeworld {
id id
} }
@ -94,23 +101,16 @@ query($id: ID!) {
} }
} }
} }
filmConnection(first: 5) {
edges {
node {
id
}
}
}
} }
... on Starship { ... on Starship {
pilotConnection(first: 5) { filmConnection(first: 5) {
edges { edges {
node { node {
id id
} }
} }
} }
filmConnection(first: 5) { pilotConnection(first: 5) {
edges { edges {
node { node {
id id
@ -119,14 +119,14 @@ query($id: ID!) {
} }
} }
... on Vehicle { ... on Vehicle {
pilotConnection(first: 5) { filmConnection(first: 5) {
edges { edges {
node { node {
id id
} }
} }
} }
filmConnection(first: 5) { pilotConnection(first: 5) {
edges { edges {
node { node {
id id

1575
spec/fixtures/swapi_schema.graphql поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -2,55 +2,59 @@ require 'graphql/relay/walker'
require 'graphql/client' require 'graphql/client'
describe GraphQL::Relay::Walker::QueryBuilder do describe GraphQL::Relay::Walker::QueryBuilder do
let(:schema_path) { 'spec/fixtures/swapi_schema.json' } [
let(:query_path) { 'spec/fixtures/swapi_query.graphql' } GraphQL::Client.load_schema('spec/fixtures/swapi_schema.json'),
let(:schema) { GraphQL::Client.load_schema(schema_path) } GraphQL::Schema.from_definition("spec/fixtures/swapi_schema.graphql")
let(:client) { GraphQL::Client.new(schema: schema) } ].each do |schema|
let(:query_builder) { described_class.new(schema) } describe "With #{schema.class} schema" do
let(:ast) { query_builder.ast } let(:query_path) { 'spec/fixtures/swapi_query.graphql' }
let(:query_string) { query_builder.query_string } 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 it 'adds an alias to all fields except id and node' do
subject { ast } 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 describe 'query_string' do
fields(ast).reject do |node| subject { query_string }
%w[node id].include?(node.name)
end.each do |field| it 'generates a valid query for the schema' do
expect(field.alias).not_to be_nil 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 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 end