graph_weaver 0.6.1 → 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 +8 -0
- data/Gemfile.lock +153 -4
- data/README.md +45 -79
- data/docs/alternatives.md +195 -0
- data/docs/cassettes.md +61 -50
- data/docs/editors.md +32 -47
- data/docs/errors.md +360 -103
- data/docs/federation.md +692 -473
- data/docs/generated_modules.md +441 -314
- data/docs/getting_started.md +370 -194
- data/docs/i18n.md +171 -0
- data/docs/logging.md +197 -50
- data/docs/real_world.md +42 -27
- data/docs/scalars.md +307 -176
- data/docs/testing.md +473 -220
- data/docs/transports.md +224 -151
- data/docs/upgrading.md +258 -305
- 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 +19 -3
- data/lib/generators/graph_weaver/install_generator.rb +138 -4
- data/lib/graph_weaver/client.rb +69 -11
- data/lib/graph_weaver/codegen/aliases.rb +7 -5
- data/lib/graph_weaver/codegen/emit.rb +98 -29
- data/lib/graph_weaver/codegen/enum_type.rb +2 -1
- data/lib/graph_weaver/codegen/nodes.rb +39 -6
- data/lib/graph_weaver/codegen/registry.rb +175 -0
- data/lib/graph_weaver/codegen/scalar_type.rb +123 -30
- data/lib/graph_weaver/codegen/type_helpers.rb +56 -11
- data/lib/graph_weaver/codegen.rb +406 -195
- data/lib/graph_weaver/coerce.rb +155 -26
- data/lib/graph_weaver/context_seam.rb +54 -0
- data/lib/graph_weaver/errors.rb +284 -46
- data/lib/graph_weaver/federation.rb +129 -27
- data/lib/graph_weaver/graph.rb +315 -0
- data/lib/graph_weaver/hints.rb +100 -24
- data/lib/graph_weaver/in_process.rb +27 -15
- data/lib/graph_weaver/input_struct.rb +119 -32
- data/lib/graph_weaver/internal/endpoint.rb +80 -0
- data/lib/graph_weaver/internal/headers.rb +70 -0
- data/lib/graph_weaver/internal/overrides.rb +67 -5
- data/lib/graph_weaver/internal/planner.rb +45 -15
- data/lib/graph_weaver/internal/refusal.rb +49 -0
- data/lib/graph_weaver/internal/schemas.rb +23 -9
- data/lib/graph_weaver/internal/selection.rb +34 -0
- data/lib/graph_weaver/internal/server_input.rb +251 -0
- data/lib/graph_weaver/internal/test_clients.rb +276 -0
- data/lib/graph_weaver/internal/unused.rb +287 -0
- data/lib/graph_weaver/internal/values.rb +40 -4
- data/lib/graph_weaver/internal.rb +249 -14
- data/lib/graph_weaver/log_subscriber.rb +74 -0
- data/lib/graph_weaver/logging.rb +163 -19
- data/lib/graph_weaver/query_module.rb +44 -3
- data/lib/graph_weaver/railtie.rb +237 -17
- data/lib/graph_weaver/representation.rb +55 -17
- data/lib/graph_weaver/result_struct.rb +90 -0
- data/lib/graph_weaver/retry.rb +45 -13
- data/lib/graph_weaver/rspec.rb +404 -93
- data/lib/graph_weaver/schema_loader.rb +266 -56
- data/lib/graph_weaver/tasks.rb +380 -89
- data/lib/graph_weaver/testing/cassette.rb +34 -10
- data/lib/graph_weaver/testing/endpoint.rb +107 -0
- data/lib/graph_weaver/testing/failure.rb +69 -12
- data/lib/graph_weaver/testing/fake_client.rb +164 -45
- data/lib/graph_weaver/testing/router.rb +64 -13
- data/lib/graph_weaver/testing.rb +200 -58
- data/lib/graph_weaver/transport/faraday.rb +41 -8
- data/lib/graph_weaver/transport/http.rb +48 -6
- data/lib/graph_weaver/transport.rb +134 -27
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +495 -106
- metadata +71 -3
- data/CHANGELOG.md +0 -2355
data/lib/graph_weaver/retry.rb
CHANGED
|
@@ -38,7 +38,8 @@ require_relative "transport"
|
|
|
38
38
|
# default — pass the codes your API uses for transient failures)
|
|
39
39
|
#
|
|
40
40
|
# A server that answers with Retry-After sets the delay itself (clamped
|
|
41
|
-
# to max_delay:)
|
|
41
|
+
# to max_delay:) — whether that answer raised or came back as a response
|
|
42
|
+
# carrying GraphQL errors; otherwise the configured backoff decides.
|
|
42
43
|
#
|
|
43
44
|
# Exhausting the retries re-raises the last error (or returns the last
|
|
44
45
|
# code-matched response).
|
|
@@ -52,22 +53,31 @@ class GraphWeaver::Retry
|
|
|
52
53
|
# server asking you to come back later rather than to fix anything
|
|
53
54
|
RETRIABLE_CLIENT_STATUSES = [408, 429].freeze
|
|
54
55
|
|
|
56
|
+
# The status half of the policy, asked of a raised ServerError's status
|
|
57
|
+
# and of the status a response arrived on — one answer, so the two can't
|
|
58
|
+
# drift apart.
|
|
59
|
+
RETRIABLE_STATUS = ->(status) { status >= 500 || RETRIABLE_CLIENT_STATUSES.include?(status) }
|
|
60
|
+
|
|
55
61
|
# retry 5xx (and 408/429), not the rest of 4xx; everything else listed
|
|
56
62
|
# in retry_on: retries
|
|
57
63
|
DEFAULT_RETRY_IF = lambda do |error|
|
|
58
|
-
!error.is_a?(GraphWeaver::ServerError) ||
|
|
59
|
-
error.status >= 500 || RETRIABLE_CLIENT_STATUSES.include?(error.status)
|
|
64
|
+
!error.is_a?(GraphWeaver::ServerError) || RETRIABLE_STATUS.call(error.status)
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
# said once, where the decision is made and where it is explained
|
|
63
68
|
MUTATION_HINT = "not retrying a mutation — a request that failed without an answer " \
|
|
64
69
|
"may still have been applied; pass retry_mutations: true if yours are idempotent"
|
|
65
|
-
private_constant :DEFAULT_RETRY_IF, :MUTATION_HINT
|
|
70
|
+
private_constant :RETRIABLE_STATUS, :DEFAULT_RETRY_IF, :MUTATION_HINT
|
|
66
71
|
|
|
67
72
|
def initialize(client, retries: 2, retry_on: [GraphWeaver::TransportError, GraphWeaver::ServerError],
|
|
68
73
|
backoff: :exponential, base_delay: 0.5, max_delay: 30, jitter: true, retry_if: DEFAULT_RETRY_IF,
|
|
69
74
|
retry_codes: [], retry_mutations: false, sleeper: nil)
|
|
70
75
|
raise ArgumentError, "retries: must be >= 0" unless retries.is_a?(Integer) && retries >= 0
|
|
76
|
+
# a negative would reach Kernel#sleep, which raises — and the failure being
|
|
77
|
+
# retried would be lost behind an ArgumentError from somewhere else
|
|
78
|
+
{ base_delay:, max_delay: }.each do |name, value|
|
|
79
|
+
raise ArgumentError, "#{name}: must be >= 0, got #{value}" unless value >= 0
|
|
80
|
+
end
|
|
71
81
|
|
|
72
82
|
@client = client
|
|
73
83
|
@retries = retries
|
|
@@ -96,13 +106,19 @@ class GraphWeaver::Retry
|
|
|
96
106
|
def execute(query, variables: {}, operation_name: nil)
|
|
97
107
|
attempts = mutation?(query) ? 1 : @retries + 1
|
|
98
108
|
attempt = 0
|
|
99
|
-
failure = T.let(nil, T.nilable(Exception))
|
|
100
109
|
|
|
101
110
|
loop do
|
|
102
111
|
attempt += 1
|
|
112
|
+
failure = nil
|
|
103
113
|
begin
|
|
104
|
-
|
|
114
|
+
# each attempt is its own EXECUTE_EVENT; :retries says which one,
|
|
115
|
+
# so "slow" and "slow after two 502s" don't read the same in an APM
|
|
116
|
+
response = GraphWeaver::Internal::Log.with_retries(attempt - 1) do
|
|
117
|
+
@client.execute(query, variables:, operation_name:)
|
|
118
|
+
end
|
|
105
119
|
return response unless attempt < attempts && retryable_response?(response)
|
|
120
|
+
|
|
121
|
+
failure = response
|
|
106
122
|
rescue *@retry_on => e
|
|
107
123
|
if attempt >= attempts || !@retry_if.call(e)
|
|
108
124
|
GraphWeaver::Internal::Log.log(:warn) { MUTATION_HINT } if attempts == 1 && @retries.positive?
|
|
@@ -119,7 +135,6 @@ class GraphWeaver::Retry
|
|
|
119
135
|
"retrying #{operation_name || "query"} in #{seconds.round(2)}s (attempt #{attempt + 1} of #{attempts})"
|
|
120
136
|
end
|
|
121
137
|
@sleeper.call(seconds)
|
|
122
|
-
failure = nil
|
|
123
138
|
end
|
|
124
139
|
end
|
|
125
140
|
|
|
@@ -130,6 +145,19 @@ class GraphWeaver::Retry
|
|
|
130
145
|
end
|
|
131
146
|
|
|
132
147
|
def retryable_response?(response)
|
|
148
|
+
retryable_status?(response) || retryable_code?(response)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# The status a response arrived on, where it came back with one — the
|
|
152
|
+
# bundled transports say; a schema class, a fake and a hand-rolled client
|
|
153
|
+
# answer a plain Hash, and a response with no status is never retried on
|
|
154
|
+
# one.
|
|
155
|
+
def retryable_status?(response)
|
|
156
|
+
status = response.http_status if response.respond_to?(:http_status)
|
|
157
|
+
!status.nil? && RETRIABLE_STATUS.call(status)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def retryable_code?(response)
|
|
133
161
|
return false if @retry_codes.empty?
|
|
134
162
|
|
|
135
163
|
codes = (response.to_h["errors"] || []).filter_map { |error| error.dig("extensions", "code") }
|
|
@@ -137,14 +165,18 @@ class GraphWeaver::Retry
|
|
|
137
165
|
end
|
|
138
166
|
|
|
139
167
|
def delay(attempt, failure)
|
|
140
|
-
# A Retry-After wins over our backoff
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
|
|
168
|
+
# A Retry-After wins over our backoff, however the failure arrived — a
|
|
169
|
+
# raised ServerError, or the envelope a rate limit that came back with an
|
|
170
|
+
# errors body makes. The server is the only party that knows when its
|
|
171
|
+
# window reopens, and it isn't guessing. Still clamped to max_delay:, so
|
|
172
|
+
# "come back in an hour" can't park a thread for an hour — and not
|
|
173
|
+
# jittered, since it's an instruction, not a guess.
|
|
174
|
+
after = failure.retry_after if failure.respond_to?(:retry_after)
|
|
145
175
|
return [after, @max_delay].min.to_f if after
|
|
146
176
|
|
|
147
|
-
|
|
177
|
+
# floored at 0: a custom backoff: is the caller's arithmetic, and a
|
|
178
|
+
# negative from it would raise out of Kernel#sleep rather than retry
|
|
179
|
+
seconds = [[@backoff.call(@base_delay, attempt), @max_delay].min.to_f, 0.0].max
|
|
148
180
|
@jitter ? seconds * (0.5 + rand * 0.5) : seconds
|
|
149
181
|
end
|
|
150
182
|
end
|