graphql_client 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml +1133 -0
- data/.rubocop.yml +24 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +45 -0
- data/CONTRIBUTING.md +28 -0
- data/CONTRIBUTING_DEVELOPER_CERTIFICATE_OF_ORIGIN.txt +37 -0
- data/Gemfile +8 -0
- data/LICENSE.md +21 -0
- data/README.md +96 -0
- data/Rakefile +13 -0
- data/bin/graphql-client +79 -0
- data/bin/rake +17 -0
- data/circle.yml +3 -0
- data/dev.yml +7 -0
- data/graphql_ruby_client.gemspec +26 -0
- data/lib/graphql_client.rb +46 -0
- data/lib/graphql_client/adapters/http_adapter.rb +72 -0
- data/lib/graphql_client/base.rb +53 -0
- data/lib/graphql_client/config.rb +42 -0
- data/lib/graphql_client/deserialization.rb +36 -0
- data/lib/graphql_client/error.rb +22 -0
- data/lib/graphql_client/graph_connection.rb +21 -0
- data/lib/graphql_client/graph_node.rb +24 -0
- data/lib/graphql_client/graph_object.rb +56 -0
- data/lib/graphql_client/introspection_query.rb +80 -0
- data/lib/graphql_client/query/add_inline_fragment.rb +42 -0
- data/lib/graphql_client/query/argument.rb +30 -0
- data/lib/graphql_client/query/document.rb +86 -0
- data/lib/graphql_client/query/field.rb +91 -0
- data/lib/graphql_client/query/fragment.rb +41 -0
- data/lib/graphql_client/query/has_selection_set.rb +72 -0
- data/lib/graphql_client/query/inline_fragment.rb +35 -0
- data/lib/graphql_client/query/mutation_document.rb +20 -0
- data/lib/graphql_client/query/operation.rb +46 -0
- data/lib/graphql_client/query/operations/mutation_operation.rb +17 -0
- data/lib/graphql_client/query/operations/query_operation.rb +17 -0
- data/lib/graphql_client/query/query_document.rb +20 -0
- data/lib/graphql_client/query/selection_set.rb +53 -0
- data/lib/graphql_client/response.rb +21 -0
- data/lib/graphql_client/response_connection.rb +18 -0
- data/lib/graphql_client/response_object.rb +32 -0
- data/lib/graphql_client/schema_patches.rb +17 -0
- data/lib/graphql_client/version.rb +7 -0
- data/shipit.rubygems.yml +1 -0
- data/shipit.yml +4 -0
- data/test/graphql_client/adapters/http_adapter_test.rb +111 -0
- data/test/graphql_client/config_test.rb +68 -0
- data/test/graphql_client/graph_connection_test.rb +8 -0
- data/test/graphql_client/graph_node_test.rb +8 -0
- data/test/graphql_client/graph_object_query_transforming_test.rb +156 -0
- data/test/graphql_client/graph_object_test.rb +142 -0
- data/test/graphql_client/graphql_client_test.rb +41 -0
- data/test/graphql_client/http_client_test.rb +52 -0
- data/test/graphql_client/query/add_inline_fragment_test.rb +57 -0
- data/test/graphql_client/query/arguments_test.rb +89 -0
- data/test/graphql_client/query/document_test.rb +246 -0
- data/test/graphql_client/query/field_test.rb +163 -0
- data/test/graphql_client/query/fragment_test.rb +45 -0
- data/test/graphql_client/query/inline_fragment_test.rb +37 -0
- data/test/graphql_client/query/mutation_document_test.rb +30 -0
- data/test/graphql_client/query/mutation_operation_test.rb +89 -0
- data/test/graphql_client/query/query_document_test.rb +30 -0
- data/test/graphql_client/query/query_operation_test.rb +262 -0
- data/test/graphql_client/query/selection_set_test.rb +116 -0
- data/test/graphql_client/response_connection_test.rb +50 -0
- data/test/graphql_client/response_object_test.rb +109 -0
- data/test/graphql_client/response_test.rb +67 -0
- data/test/support/fixtures/schema.json +13710 -0
- data/test/test_helper.rb +37 -0
- metadata +227 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Client
|
5
|
+
module Query
|
6
|
+
Argument = Struct.new(:value) do
|
7
|
+
def to_query
|
8
|
+
case value
|
9
|
+
when FalseClass, Float, Integer, NilClass, String, TrueClass
|
10
|
+
generate_query_value(value)
|
11
|
+
when Array
|
12
|
+
"[#{value.map { |v| generate_query_value(v) }.join(', ')}]"
|
13
|
+
when Hash
|
14
|
+
"{ #{value.map { |k, v| "#{k}: #{generate_query_value(v)}" }.join(', ')} }"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def generate_query_value(value)
|
21
|
+
if value.to_s.start_with?('$')
|
22
|
+
value
|
23
|
+
else
|
24
|
+
JSON.generate(value, quirks_mode: true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Client
|
5
|
+
module Query
|
6
|
+
class Document
|
7
|
+
DEFAULT_OPERATION_NAME = 'default'
|
8
|
+
|
9
|
+
DUPLICATE_OPERATION_NAME = Class.new(StandardError)
|
10
|
+
INVALID_DOCUMENT = Class.new(StandardError)
|
11
|
+
INVALID_FRAGMENT_TARGET = Class.new(StandardError)
|
12
|
+
|
13
|
+
attr_reader :fragments, :operations, :schema
|
14
|
+
|
15
|
+
def initialize(schema)
|
16
|
+
@schema = schema
|
17
|
+
@fragments = {}
|
18
|
+
@operations = {}
|
19
|
+
|
20
|
+
yield self if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_mutation(name = nil, &block)
|
24
|
+
add_operation(MutationOperation, name, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_query(name = nil, &block)
|
28
|
+
add_operation(QueryOperation, name, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def define_fragment(name, on:)
|
32
|
+
type = schema.type(on)
|
33
|
+
|
34
|
+
unless type.object? || type.interface? || type.union?
|
35
|
+
raise INVALID_FRAGMENT_TARGET, "invalid target type (#{type.kind}) for fragment #{name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
fragment = Fragment.new(name, type, document: self)
|
39
|
+
@fragments[name] = fragment
|
40
|
+
|
41
|
+
if block_given?
|
42
|
+
yield fragment
|
43
|
+
else
|
44
|
+
fragment
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def fragment_definitions
|
49
|
+
fragments.values.map(&:to_definition).join("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_query
|
53
|
+
''.dup.tap do |query_string|
|
54
|
+
query_string << "#{fragment_definitions}\n" unless fragments.empty?
|
55
|
+
query_string << operations.values.map(&:to_query).join("\n")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_method :to_s, :to_query
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def add_operation(operation_type, name)
|
64
|
+
if operations.key?(DEFAULT_OPERATION_NAME)
|
65
|
+
raise INVALID_DOCUMENT, 'a document with multiple operations must have named operations'
|
66
|
+
end
|
67
|
+
|
68
|
+
operation = operation_type.new(self, name: name)
|
69
|
+
operation_name = operation.name || DEFAULT_OPERATION_NAME
|
70
|
+
|
71
|
+
if operations.key?(operation_name)
|
72
|
+
raise DUPLICATE_OPERATION_NAME, "an operation named #{operation_name} already exists in the document"
|
73
|
+
end
|
74
|
+
|
75
|
+
@operations[operation_name] = operation
|
76
|
+
|
77
|
+
if block_given?
|
78
|
+
yield operation
|
79
|
+
else
|
80
|
+
operation
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Client
|
5
|
+
module Query
|
6
|
+
class Field
|
7
|
+
include AddInlineFragment
|
8
|
+
include HasSelectionSet
|
9
|
+
|
10
|
+
INVALID_ARGUMENTS = Class.new(StandardError)
|
11
|
+
|
12
|
+
attr_reader :arguments, :as, :document, :field_defn
|
13
|
+
|
14
|
+
def initialize(field_defn, document:, arguments: {}, as: nil)
|
15
|
+
@field_defn = field_defn
|
16
|
+
@document = document
|
17
|
+
@arguments = validate_arguments(arguments)
|
18
|
+
@as = as
|
19
|
+
@selection_set = SelectionSet.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_arguments(**arguments)
|
23
|
+
new_arguments = validate_arguments(arguments)
|
24
|
+
@arguments.merge!(new_arguments)
|
25
|
+
end
|
26
|
+
|
27
|
+
def aliased?
|
28
|
+
name != field_defn.name
|
29
|
+
end
|
30
|
+
|
31
|
+
def arguments=(arguments)
|
32
|
+
@arguments = validate_arguments(arguments)
|
33
|
+
end
|
34
|
+
|
35
|
+
def connection?
|
36
|
+
resolver_type.name.to_s.end_with?('Connection')
|
37
|
+
end
|
38
|
+
|
39
|
+
def name
|
40
|
+
as || field_defn.name
|
41
|
+
end
|
42
|
+
|
43
|
+
def node?
|
44
|
+
field_defn.name == 'Node' || (resolver_type.object? && resolver_type.implement?('Node'))
|
45
|
+
end
|
46
|
+
|
47
|
+
def resolver_type
|
48
|
+
@resolver_type ||= schema.type(field_defn.type.unwrap.name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def schema
|
52
|
+
document.schema
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_query(indent: '')
|
56
|
+
indent.dup.tap do |query_string|
|
57
|
+
query_string << "#{as}: " if aliased?
|
58
|
+
query_string << field_defn.name
|
59
|
+
query_string << "(#{arguments_string.join(', ')})" if arguments.any?
|
60
|
+
|
61
|
+
unless selection_set.empty?
|
62
|
+
query_string << " {\n"
|
63
|
+
query_string << selection_set.to_query(indent)
|
64
|
+
query_string << "\n#{indent}}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
alias_method :to_s, :to_query
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def arguments_string
|
74
|
+
arguments.map do |name, value|
|
75
|
+
"#{name}: #{value.to_query}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate_arguments(arguments)
|
80
|
+
arguments.each_with_object({}) do |(arg_name, value), hash|
|
81
|
+
if field_defn.args.any? { |arg| arg.name == arg_name.to_s }
|
82
|
+
hash[arg_name] = value.is_a?(Argument) ? value : Argument.new(value)
|
83
|
+
else
|
84
|
+
raise INVALID_ARGUMENTS, "#{arg_name} is not a valid arg for #{name}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Client
|
5
|
+
module Query
|
6
|
+
class Fragment
|
7
|
+
include AddInlineFragment
|
8
|
+
include HasSelectionSet
|
9
|
+
|
10
|
+
attr_reader :document, :name, :type
|
11
|
+
|
12
|
+
def initialize(name, type, document:)
|
13
|
+
@name = name
|
14
|
+
@type = type
|
15
|
+
@document = document
|
16
|
+
@selection_set = SelectionSet.new
|
17
|
+
|
18
|
+
yield self if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
def resolver_type
|
22
|
+
type
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_definition(indent: '')
|
26
|
+
indent.dup.tap do |query_string|
|
27
|
+
query_string << "fragment #{name} on #{type.name} {\n"
|
28
|
+
query_string << selection_set.to_query(indent)
|
29
|
+
query_string << "\n#{indent}}\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_query(indent: '')
|
34
|
+
"#{indent}...#{name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :to_s, :to_query
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Client
|
5
|
+
module Query
|
6
|
+
module HasSelectionSet
|
7
|
+
ID_FIELD_NAME = 'id'
|
8
|
+
INVALID_FIELD = Class.new(StandardError)
|
9
|
+
UNDEFINED_FRAGMENT = Class.new(StandardError)
|
10
|
+
|
11
|
+
attr_accessor :selection_set
|
12
|
+
|
13
|
+
def add_connection(connection_name, as: nil, **arguments)
|
14
|
+
node_field = nil
|
15
|
+
|
16
|
+
add_field(connection_name, as: as, **arguments) do |connection|
|
17
|
+
connection.add_field('edges') do |edges|
|
18
|
+
edges.add_field('cursor')
|
19
|
+
edges.add_field('node') do |node|
|
20
|
+
node_field = node
|
21
|
+
yield node
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
connection.add_field('pageInfo') do |page_info|
|
26
|
+
page_info.add_field('hasPreviousPage')
|
27
|
+
page_info.add_field('hasNextPage')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
node_field
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_field(field_name, as: nil, **arguments)
|
35
|
+
field_defn = resolve(field_name)
|
36
|
+
field = Field.new(field_defn, arguments: arguments, as: as, document: document)
|
37
|
+
selection_set.add_field(field)
|
38
|
+
|
39
|
+
field.add_field(ID_FIELD_NAME) if field.node?
|
40
|
+
|
41
|
+
if block_given?
|
42
|
+
yield field
|
43
|
+
else
|
44
|
+
field
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_fields(*field_names)
|
49
|
+
field_names.each do |field_name|
|
50
|
+
add_field(field_name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_fragment(fragment_name)
|
55
|
+
fragment = document.fragments.fetch(fragment_name) do
|
56
|
+
raise UNDEFINED_FRAGMENT, "a fragment named #{fragment_name} has not been defined"
|
57
|
+
end
|
58
|
+
|
59
|
+
selection_set.add_fragment(fragment)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def resolve(field_name)
|
65
|
+
resolver_type.field(field_name) do
|
66
|
+
raise INVALID_FIELD, "#{field_name} is not a valid field for #{resolver_type}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Client
|
5
|
+
module Query
|
6
|
+
class InlineFragment
|
7
|
+
include HasSelectionSet
|
8
|
+
|
9
|
+
attr_reader :document, :type
|
10
|
+
|
11
|
+
def initialize(type, document:)
|
12
|
+
@type = type
|
13
|
+
@document = document
|
14
|
+
@selection_set = SelectionSet.new
|
15
|
+
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def resolver_type
|
20
|
+
type
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_query(indent: '')
|
24
|
+
indent.dup.tap do |query_string|
|
25
|
+
query_string << "... on #{type.name} {\n"
|
26
|
+
query_string << selection_set.to_query(indent)
|
27
|
+
query_string << "\n#{indent}}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :to_s, :to_query
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Client
|
5
|
+
module Query
|
6
|
+
class MutationDocument
|
7
|
+
def self.new(schema, name = nil)
|
8
|
+
document = Document.new(schema)
|
9
|
+
mutation = document.add_mutation(name)
|
10
|
+
|
11
|
+
if block_given?
|
12
|
+
yield mutation
|
13
|
+
end
|
14
|
+
|
15
|
+
mutation
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Client
|
5
|
+
module Query
|
6
|
+
class Operation
|
7
|
+
include HasSelectionSet
|
8
|
+
|
9
|
+
attr_reader :document, :name, :selection_set, :variables
|
10
|
+
|
11
|
+
def initialize(document, name: nil, variables: {})
|
12
|
+
@document = document
|
13
|
+
@name = name
|
14
|
+
@selection_set = SelectionSet.new
|
15
|
+
@variables = variables
|
16
|
+
|
17
|
+
yield self if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
def schema
|
21
|
+
document.schema
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_query
|
25
|
+
operation_type.dup.tap do |query_string|
|
26
|
+
query_string << " #{name}" if name
|
27
|
+
query_string << "(#{variables_string.join(', ')})" if variables.any?
|
28
|
+
query_string << " {\n"
|
29
|
+
query_string << selection_set.to_query
|
30
|
+
query_string << "\n}\n"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :to_s, :to_query
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def variables_string
|
39
|
+
variables.map do |name, type|
|
40
|
+
"$#{name}: #{type}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Client
|
5
|
+
module Query
|
6
|
+
class MutationOperation < Operation
|
7
|
+
def operation_type
|
8
|
+
'mutation'
|
9
|
+
end
|
10
|
+
|
11
|
+
def resolver_type
|
12
|
+
schema.type(schema.mutation_root_name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|