graphql-client 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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