graph_weaver 0.7.0 → 0.7.1
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 +4 -4
- data/Gemfile.lock +4 -4
- data/README.md +40 -88
- data/docs/alternatives.md +1 -7
- data/docs/cassettes.md +54 -59
- data/docs/editors.md +32 -47
- data/docs/errors.md +261 -369
- data/docs/federation.md +650 -837
- data/docs/generated_modules.md +370 -459
- data/docs/getting_started.md +211 -428
- data/docs/i18n.md +114 -177
- data/docs/logging.md +127 -116
- data/docs/real_world.md +26 -39
- data/docs/scalars.md +277 -310
- data/docs/testing.md +340 -486
- data/docs/transports.md +191 -263
- data/docs/upgrading.md +188 -560
- data/examples/README.md +38 -0
- data/examples/countries.rb +39 -0
- data/examples/federation.rb +62 -0
- data/examples/github/generate.rb +20 -0
- data/examples/github/generated/star_mutation.rb +126 -0
- data/examples/github/generated/stargazers_query.rb +232 -0
- data/examples/github/generated/starred_query.rb +151 -0
- data/examples/github/queries/star.graphql +8 -0
- data/examples/github/queries/stargazers.graphql +22 -0
- data/examples/github/queries/starred.graphql +11 -0
- data/examples/github/run.rb +43 -0
- data/examples/github/setup.rb +18 -0
- data/examples/rick_and_morty.rb +57 -0
- data/graph_weaver.gemspec +12 -3
- data/lib/graph_weaver/client.rb +22 -1
- data/lib/graph_weaver/codegen.rb +5 -1
- data/lib/graph_weaver/context_seam.rb +54 -0
- data/lib/graph_weaver/errors.rb +23 -15
- data/lib/graph_weaver/federation.rb +11 -2
- data/lib/graph_weaver/in_process.rb +15 -9
- data/lib/graph_weaver/internal/endpoint.rb +7 -5
- data/lib/graph_weaver/internal/headers.rb +19 -0
- data/lib/graph_weaver/internal.rb +66 -13
- data/lib/graph_weaver/log_subscriber.rb +10 -2
- data/lib/graph_weaver/logging.rb +33 -13
- data/lib/graph_weaver/query_module.rb +8 -0
- data/lib/graph_weaver/retry.rb +12 -8
- data/lib/graph_weaver/schema_loader.rb +52 -14
- data/lib/graph_weaver/testing/cassette.rb +28 -5
- data/lib/graph_weaver/testing/endpoint.rb +14 -13
- data/lib/graph_weaver/testing/fake_client.rb +33 -3
- data/lib/graph_weaver/testing/router.rb +7 -3
- data/lib/graph_weaver/transport/http.rb +2 -2
- data/lib/graph_weaver/transport.rb +47 -23
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +22 -1
- metadata +16 -3
- data/CHANGELOG.md +0 -3801
|
@@ -96,7 +96,8 @@ class GraphWeaver::Transport
|
|
|
96
96
|
# a raw query string falls back to the name in the document itself.
|
|
97
97
|
def execute(query, variables: {}, operation_name: nil)
|
|
98
98
|
operation_name ||= GraphWeaver::Internal::Wire.operation_name(query)
|
|
99
|
-
payload = { url: safe_url, operation: operation_name, client: self.class
|
|
99
|
+
payload = { url: safe_url, operation: operation_name, client: self.class,
|
|
100
|
+
kind: GraphWeaver::Internal::Wire.kind(query) }
|
|
100
101
|
|
|
101
102
|
GraphWeaver::Internal::Log.instrument(GraphWeaver::EXECUTE_EVENT, payload) do
|
|
102
103
|
perform(query, variables, operation_name, payload)
|
|
@@ -132,12 +133,24 @@ class GraphWeaver::Transport
|
|
|
132
133
|
post(encoded)
|
|
133
134
|
end
|
|
134
135
|
rescue *GraphWeaver.transport_errors.to_a => e
|
|
135
|
-
# never got a response — DNS, connection refused/reset, TLS, timeout
|
|
136
|
-
|
|
136
|
+
# never got a response — DNS, connection refused/reset, TLS, timeout.
|
|
137
|
+
# The adapter's sentence is its own words, capped like any text we
|
|
138
|
+
# didn't author.
|
|
139
|
+
raise GraphWeaver::TransportError.new(
|
|
140
|
+
"#{e.class}: #{GraphWeaver::Internal::Redact.cap(e.message)}", url: safe_url,
|
|
141
|
+
)
|
|
137
142
|
end
|
|
138
143
|
|
|
139
144
|
payload[:http_status] = status
|
|
140
|
-
|
|
145
|
+
# folded once: a third-party subclass's headers may come back in any
|
|
146
|
+
# casing, and a subclass that returns none says nothing
|
|
147
|
+
fields = GraphWeaver::Internal::Headers.wrap(headers || {})
|
|
148
|
+
# the content type, not the body: it is what tells a proxy's HTML page
|
|
149
|
+
# from a router's JSON without quoting bytes a server chose
|
|
150
|
+
GraphWeaver::Internal::Log.log(:debug) do
|
|
151
|
+
type = GraphWeaver::Internal::Redact.tag(fields["content-type"])
|
|
152
|
+
"HTTP #{status} #{tag} from #{safe_url} (#{body.to_s.bytesize} bytes#{", #{type}" if type})"
|
|
153
|
+
end
|
|
141
154
|
|
|
142
155
|
parsed = parse_body(body)
|
|
143
156
|
|
|
@@ -151,42 +164,54 @@ class GraphWeaver::Transport
|
|
|
151
164
|
# `"errors": null` (or []) isn't a structured error response, so the
|
|
152
165
|
# status stays the signal
|
|
153
166
|
if parsed.is_a?(Hash) && parsed["errors"].is_a?(Array) && parsed["errors"].any?
|
|
154
|
-
return Envelope.new(parsed, status)
|
|
167
|
+
return Envelope.new(parsed, status, fields.retry_after)
|
|
155
168
|
end
|
|
156
169
|
|
|
157
|
-
|
|
170
|
+
refuse!(status, body, headers)
|
|
158
171
|
end
|
|
159
172
|
|
|
160
173
|
unless parsed.is_a?(Hash)
|
|
161
174
|
# a 200 that isn't a GraphQL object — an HTML error page from a proxy, a
|
|
162
175
|
# captive portal, or a bare JSON array/string: the server misbehaved.
|
|
163
|
-
#
|
|
164
|
-
# well-formed @defer stream is named rather than dumped: it isn't
|
|
176
|
+
# A well-formed @defer stream is named rather than lumped in: it isn't
|
|
165
177
|
# non-GraphQL, it's more than one GraphQL document.
|
|
166
|
-
|
|
167
|
-
if incremental?(
|
|
178
|
+
detail =
|
|
179
|
+
if incremental?(fields)
|
|
168
180
|
"this response is incremental delivery (@defer/@stream), which this client doesn't read"
|
|
169
181
|
elsif body.to_s.empty?
|
|
170
182
|
"empty response body"
|
|
171
183
|
else
|
|
172
|
-
"non-GraphQL response
|
|
184
|
+
"non-GraphQL response"
|
|
173
185
|
end
|
|
174
|
-
|
|
186
|
+
refuse!(status, body, headers, detail:)
|
|
175
187
|
end
|
|
176
188
|
|
|
177
|
-
Envelope.new(parsed, status)
|
|
189
|
+
Envelope.new(parsed, status, fields.retry_after)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# The response wasn't one we can read. A body is never quoted — not into the
|
|
193
|
+
# message, not into a log line: it is text a server chose, and an error page
|
|
194
|
+
# that echoes the request fills it with the variables and the Authorization
|
|
195
|
+
# header we just sent. `detail` is what WE say went wrong; the bytes are on
|
|
196
|
+
# ServerError#body for whoever rescues it.
|
|
197
|
+
private def refuse!(status, body, headers, detail: nil)
|
|
198
|
+
raise GraphWeaver::ServerError.new(status:, body: body.to_s, headers: headers || {}, url: safe_url, detail:)
|
|
178
199
|
end
|
|
179
200
|
|
|
180
|
-
# The parsed envelope, plus the HTTP
|
|
181
|
-
# everything that reads a GraphQL response, and to the one caller
|
|
182
|
-
# needs
|
|
183
|
-
# an errors body, so the body alone can't say whether to come
|
|
201
|
+
# The parsed envelope, plus what the HTTP response said around it — a Hash
|
|
202
|
+
# to everything that reads a GraphQL response, and more to the one caller
|
|
203
|
+
# that needs it. Retry asks both: a router answers rate limiting with a 503
|
|
204
|
+
# or 429 AND an errors body, so the body alone can't say whether to come
|
|
205
|
+
# back, and Retry-After says when. The seconds, not the headers — that is
|
|
206
|
+
# the whole of what Retry asks, and every other header stays where a
|
|
207
|
+
# ServerError already carries it.
|
|
184
208
|
class Envelope < Hash
|
|
185
|
-
attr_reader :http_status
|
|
209
|
+
attr_reader :http_status, :retry_after
|
|
186
210
|
|
|
187
|
-
def initialize(parsed, http_status)
|
|
211
|
+
def initialize(parsed, http_status, retry_after = nil)
|
|
188
212
|
super()
|
|
189
213
|
@http_status = http_status
|
|
214
|
+
@retry_after = retry_after
|
|
190
215
|
update(parsed)
|
|
191
216
|
end
|
|
192
217
|
end
|
|
@@ -201,10 +226,9 @@ class GraphWeaver::Transport
|
|
|
201
226
|
private_constant :BOM
|
|
202
227
|
|
|
203
228
|
# A multipart/mixed body is one @defer/@stream response arriving in
|
|
204
|
-
# installments.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
GraphWeaver::Internal::Headers.wrap(headers || {})["content-type"].to_s.start_with?("multipart/mixed")
|
|
229
|
+
# installments.
|
|
230
|
+
private def incremental?(fields)
|
|
231
|
+
fields["content-type"].to_s.start_with?("multipart/mixed")
|
|
208
232
|
end
|
|
209
233
|
|
|
210
234
|
# the parsed body, or nil when it isn't JSON (a caller's connection may
|
data/lib/graph_weaver/version.rb
CHANGED
data/lib/graph_weaver.rb
CHANGED
|
@@ -849,7 +849,9 @@ module GraphWeaver
|
|
|
849
849
|
used_unions = []
|
|
850
850
|
shared = Codegen.load_fragments(fragments)
|
|
851
851
|
|
|
852
|
-
|
|
852
|
+
refusals = []
|
|
853
|
+
paths = Internal::Util.query_files(graph.queries)
|
|
854
|
+
plan = paths.filter_map do |path|
|
|
853
855
|
source = File.read(path)
|
|
854
856
|
name, filename = graph.generated_names(path, source)
|
|
855
857
|
refuse_duplicate!(seen, name, filename, graph, path)
|
|
@@ -870,7 +872,13 @@ module GraphWeaver
|
|
|
870
872
|
found.concat(codegen.untyped_scalars).uniq!
|
|
871
873
|
used_unions |= codegen.used_union_names
|
|
872
874
|
[filename, out]
|
|
875
|
+
rescue GraphWeaver::Error => e
|
|
876
|
+
# collected, not raised: nothing is written either way, and an adopter
|
|
877
|
+
# aiming generate! at an existing query directory wants the list
|
|
878
|
+
refusals << e
|
|
879
|
+
nil
|
|
873
880
|
end
|
|
881
|
+
refuse_all!(refusals, paths.size)
|
|
874
882
|
|
|
875
883
|
if used_unions.any? || used.values.any?(&:any?)
|
|
876
884
|
refuse_duplicate_types!(seen, graph)
|
|
@@ -890,6 +898,19 @@ module GraphWeaver
|
|
|
890
898
|
end
|
|
891
899
|
private :generation_plan
|
|
892
900
|
|
|
901
|
+
# Every query that refused, in one error. One refusal is re-raised as
|
|
902
|
+
# itself, so a single bad file reads exactly as it always has — class,
|
|
903
|
+
# message and all; several become one list, because clearing them a file
|
|
904
|
+
# per run is the slowest way there is to adopt this.
|
|
905
|
+
def refuse_all!(refusals, considered)
|
|
906
|
+
return if refusals.empty?
|
|
907
|
+
raise refusals.first if refusals.one?
|
|
908
|
+
|
|
909
|
+
raise GraphWeaver::Error, "#{refusals.size} of #{considered} queries refused:\n" +
|
|
910
|
+
refusals.map { |refusal| refusal.message.gsub(/^/, " ") }.join("\n")
|
|
911
|
+
end
|
|
912
|
+
private :refuse_all!
|
|
913
|
+
|
|
893
914
|
# Two query files landing on one constant, or on one output file. Within a
|
|
894
915
|
# graph the fix is a rename, as it has always been; across two graphs it is
|
|
895
916
|
# `namespace:` for the constant and `output:` for the file — so the message
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graph_weaver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Pepper
|
|
@@ -268,7 +268,6 @@ extensions: []
|
|
|
268
268
|
extra_rdoc_files: []
|
|
269
269
|
files:
|
|
270
270
|
- ".yardopts"
|
|
271
|
-
- CHANGELOG.md
|
|
272
271
|
- Gemfile
|
|
273
272
|
- Gemfile.lock
|
|
274
273
|
- LICENSE.txt
|
|
@@ -287,6 +286,19 @@ files:
|
|
|
287
286
|
- docs/testing.md
|
|
288
287
|
- docs/transports.md
|
|
289
288
|
- docs/upgrading.md
|
|
289
|
+
- examples/README.md
|
|
290
|
+
- examples/countries.rb
|
|
291
|
+
- examples/federation.rb
|
|
292
|
+
- examples/github/generate.rb
|
|
293
|
+
- examples/github/generated/star_mutation.rb
|
|
294
|
+
- examples/github/generated/stargazers_query.rb
|
|
295
|
+
- examples/github/generated/starred_query.rb
|
|
296
|
+
- examples/github/queries/star.graphql
|
|
297
|
+
- examples/github/queries/stargazers.graphql
|
|
298
|
+
- examples/github/queries/starred.graphql
|
|
299
|
+
- examples/github/run.rb
|
|
300
|
+
- examples/github/setup.rb
|
|
301
|
+
- examples/rick_and_morty.rb
|
|
290
302
|
- graph_weaver.gemspec
|
|
291
303
|
- lib/generators/graph_weaver/install_generator.rb
|
|
292
304
|
- lib/graph_weaver.rb
|
|
@@ -300,6 +312,7 @@ files:
|
|
|
300
312
|
- lib/graph_weaver/codegen/scalar_type.rb
|
|
301
313
|
- lib/graph_weaver/codegen/type_helpers.rb
|
|
302
314
|
- lib/graph_weaver/coerce.rb
|
|
315
|
+
- lib/graph_weaver/context_seam.rb
|
|
303
316
|
- lib/graph_weaver/errors.rb
|
|
304
317
|
- lib/graph_weaver/federation.rb
|
|
305
318
|
- lib/graph_weaver/graph.rb
|
|
@@ -350,7 +363,7 @@ licenses:
|
|
|
350
363
|
- MIT
|
|
351
364
|
metadata:
|
|
352
365
|
bug_tracker_uri: https://github.com/dpep/graph_weaver/issues
|
|
353
|
-
changelog_uri: https://github.com/dpep/graph_weaver/blob/
|
|
366
|
+
changelog_uri: https://github.com/dpep/graph_weaver/blob/v0.7.1/CHANGELOG.md
|
|
354
367
|
documentation_uri: https://github.com/dpep/graph_weaver/tree/main/docs
|
|
355
368
|
rubygems_mfa_required: 'true'
|
|
356
369
|
source_code_uri: https://github.com/dpep/graph_weaver
|