apollo-federation 1.1.0 → 1.1.4

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
  SHA256:
3
- metadata.gz: c06244e9136f28d4a4c15906ab176c9d4d83ea685c1b2a73bcac91d6f1ac8e65
4
- data.tar.gz: f9f9eec152182226fe35fc07f97f7e4a787a7ba2cd45442f2725f8c7a593a28e
3
+ metadata.gz: 21cd789f15e0947e99f00d58223c517a58dc1358760c4372f24e55235d84a270
4
+ data.tar.gz: 0bf2d29c1ff4036be133922f7a9bfe1bea0bc925eed1d27e321de191af2403a8
5
5
  SHA512:
6
- metadata.gz: 451f1e7582fcbe3a85061755ef537678c819532879994584243c9ce9424883496d4ae9db79926dd25a7b5baab374395438d670e0c1967735ca31cf07519c5058
7
- data.tar.gz: b118eda2b44fb44499745b4161cb75fc75dc59b59e29158d434b8152e23ce772b1b51c47820a8e4c9fb5c0275262b0a02d826dc1bfeda56ad3d4fac697fb3ab7
6
+ metadata.gz: b139f2366cbc0a90051869c086350d2c7af63beef66376cbc4ce8190aaef422574489a0dd2d5599cfc223dcf7a92bf8191e2f424af31ce9c5a594e70a87d1372
7
+ data.tar.gz: 0d6f9257c11937ba747d54c6f903928ffef52a36875d87b67d8e877562fc34ba771545b5e8f81bf6c5a7c9de388bd0a56820a6e7acd34056c1a1699420f6378c
@@ -1,3 +1,38 @@
1
+ ## [1.1.4](https://github.com/Gusto/apollo-federation-ruby/compare/v1.1.3...v1.1.4) (2020-09-25)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **tracing:** properly handle parsing and validation errors ([#102](https://github.com/Gusto/apollo-federation-ruby/issues/102)) ([a1c2a41](https://github.com/Gusto/apollo-federation-ruby/commit/a1c2a41d3d01f06364d439cdcc273f4678fed7bd)), closes [#101](https://github.com/Gusto/apollo-federation-ruby/issues/101) [#101](https://github.com/Gusto/apollo-federation-ruby/issues/101)
7
+
8
+ ## [1.1.4-beta.1](https://github.com/Gusto/apollo-federation-ruby/compare/v1.1.3...v1.1.4-beta.1) (2020-09-21)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **tracing:** properly handle parsing and validation errors ([#101](https://github.com/Gusto/apollo-federation-ruby/issues/101)) ([6cf8202](https://github.com/Gusto/apollo-federation-ruby/commit/6cf820281dd85bd358c6bf4c176b9a73a9280d54))
14
+
15
+ ## [1.1.3](https://github.com/Gusto/apollo-federation-ruby/compare/v1.1.2...v1.1.3) (2020-07-16)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **tracing:** Properly handle tracing fields that resolve an array of lazy values ([#87](https://github.com/Gusto/apollo-federation-ruby/issues/87)) ([a9eed77](https://github.com/Gusto/apollo-federation-ruby/commit/a9eed77bbe5859456f93be00fbcafa02142ad5ed))
21
+
22
+ ## [1.1.2](https://github.com/Gusto/apollo-federation-ruby/compare/v1.1.1...v1.1.2) (2020-06-09)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * Fix _service field type owner ([#70](https://github.com/Gusto/apollo-federation-ruby/issues/70)) ([364e54f](https://github.com/Gusto/apollo-federation-ruby/commit/364e54fbb333b7cd4fe30f04bf72733b0e18d3f4))
28
+
29
+ ## [1.1.1](https://github.com/Gusto/apollo-federation-ruby/compare/v1.1.0...v1.1.1) (2020-05-29)
30
+
31
+
32
+ ### Bug Fixes
33
+
34
+ * **lazy resolve:** Handle problem with sync resolve ([#58](https://github.com/Gusto/apollo-federation-ruby/issues/58)) ([e66c22b](https://github.com/Gusto/apollo-federation-ruby/commit/e66c22ba6fe51a7c282190ee77bd02dbfa514a66))
35
+
1
36
  # [1.1.0](https://github.com/Gusto/apollo-federation-ruby/compare/v1.0.4...v1.1.0) (2020-05-27)
2
37
 
3
38
 
data/README.md CHANGED
@@ -175,14 +175,6 @@ To support [federated tracing](https://www.apollographql.com/docs/apollo-server/
175
175
  # ...
176
176
  end
177
177
  ```
178
- 3. Change your controller to attach the traces to the response:
179
- ```ruby
180
- def execute
181
- # ...
182
- result = YourSchema.execute(query, ...)
183
- render json: ApolloFederation::Tracing.attach_trace_to_result(result)
184
- end
185
- ```
186
178
 
187
179
  ## Known Issues and Limitations
188
180
  - Only works with class-based schemas, the legacy `.define` API will not be supported
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
+ Pathname.new(__FILE__).realpath,)
14
+
15
+ bundle_binstub = File.expand_path('bundle', __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require 'rubygems'
27
+ require 'bundler/setup'
28
+
29
+ load Gem.bin_path('rspec-core', 'rspec')
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'graphql'
4
4
  require 'apollo-federation/any'
5
- require 'apollo-federation/entity_type_resolution_extension'
6
5
 
7
6
  module ApolloFederation
8
7
  module EntitiesField
@@ -23,7 +22,6 @@ module ApolloFederation
23
22
 
24
23
  field(:_entities, [entity_type, null: true], null: false) do
25
24
  argument :representations, [Any], required: true
26
- extension(EntityTypeResolutionExtension)
27
25
  end
28
26
  end
29
27
  end
@@ -47,7 +45,14 @@ module ApolloFederation
47
45
  result = reference
48
46
  end
49
47
 
50
- [type, result]
48
+ context.schema.after_lazy(result) do |resolved_value|
49
+ # TODO: This isn't 100% correct: if (for some reason) 2 different resolve_reference calls
50
+ # return the same object, it might not have the right type
51
+ # Right now, apollo-federation just adds a __typename property to the result,
52
+ # but I don't really like the idea of modifying the resolved object
53
+ context[resolved_value] = type
54
+ resolved_value
55
+ end
51
56
  end
52
57
  end
53
58
  end
@@ -33,7 +33,7 @@ module ApolloFederation
33
33
  base = query_obj.metadata[:type_class]
34
34
  end
35
35
 
36
- Class.new(base) do
36
+ klass = Class.new(base) do
37
37
  # TODO: Maybe the name should inherit from the original Query name
38
38
  # Or MAYBE we should just modify the original class?
39
39
  graphql_name 'Query'
@@ -41,6 +41,9 @@ module ApolloFederation
41
41
  include EntitiesField
42
42
  include ServiceField
43
43
  end
44
+
45
+ klass.define_service_field
46
+ klass
44
47
  end
45
48
  end
46
49
 
@@ -5,9 +5,17 @@ require 'apollo-federation/service'
5
5
 
6
6
  module ApolloFederation
7
7
  module ServiceField
8
- extend GraphQL::Schema::Member::HasFields
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ extend GraphQL::Schema::Member::HasFields
9
14
 
10
- field(:_service, Service, null: false)
15
+ def define_service_field
16
+ field(:_service, Service, null: false)
17
+ end
18
+ end
11
19
 
12
20
  def _service
13
21
  schema_class = context.schema.is_a?(GraphQL::Schema) ? context.schema.class : context.schema
@@ -4,14 +4,6 @@ module ApolloFederation
4
4
  module Tracing
5
5
  KEY = :ftv1
6
6
  DEBUG_KEY = "#{KEY}_debug".to_sym
7
- class NotInstalledError < StandardError
8
- MESSAGE = 'Apollo Federation Tracing not installed. \
9
- Add `use ApolloFederation::Tracing` to your schema.'
10
-
11
- def message
12
- MESSAGE
13
- end
14
- end
15
7
 
16
8
  module_function
17
9
 
@@ -23,35 +15,10 @@ Add `use ApolloFederation::Tracing` to your schema.'
23
15
  headers && headers['apollo-federation-include-trace'] == KEY.to_s
24
16
  end
25
17
 
26
- def attach_trace_to_result(result)
27
- return result unless result.context[:tracing_enabled]
28
-
29
- trace = result.context.namespace(KEY)
30
- raise NotInstalledError unless trace[:start_time]
31
-
32
- result['errors']&.each do |error|
33
- trace[:node_map].add_error(error)
34
- end
35
-
36
- proto = ApolloFederation::Tracing::Trace.new(
37
- start_time: to_proto_timestamp(trace[:start_time]),
38
- end_time: to_proto_timestamp(trace[:end_time]),
39
- duration_ns: trace[:end_time_nanos] - trace[:start_time_nanos],
40
- root: trace[:node_map].root,
41
- )
42
-
43
- result[:extensions] ||= {}
44
- result[:extensions][KEY] = Base64.encode64(proto.class.encode(proto))
45
-
46
- if result.context[:debug_tracing]
47
- result[:extensions][DEBUG_KEY] = proto.to_h
48
- end
49
-
50
- result.to_h
51
- end
52
-
53
- def to_proto_timestamp(time)
54
- Google::Protobuf::Timestamp.new(seconds: time.to_i, nanos: time.nsec)
18
+ # @deprecated There is no need to call this method. Traces are added to the result automatically
19
+ def attach_trace_to_result(_result)
20
+ warn '[DEPRECATION] `attach_trace_to_result` is deprecated. There is no need to call it, as '\
21
+ 'traces are added to the result automatically'
55
22
  end
56
23
  end
57
24
  end
@@ -35,15 +35,15 @@ module ApolloFederation
35
35
  module Tracing
36
36
  module Tracer
37
37
  # store string constants to avoid creating new strings for each call to .trace
38
- EXECUTE_QUERY = 'execute_query'
38
+ EXECUTE_MULTIPLEX = 'execute_multiplex'
39
39
  EXECUTE_QUERY_LAZY = 'execute_query_lazy'
40
40
  EXECUTE_FIELD = 'execute_field'
41
41
  EXECUTE_FIELD_LAZY = 'execute_field_lazy'
42
42
 
43
43
  def self.trace(key, data, &block)
44
44
  case key
45
- when EXECUTE_QUERY
46
- execute_query(data, &block)
45
+ when EXECUTE_MULTIPLEX
46
+ execute_multiplex(data, &block)
47
47
  when EXECUTE_QUERY_LAZY
48
48
  execute_query_lazy(data, &block)
49
49
  when EXECUTE_FIELD
@@ -55,19 +55,26 @@ module ApolloFederation
55
55
  end
56
56
  end
57
57
 
58
- # Step 1:
59
- # Create a trace hash on the query context and record start times.
60
- def self.execute_query(data, &block)
61
- query = data.fetch(:query)
62
- return block.call unless query.context && query.context[:tracing_enabled]
58
+ def self.execute_multiplex(data, &block)
59
+ # Step 1:
60
+ # Create a trace hash on each query's context and record start times.
61
+ data.fetch(:multiplex).queries.each { |query| start_trace(query) }
62
+
63
+ results = block.call
64
+
65
+ # Step 5
66
+ # Attach the trace to the 'extensions' key of each result
67
+ results.map { |result| attach_trace_to_result(result) }
68
+ end
69
+
70
+ def self.start_trace(query)
71
+ return unless query.context && query.context[:tracing_enabled]
63
72
 
64
73
  query.context.namespace(ApolloFederation::Tracing::KEY).merge!(
65
74
  start_time: Time.now.utc,
66
75
  start_time_nanos: Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond),
67
76
  node_map: NodeMap.new,
68
77
  )
69
-
70
- block.call
71
78
  end
72
79
 
73
80
  # Step 4:
@@ -159,17 +166,27 @@ module ApolloFederation
159
166
 
160
167
  end_time_nanos = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
161
168
 
162
- # interpreter runtime
169
+ # legacy runtime
163
170
  if data.include?(:context)
164
- context = data.fetch(:context)
165
171
  path = context.path
166
- else # legacy runtime
167
- context = data.fetch(:query).context
172
+ field = context.field
173
+ else # interpreter runtime
168
174
  path = data.fetch(:path)
175
+ field = data.fetch(:field)
169
176
  end
170
177
 
171
178
  trace = context.namespace(ApolloFederation::Tracing::KEY)
172
179
 
180
+ # When a field is resolved with an array of lazy values, the interpreter fires an
181
+ # `execute_field` for the resolution of the field and then a `execute_field_lazy` event for
182
+ # each lazy value in the array. Since the path here will contain an index (indicating which
183
+ # lazy value we're executing: e.g. ['arrayOfLazies', 0]), we won't have a node for the path.
184
+ # We only care about the end of the parent field (e.g. ['arrayOfLazies']), so we get the
185
+ # node for that path. What ends up happening is we update the end_time for the parent node
186
+ # for each of the lazy values. The last one that's executed becomes the final end time.
187
+ if field.type.list? && path.last.is_a?(Integer)
188
+ path = path[0...-1]
189
+ end
173
190
  node = trace[:node_map].node_for_path(path)
174
191
  node.end_time = end_time_nanos - trace[:start_time_nanos]
175
192
 
@@ -178,6 +195,37 @@ module ApolloFederation
178
195
  result
179
196
  end
180
197
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
198
+
199
+ def self.attach_trace_to_result(result)
200
+ return result unless result.context[:tracing_enabled]
201
+
202
+ trace = result.context.namespace(ApolloFederation::Tracing::KEY)
203
+
204
+ result['errors']&.each do |error|
205
+ trace[:node_map].add_error(error)
206
+ end
207
+
208
+ proto = ApolloFederation::Tracing::Trace.new(
209
+ start_time: to_proto_timestamp(trace[:start_time]),
210
+ end_time: to_proto_timestamp(trace[:end_time]),
211
+ duration_ns: trace[:end_time_nanos] - trace[:start_time_nanos],
212
+ root: trace[:node_map].root,
213
+ )
214
+
215
+ result[:extensions] ||= {}
216
+ result[:extensions][ApolloFederation::Tracing::KEY] =
217
+ Base64.encode64(proto.class.encode(proto))
218
+
219
+ if result.context[:debug_tracing]
220
+ result[:extensions][ApolloFederation::Tracing::DEBUG_KEY] = proto.to_h
221
+ end
222
+
223
+ result
224
+ end
225
+
226
+ def self.to_proto_timestamp(time)
227
+ Google::Protobuf::Timestamp.new(seconds: time.to_i, nanos: time.nsec)
228
+ end
181
229
  end
182
230
  end
183
231
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ApolloFederation
4
- VERSION = '1.1.0'
4
+ VERSION = '1.1.4'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apollo-federation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noa Elad
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-05-27 00:00:00.000000000 Z
12
+ date: 2020-09-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: graphql
@@ -53,6 +53,34 @@ dependencies:
53
53
  - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: appraisal
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: debase
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
56
84
  - !ruby/object:Gem::Dependency
57
85
  name: pry-byebug
58
86
  requirement: !ruby/object:Gem::Requirement
@@ -137,6 +165,20 @@ dependencies:
137
165
  - - ">="
138
166
  - !ruby/object:Gem::Version
139
167
  version: '0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: ruby-debug-ide
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
140
182
  description: A Ruby implementation of Apollo Federation
141
183
  email:
142
184
  - noa.elad@gusto.com
@@ -149,13 +191,11 @@ files:
149
191
  - LICENSE
150
192
  - README.md
151
193
  - bin/generate-protos.sh
152
- - bin/prepare.rb
153
- - bin/publish.rb
194
+ - bin/rspec
154
195
  - lib/apollo-federation.rb
155
196
  - lib/apollo-federation/any.rb
156
197
  - lib/apollo-federation/entities_field.rb
157
198
  - lib/apollo-federation/entity.rb
158
- - lib/apollo-federation/entity_type_resolution_extension.rb
159
199
  - lib/apollo-federation/federated_document_from_schema_definition.rb
160
200
  - lib/apollo-federation/field.rb
161
201
  - lib/apollo-federation/has_directives.rb
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- def set_version
4
- new_version = ARGV[0]
5
-
6
- contents = File.read('lib/apollo-federation/version.rb')
7
-
8
- new_contents = contents.gsub(/VERSION = '[0-9.]*'/, "VERSION = '#{new_version}'")
9
- File.write('lib/apollo-federation/version.rb', new_contents)
10
- end
11
-
12
- def bundle_install
13
- system('bundle install')
14
- end
15
-
16
- def build_gem
17
- system('gem build apollo-federation.gemspec')
18
- end
19
-
20
- set_version
21
- bundle_install
22
- build_gem
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- new_version = ARGV[0]
4
- gem_name = "apollo-federation-#{new_version}.gem"
5
- system("gem push #{gem_name}")
6
- File.delete(gem_name)
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class EntityTypeResolutionExtension < GraphQL::Schema::FieldExtension
4
- def after_resolve(value:, context:, **_rest)
5
- synced_value =
6
- value.map do |type, result|
7
- [type, context.query.schema.sync_lazy(result)]
8
- end
9
-
10
- # TODO: This isn't 100% correct: if (for some reason) 2 different resolve_reference calls
11
- # return the same object, it might not have the right type
12
- # Right now, apollo-federation just adds a __typename property to the result,
13
- # but I don't really like the idea of modifying the resolved object
14
- synced_value.each { |type, result| context[result] = type }
15
-
16
- synced_value.map { |_, result| result }
17
- end
18
- end