graph_weaver 0.4.6 → 0.5.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/CHANGELOG.md +1442 -0
- data/Gemfile.lock +23 -23
- data/README.md +115 -96
- data/docs/cassettes.md +93 -46
- data/docs/editors.md +82 -0
- data/docs/errors.md +34 -30
- data/docs/federation.md +521 -48
- data/docs/generated_modules.md +352 -137
- data/docs/getting_started.md +237 -67
- data/docs/logging.md +35 -6
- data/docs/real_world.md +21 -15
- data/docs/scalars.md +49 -154
- data/docs/testing.md +300 -52
- data/docs/transports.md +129 -30
- data/docs/upgrading.md +134 -0
- data/graph_weaver.gemspec +19 -3
- data/lib/generators/graph_weaver/install_generator.rb +259 -0
- data/lib/graph_weaver/client.rb +118 -111
- data/lib/graph_weaver/codegen/aliases.rb +223 -0
- data/lib/graph_weaver/codegen/emit.rb +283 -261
- data/lib/graph_weaver/codegen/enum_type.rb +25 -124
- data/lib/graph_weaver/codegen/nodes.rb +72 -13
- data/lib/graph_weaver/codegen/scalar_type.rb +69 -66
- data/lib/graph_weaver/codegen/type_helpers.rb +140 -0
- data/lib/graph_weaver/codegen.rb +672 -336
- data/lib/graph_weaver/errors.rb +154 -16
- data/lib/graph_weaver/federation.rb +259 -0
- data/lib/graph_weaver/hints.rb +9 -1
- data/lib/graph_weaver/in_process.rb +90 -0
- data/lib/graph_weaver/input_struct.rb +14 -2
- data/lib/graph_weaver/logging.rb +29 -0
- data/lib/graph_weaver/parsing.rb +59 -0
- data/lib/graph_weaver/query_module.rb +55 -0
- data/lib/graph_weaver/railtie.rb +23 -1
- data/lib/graph_weaver/representation.rb +74 -0
- data/lib/graph_weaver/response.rb +7 -0
- data/lib/graph_weaver/retry.rb +29 -8
- data/lib/graph_weaver/rspec.rb +220 -16
- data/lib/graph_weaver/schema_loader.rb +819 -60
- data/lib/graph_weaver/schemas.rb +48 -0
- data/lib/graph_weaver/selection.rb +43 -8
- data/lib/graph_weaver/tasks.rb +220 -22
- data/lib/graph_weaver/testing/cassette.rb +249 -81
- data/lib/graph_weaver/testing/coverage.rb +160 -0
- data/lib/graph_weaver/testing/failure.rb +14 -25
- data/lib/graph_weaver/testing/fake_client.rb +182 -22
- data/lib/graph_weaver/testing/fake_subgraph.rb +85 -0
- data/lib/graph_weaver/testing/router.rb +1452 -0
- data/lib/graph_weaver/testing/subgraphs.rb +134 -0
- data/lib/graph_weaver/testing.rb +209 -13
- data/lib/graph_weaver/transport/faraday.rb +28 -10
- data/lib/graph_weaver/transport/http.rb +99 -36
- data/lib/graph_weaver/transport.rb +67 -14
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +416 -172
- metadata +25 -9
- data/CLAUDE.md +0 -69
- data/Makefile +0 -23
- data/NOTES.md +0 -182
- data/PLAN.md +0 -144
data/docs/errors.md
CHANGED
|
@@ -11,6 +11,7 @@ response = PersonQuery.execute(id: "1") # => GraphWeaver::Response[Result]
|
|
|
11
11
|
response.data # T.nilable(Result) — typed, present even on partial success
|
|
12
12
|
response.errors # Array[GraphWeaver::GraphQLError]
|
|
13
13
|
response.errors? # any top-level errors?
|
|
14
|
+
response.success? # the same question the other way round
|
|
14
15
|
response.extensions # { "cost" => … } — rides on success too
|
|
15
16
|
response.data! # the Result, or raise GraphWeaver::QueryError
|
|
16
17
|
```
|
|
@@ -22,17 +23,29 @@ Every `GraphQLError` exposes `#message`, `#locations`, `#path`, `#extensions`,
|
|
|
22
23
|
and `#code` (`extensions["code"]`) — match on the **code**, not the message
|
|
23
24
|
string (`response.errors.first.code == "THROTTLED"`).
|
|
24
25
|
|
|
25
|
-
Everything GraphWeaver
|
|
26
|
-
|
|
26
|
+
Everything GraphWeaver *concludes* descends from `GraphWeaver::Error` — a
|
|
27
|
+
transport failure, a rejected query, a response that wouldn't cast, a plan the
|
|
28
|
+
[local router](federation.md) refused, a subgraph map that doesn't add up. The
|
|
29
|
+
subclass says where it failed:
|
|
27
30
|
|
|
28
31
|
| Class | When |
|
|
29
32
|
|-------|------|
|
|
30
33
|
| `TransportError` | never reached the server — DNS, connection refused, TLS, timeout |
|
|
31
|
-
| `ServerError` | reached it, non-2xx HTTP — `#status`, `#body
|
|
32
|
-
| `QueryError` | 200 body with top-level GraphQL errors — `#errors`, `#data`, `#extensions`, `#codes
|
|
34
|
+
| `ServerError` | reached it, non-2xx HTTP — `#status`, `#body`, `#headers`, `#retry_after`, `#throttled?` |
|
|
35
|
+
| `QueryError` | 200 body with top-level GraphQL errors — `#errors`, `#data`, `#extensions`, `#codes`, `#throttled?` |
|
|
33
36
|
| `TypeError` | the response wouldn't cast into the generated structs — `#struct`, `#cause` |
|
|
34
|
-
| `InputError` | the variables wouldn't build into the generated input structs — unknown/typo'd key, missing required field, out-of-range enum, wrong-typed field — `#field`, `#struct` |
|
|
37
|
+
| `InputError` | the variables wouldn't build into the generated input structs — unknown/typo'd key, missing required field, out-of-range enum, wrong-typed field, wrong number of @oneOf fields — `#field`, `#struct` |
|
|
35
38
|
| `ValidationError` | build time: the query didn't validate against the schema |
|
|
39
|
+
| `Codegen::Aliases::UnknownSegment` | build time: an [`alias:`](generated_modules.md#flat-accessors-with-alias) path names a field no type here has — a typo, so `optional: true` won't skip it |
|
|
40
|
+
| `ConfigurationError` | setup judged against your schema — which Ruby schema serves which subgraph (`Testing::Router`, `federation:diff`) |
|
|
41
|
+
| `Testing::Unplannable` | the local test router won't plan this operation — `#category`, `#detail` |
|
|
42
|
+
| `Testing::MissingRecording` | a [cassette](cassettes.md) holds no entry for this request — the message prints the variables, and the ones it did record |
|
|
43
|
+
|
|
44
|
+
An argument that is wrong *on its face* raises a plain `ArgumentError` instead
|
|
45
|
+
(`pool_size: must be >= 1`, `cast: must be a Symbol, Proc, :itself, or nil`),
|
|
46
|
+
like any Ruby method — a bug at the call site, not a condition to rescue. The
|
|
47
|
+
line is whether the library had to read your schema to reach the verdict: it
|
|
48
|
+
did for `ConfigurationError`, which is why a spec helper can rescue that one.
|
|
36
49
|
|
|
37
50
|
```ruby
|
|
38
51
|
begin
|
|
@@ -40,30 +53,22 @@ begin
|
|
|
40
53
|
rescue GraphWeaver::TransportError
|
|
41
54
|
retry # network blip
|
|
42
55
|
rescue GraphWeaver::ServerError => e
|
|
43
|
-
e.status >= 500 ? backoff : raise
|
|
56
|
+
e.throttled? || e.status >= 500 ? backoff : raise # a plain 4xx is our bug
|
|
44
57
|
rescue GraphWeaver::QueryError => e
|
|
45
|
-
e.
|
|
58
|
+
e.throttled? ? backoff : raise # the same question, asked of the errors array
|
|
46
59
|
end
|
|
47
60
|
```
|
|
48
61
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
tries: 5, # total attempts
|
|
56
|
-
backoff: :exponential, # or :linear, or ->(attempt) { seconds }
|
|
57
|
-
base: 0.5, max: 30, # seconds, clamped at max:
|
|
58
|
-
jitter: true, # randomize each delay by 50-100%
|
|
59
|
-
retry_codes: ["THROTTLED"], # also retry GraphQL errors by code
|
|
60
|
-
)
|
|
61
|
-
```
|
|
62
|
+
`#throttled?` deliberately spells the same on both: an API may say "slow
|
|
63
|
+
down" with a 429 or with a `THROTTLED` error in a 200 body, and a caller
|
|
64
|
+
shouldn't have to know which. It recognizes the codes the big graphs
|
|
65
|
+
actually send (`GraphWeaver::GraphQLError::THROTTLE_CODES` — Shopify's
|
|
66
|
+
`THROTTLED`, GitHub's `RATE_LIMITED`, and friends); pass that constant to
|
|
67
|
+
`Retry`'s `retry_codes:` instead of hand-writing the strings.
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
`retry_codes:`. Exhausting `tries:` re-raises the last error.
|
|
69
|
+
Or skip the hand-rolling: [`Retry`](transports.md#retries) wraps any client and
|
|
70
|
+
already defaults to exactly the policy above — transport failures always,
|
|
71
|
+
`ServerError` on 5xx plus 408/429, and GraphQL error codes you name.
|
|
67
72
|
|
|
68
73
|
**Top-level scalar variables** fail like any Ruby method call, *outside* the
|
|
69
74
|
hierarchy on purpose — passing the wrong Ruby type for a scalar kwarg is a
|
|
@@ -73,10 +78,9 @@ String"), a missing required one a plain `ArgumentError` ("missing keyword: :id"
|
|
|
73
78
|
|
|
74
79
|
**Input-object variables** are the caller-input case, so they're *inside* the
|
|
75
80
|
hierarchy. When you pass an input object as a hash (or struct) it's built
|
|
76
|
-
through the generated `coerce`, and
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
into a 422:
|
|
81
|
+
through the generated `coerce`, and anything wrong in there raises
|
|
82
|
+
`GraphWeaver::InputError` — one rescue point for turning invalid input into a
|
|
83
|
+
422:
|
|
80
84
|
|
|
81
85
|
```ruby
|
|
82
86
|
rescue GraphWeaver::InputError => e
|
|
@@ -94,8 +98,8 @@ Business/validation failures returned *as data* (Shopify-style `userErrors { fie
|
|
|
94
98
|
message code }`) aren't errors here — they're just fields you selected, so they
|
|
95
99
|
deserialize onto `response.data` like anything else and you inspect them there.
|
|
96
100
|
|
|
97
|
-
The one-shot `GraphWeaver.
|
|
98
|
-
the envelope, `
|
|
101
|
+
The one-shot `GraphWeaver.run` / `run!` mirror this: `run` returns
|
|
102
|
+
the envelope, `run!` the result-or-raise.
|
|
99
103
|
|
|
100
104
|
## Extending TransportError
|
|
101
105
|
|