apollo-federation 1.1.0 → 1.1.4

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
  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