graphql-client 0.9.0 → 0.10.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 682f2fda3a85b90c7f7ebe9ff03c6c3b8a6853e3
4
- data.tar.gz: 85ad7663333b37fc0e7407254211b88ea670dab6
3
+ metadata.gz: 54d0c609ffe31a1375380c8647fe4a1502f19a76
4
+ data.tar.gz: 57dc7311a2da955fa6a7297aa383eacf7473bee6
5
5
  SHA512:
6
- metadata.gz: 8df3fe212b2ae0ba9ed43b26b326a40d54e5de231bd1958cd1538cdf5fef6d4cf65743938c73310bc84523f053e2ac4cd4a8da90c5015cf1d9195215d261d74e
7
- data.tar.gz: 0e0fa5380ae41b37113927d592ccdb39e03f586a2a07155e590963b2c4d07a1c5048e83357aadfa0c7ecd0e8e7cfeba130ec4d1cbfd4ebf0a9766733151e1fdb
6
+ metadata.gz: 645186f0b676ade5ec368fe51c0a0fc97110fb6d100eb08411b8f079b8e793c5837c94bde44d44a4ec119fe40593ddce826c943a3ee660635e4c8899dbcdfdcd
7
+ data.tar.gz: ad0e699625c3f34dfec487284e94ca6a4e4dc31b2522f4a7909a162f9d7db6d27d42873b6a057e3b95158027a93f9eb3708dd3f12535a2d51f0da3fa1604a31c
data/README.md CHANGED
@@ -94,7 +94,7 @@ end
94
94
 
95
95
  ### Executing queries
96
96
 
97
- Pass the reference of a parsed query definition to `GraphQL::Client#query`. Data is returned back in a wrapped `GraphQL::QueryResult` struct that provides Ruby-ish accessors.
97
+ Pass the reference of a parsed query definition to `GraphQL::Client#query`. Data is returned back in a wrapped `GraphQL::Client::Schema::ObjectType` struct that provides Ruby-ish accessors.
98
98
 
99
99
  ``` ruby
100
100
  result = SWAPI::Client.query(Hero::Query)
@@ -9,9 +9,9 @@ require "graphql/client/error"
9
9
  require "graphql/client/errors"
10
10
  require "graphql/client/fragment_definition"
11
11
  require "graphql/client/operation_definition"
12
- require "graphql/client/query_result"
13
12
  require "graphql/client/query_typename"
14
13
  require "graphql/client/response"
14
+ require "graphql/client/schema"
15
15
  require "graphql/language/nodes/deep_freeze_ext"
16
16
  require "json"
17
17
 
@@ -31,6 +31,8 @@ module GraphQL
31
31
 
32
32
  attr_reader :schema, :execute
33
33
 
34
+ attr_reader :types
35
+
34
36
  attr_accessor :document_tracking_enabled
35
37
 
36
38
  # Public: Check if collocated caller enforcement is enabled.
@@ -89,6 +91,8 @@ module GraphQL
89
91
  @document_tracking_enabled = false
90
92
  @allow_dynamic_queries = false
91
93
  @enforce_collocated_callers = enforce_collocated_callers
94
+
95
+ @types = Schema.generate(@schema)
92
96
  end
93
97
 
94
98
  def parse(str, filename = nil, lineno = nil)
@@ -146,6 +150,9 @@ module GraphQL
146
150
 
147
151
  doc = GraphQL.parse(str)
148
152
 
153
+ document_types = DocumentTypes.analyze_types(self.schema, doc).freeze
154
+ QueryTypename.insert_typename_fields(doc, types: document_types)
155
+
149
156
  doc.definitions.each do |node|
150
157
  node.name ||= "__anonymous__"
151
158
  end
@@ -168,21 +175,24 @@ module GraphQL
168
175
  raise error
169
176
  end
170
177
 
171
- document_types = DocumentTypes.analyze_types(self.schema, doc).freeze
172
-
173
- QueryTypename.insert_typename_fields(doc, types: document_types)
174
-
175
178
  definitions = {}
176
179
  doc.definitions.each do |node|
180
+ irep_node = case node
181
+ when GraphQL::Language::Nodes::OperationDefinition
182
+ errors[:irep].operation_definitions[node.name]
183
+ when GraphQL::Language::Nodes::FragmentDefinition
184
+ errors[:irep].fragment_definitions[node.name]
185
+ else
186
+ raise TypeError, "unexpected #{node.class}"
187
+ end
188
+
177
189
  node.name = nil if node.name == "__anonymous__"
178
190
  sliced_document = Language::DefinitionSlice.slice(document_dependencies, node.name)
179
191
  definition = Definition.for(
180
- schema: self.schema,
181
- node: node,
192
+ client: self,
193
+ irep_node: irep_node,
182
194
  document: sliced_document,
183
- document_types: document_types,
184
- source_location: source_location,
185
- enforce_collocated_callers: enforce_collocated_callers
195
+ source_location: source_location
186
196
  )
187
197
  definitions[node.name] = definition
188
198
  end
@@ -1,4 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require "graphql"
4
+ require "graphql/client/collocated_enforcement"
5
+ require "graphql/client/schema/object_type"
6
+ require "graphql/client/schema/possible_types"
7
+ require "set"
8
+
2
9
  module GraphQL
3
10
  class Client
4
11
  # Definitions are constructed by Client.parse and wrap a parsed AST of the
@@ -7,31 +14,48 @@ module GraphQL
7
14
  #
8
15
  # Definitions MUST be assigned to a constant.
9
16
  class Definition < Module
10
- def self.for(node:, **kargs)
11
- case node
17
+ def self.for(irep_node:, **kargs)
18
+ case irep_node.ast_node
12
19
  when Language::Nodes::OperationDefinition
13
- OperationDefinition.new(node: node, **kargs)
20
+ OperationDefinition.new(irep_node: irep_node, **kargs)
14
21
  when Language::Nodes::FragmentDefinition
15
- FragmentDefinition.new(node: node, **kargs)
22
+ FragmentDefinition.new(irep_node: irep_node, **kargs)
16
23
  else
17
- raise TypeError, "expected node to be a definition type, but was #{node.class}"
24
+ raise TypeError, "expected node to be a definition type, but was #{irep_node.ast_node.class}"
18
25
  end
19
26
  end
20
27
 
21
- def initialize(node:, document:, schema:, document_types:, source_location:, enforce_collocated_callers:)
22
- @definition_node = node
28
+ def initialize(client:, document:, irep_node:, source_location:)
29
+ @client = client
23
30
  @document = document
24
- @schema = schema
25
- @document_types = document_types
31
+ @definition_irep_node = irep_node
26
32
  @source_location = source_location
27
- @enforce_collocated_callers = enforce_collocated_callers
33
+ @schema_class = client.types.define_class(self, definition_irep_node, definition_irep_node.return_type)
28
34
  end
29
35
 
36
+ # Internal: Get associated owner GraphQL::Client instance.
37
+ attr_reader :client
38
+
39
+ # Internal root schema class for defintion. Returns
40
+ # GraphQL::Client::Schema::ObjectType or
41
+ # GraphQL::Client::Schema::PossibleTypes.
42
+ attr_reader :schema_class
43
+
44
+ # Deprecated: Use schema_class
45
+ alias_method :type, :schema_class
46
+
47
+ # Internal: Get underlying IRep Node for the definition.
48
+ #
49
+ # Returns GraphQL::InternalRepresentation::Node object.
50
+ attr_reader :definition_irep_node
51
+
30
52
  # Internal: Get underlying operation or fragment defintion AST node for
31
53
  # definition.
32
54
  #
33
55
  # Returns OperationDefinition or FragmentDefinition object.
34
- attr_reader :definition_node
56
+ def definition_node
57
+ definition_irep_node.ast_node
58
+ end
35
59
 
36
60
  # Public: Global name of definition in client document.
37
61
  #
@@ -57,27 +81,96 @@ module GraphQL
57
81
  # and any FragmentDefinition dependencies.
58
82
  attr_reader :document
59
83
 
60
- # Internal: Mapping of document nodes to schema types.
61
- attr_reader :document_types
62
-
63
- attr_reader :schema
64
-
65
84
  # Public: Returns the Ruby source filename and line number containing this
66
85
  # definition was not defined in Ruby.
67
86
  #
68
87
  # Returns Array pair of [String, Fixnum].
69
88
  attr_reader :source_location
70
89
 
71
- attr_reader :enforce_collocated_callers
72
-
73
- def new(*args)
74
- type.new(*args)
90
+ def new(obj, errors = Errors.new)
91
+ case schema_class
92
+ when GraphQL::Client::Schema::PossibleTypes
93
+ case obj
94
+ when NilClass
95
+ nil
96
+ else
97
+ schema_class.cast(obj.to_h, obj.errors)
98
+ end
99
+ when GraphQL::Client::Schema::ObjectType
100
+ case obj
101
+ when NilClass, schema_class
102
+ obj
103
+ when Hash
104
+ schema_class.new(obj, errors)
105
+ else
106
+ if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
107
+ unless obj.class._spreads.include?(definition_node.name)
108
+ raise TypeError, "#{definition_node.name} is not included in #{obj.class.source_definition.name}"
109
+ end
110
+ schema_class.cast(obj.to_h, obj.errors)
111
+ else
112
+ raise TypeError, "unexpected #{obj.class}"
113
+ end
114
+ end
115
+ else
116
+ raise TypeError, "unexpected #{schema_class}"
117
+ end
75
118
  end
76
119
 
77
- def type
78
- # TODO: Fix type indirection
79
- @type ||= GraphQL::Client::QueryResult.wrap(self, definition_node, document_types[definition_node], name: "#{name}.type")
120
+ # Internal: Nodes AST indexes.
121
+ def indexes
122
+ @indexes ||= begin
123
+ visitor = GraphQL::Language::Visitor.new(document)
124
+ definitions = index_node_definitions(visitor)
125
+ spreads = index_spreads(visitor)
126
+ visitor.visit
127
+ { definitions: definitions, spreads: spreads }
128
+ end
80
129
  end
130
+
131
+ private
132
+ def index_spreads(visitor)
133
+ spreads = {}
134
+ on_node = ->(node, _parent) { spreads[node] = Set.new(flatten_spreads(node).map(&:name)) }
135
+
136
+ visitor[GraphQL::Language::Nodes::Field] << on_node
137
+ visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
138
+ visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
139
+
140
+ spreads
141
+ end
142
+
143
+ def flatten_spreads(node)
144
+ node.selections.flat_map do |selection|
145
+ case selection
146
+ when Language::Nodes::FragmentSpread
147
+ selection
148
+ when Language::Nodes::InlineFragment
149
+ flatten_spreads(selection)
150
+ else
151
+ []
152
+ end
153
+ end
154
+ end
155
+
156
+ def index_node_definitions(visitor)
157
+ current_definition = nil
158
+ enter_definition = ->(node, _parent) { current_definition = node }
159
+ leave_definition = ->(node, _parent) { current_definition = nil }
160
+
161
+ visitor[GraphQL::Language::Nodes::FragmentDefinition].enter << enter_definition
162
+ visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << leave_definition
163
+ visitor[GraphQL::Language::Nodes::OperationDefinition].enter << enter_definition
164
+ visitor[GraphQL::Language::Nodes::OperationDefinition].leave << leave_definition
165
+
166
+ definitions = {}
167
+ on_node = ->(node, _parent) { definitions[node] = current_definition }
168
+ visitor[GraphQL::Language::Nodes::Field] << on_node
169
+ visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
170
+ visitor[GraphQL::Language::Nodes::InlineFragment] << on_node
171
+ visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
172
+ definitions
173
+ end
81
174
  end
82
175
  end
83
176
  end
@@ -4,7 +4,7 @@ require "active_support/deprecation"
4
4
  module GraphQL
5
5
  class Client
6
6
  if ActiveSupport::Deprecation.is_a?(Class)
7
- Deprecation = ActiveSupport::Deprecation.new("0.9", "graphql-client")
7
+ Deprecation = ActiveSupport::Deprecation.new("11.0", "graphql-client")
8
8
  else
9
9
  module Deprecation
10
10
  extend self
@@ -22,7 +22,7 @@ module GraphQL
22
22
  end
23
23
 
24
24
  def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
25
- warn "#{deprecated_method_name} is deprecated and will be removed from graphql-client 0.9 (#{message})"
25
+ warn "#{deprecated_method_name} is deprecated and will be removed from graphql-client 0.11 (#{message})"
26
26
  end
27
27
  end
28
28
  end
@@ -39,6 +39,9 @@ module GraphQL
39
39
  end
40
40
  visitor.visit
41
41
 
42
+ fields
43
+ rescue StandardError
44
+ # FIXME: TypeStack my crash on invalid documents
42
45
  fields
43
46
  end
44
47
  end
@@ -5,6 +5,9 @@ module GraphQL
5
5
  class Error < StandardError
6
6
  end
7
7
 
8
+ class InvariantError < Error
9
+ end
10
+
8
11
  class ImplicitlyFetchedFieldError < NoMethodError
9
12
  end
10
13
 
@@ -7,11 +7,11 @@ module GraphQL
7
7
  #
8
8
  # https://facebook.github.io/graphql/#sec-Response-Format
9
9
  class Response
10
- # Public: Wrapped QueryResult of data returned from the server.
10
+ # Public: Wrapped ObjectType of data returned from the server.
11
11
  #
12
12
  # https://facebook.github.io/graphql/#sec-Data
13
13
  #
14
- # Returns instance of QueryResult subclass.
14
+ # Returns instance of ObjectType subclass.
15
15
  attr_reader :data
16
16
 
17
17
  # Public: Get partial failures from response.
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "graphql"
4
+ require "graphql/client/schema/enum_type"
5
+ require "graphql/client/schema/include_directive"
6
+ require "graphql/client/schema/interface_type"
7
+ require "graphql/client/schema/list_type"
8
+ require "graphql/client/schema/non_null_type"
9
+ require "graphql/client/schema/object_type"
10
+ require "graphql/client/schema/scalar_type"
11
+ require "graphql/client/schema/skip_directive"
12
+ require "graphql/client/schema/union_type"
13
+
14
+ module GraphQL
15
+ class Client
16
+ module Schema
17
+ module ClassMethods
18
+ def define_class(definition, irep_node, type)
19
+ type_klass = case type
20
+ when GraphQL::NonNullType
21
+ define_class(definition, irep_node, type.of_type).to_non_null_type
22
+ when GraphQL::ListType
23
+ define_class(definition, irep_node, type.of_type).to_list_type
24
+ else
25
+ const_get(type.name).define_class(definition, irep_node)
26
+ end
27
+
28
+ irep_node.ast_node.directives.inject(type_klass) do |klass, directive|
29
+ if directive = self.directives[directive.name.to_sym]
30
+ directive.new(klass)
31
+ else
32
+ klass
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def self.generate(schema)
39
+ mod = Module.new
40
+ mod.extend ClassMethods
41
+
42
+ mod.define_singleton_method :schema do
43
+ schema
44
+ end
45
+
46
+ cache = {}
47
+ schema.types.each do |name, type|
48
+ next if name.start_with?("__")
49
+ if klass = class_for(schema, type, cache)
50
+ klass.schema_module = mod
51
+ mod.const_set(name, klass)
52
+ end
53
+ end
54
+
55
+ directives = {}
56
+ mod.define_singleton_method(:directives) { directives }
57
+ directives[:include] = IncludeDirective
58
+ directives[:skip] = SkipDirective
59
+
60
+ mod
61
+ end
62
+
63
+ def self.class_for(schema, type, cache)
64
+ return cache[type] if cache[type]
65
+
66
+ case type
67
+ when GraphQL::InputObjectType
68
+ nil
69
+ when GraphQL::ScalarType
70
+ cache[type] = ScalarType.new(type)
71
+ when GraphQL::EnumType
72
+ cache[type] = EnumType.new(type)
73
+ when GraphQL::ListType
74
+ cache[type] = class_for(schema, type.of_type, cache).to_list_type
75
+ when GraphQL::NonNullType
76
+ cache[type] = class_for(schema, type.of_type, cache).to_non_null_type
77
+ when GraphQL::UnionType
78
+ klass = cache[type] = UnionType.new(type)
79
+
80
+ type.possible_types.each do |possible_type|
81
+ possible_klass = class_for(schema, possible_type, cache)
82
+ possible_klass.send :include, klass
83
+ end
84
+
85
+ klass
86
+ when GraphQL::InterfaceType
87
+ cache[type] = InterfaceType.new(type)
88
+ when GraphQL::ObjectType
89
+ klass = cache[type] = ObjectType.new(type)
90
+
91
+ type.interfaces.each do |interface|
92
+ klass.send :include, class_for(schema, interface, cache)
93
+ end
94
+
95
+ type.all_fields.each do |field|
96
+ klass.fields[field.name.to_sym] = class_for(schema, field.type, cache)
97
+ end
98
+
99
+ klass
100
+ else
101
+ raise TypeError, "unexpected #{type.class}"
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Client
5
+ module Schema
6
+ module BaseType
7
+ # Public: Get associated GraphQL::BaseType with for this class.
8
+ attr_reader :type
9
+
10
+ # Internal: Get owner schema Module container.
11
+ attr_accessor :schema_module
12
+
13
+ # Internal: Cast JSON value to wrapped value.
14
+ #
15
+ # value - JSON value
16
+ # errors - Errors instance
17
+ #
18
+ # Returns BaseType instance.
19
+ def cast(value, errors)
20
+ raise NotImplementedError, "subclasses must implement #cast(value, errors)"
21
+ end
22
+
23
+ # Internal: Get non-nullable wrapper of this type class.
24
+ #
25
+ # Returns NonNullType instance.
26
+ def to_non_null_type
27
+ @null_type ||= NonNullType.new(self)
28
+ end
29
+
30
+ # Internal: Get list wrapper of this type class.
31
+ #
32
+ # Returns ListType instance.
33
+ def to_list_type
34
+ @list_type ||= ListType.new(self)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end