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/docs/upgrading.md
CHANGED
|
@@ -1,333 +1,286 @@
|
|
|
1
1
|
# Upgrading
|
|
2
2
|
|
|
3
|
+
[Regenerate](#regenerate-on-every-upgrade) whichever version you're on, then read
|
|
4
|
+
the one section that is yours: from [0.7.0](#upgrading-from-070) or from
|
|
5
|
+
[0.6.1](#upgrading-from-061). Coming from 0.6.0 or older, the path is that
|
|
6
|
+
version's own upgrade notes — read them at the tag they shipped under
|
|
7
|
+
(`git show v0.7.1:docs/upgrading.md`), then this page from 0.6.1 down.
|
|
8
|
+
|
|
3
9
|
## Regenerate on every upgrade
|
|
4
10
|
|
|
5
|
-
**Any release can change what codegen emits
|
|
11
|
+
**Any release can change what codegen emits**, patch releases included — most of
|
|
6
12
|
them are fixes to a generated type, and a fix to a type is a change to the bytes.
|
|
7
|
-
0.5.1 was a patch and moved three of them.
|
|
8
|
-
|
|
9
13
|
So `rake graph_weaver:generate` is part of upgrading the gem, every time, and
|
|
10
14
|
`rake graph_weaver:verify` is the detector: it fails when the checked-in Ruby
|
|
11
|
-
isn't what this version would write. Nothing beyond that is promised — there is
|
|
12
|
-
|
|
13
|
-
changed
|
|
15
|
+
isn't what this version would write. Nothing beyond that is promised — there is no
|
|
16
|
+
"generated output is stable within a minor" rule to lean on, and what each release
|
|
17
|
+
changed is in the [changelog](../CHANGELOG.md).
|
|
14
18
|
|
|
15
19
|
A generated file's header names the release that wrote it, so the first `verify`
|
|
16
|
-
after an upgrade reports the tree as stale whether or not codegen actually
|
|
17
|
-
|
|
20
|
+
after an upgrade reports the tree as stale whether or not codegen actually moved.
|
|
21
|
+
That's the reminder working, not a false alarm. Generation is deterministic, so
|
|
22
|
+
the diff is exactly what the new version emits differently and nothing else —
|
|
23
|
+
worth reading rather than rubber-stamping.
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
differently and nothing else — worth reading rather than rubber-stamping.
|
|
25
|
+
## Upgrading from 0.7.0
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
A patch release of fixes, and a typical app ticks none of these. Read the left
|
|
28
|
+
column and skip what isn't yours; the [changelog](../CHANGELOG.md) says why each
|
|
29
|
+
one moved.
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
| applies if you… | what changed |
|
|
32
|
+
|---|---|
|
|
33
|
+
| read `payload[:code]` in an alert or a dashboard expecting an HTTP status | it is a GraphQL error code or nothing now — the number is on `:http_status`, where it already was |
|
|
34
|
+
| assert a fabricated `userErrors` is non-empty under `graphql: :fake` | a list field whose name ends in `errors` fabricates `[]` — pin it (`{ "userErrors" => [{ "message" => "…" }] }`) to fabricate failures |
|
|
35
|
+
| grep or parse the debug log for `[req 3 …]` | the tag names the process: `[req 4123-3 …]` |
|
|
36
|
+
| run more than one graph with `cache: true` | the second graph caches to `schema-<url digest>.json` of its own instead of sharing the first's dump — `cache: "<path>"` names it yourself |
|
|
37
|
+
| commit your cassette directory | recording takes a `<cassette>.yml.lock` sidecar — gitignore `*.yml.lock` |
|
|
38
|
+
| `rescue ArgumentError` around the union-dispatch refusal | it is a `GraphWeaver::Error` naming the query file now |
|
|
39
|
+
| wrote a client of your own with a bare `attr_accessor :context` and mount it behind `Testing::Endpoint` | include `GraphWeaver::ContextSeam` — the lock lives on whoever owns the field |
|
|
40
|
+
| read `CHANGELOG.md` out of the installed gem | it isn't packaged any more; `changelog_uri` points at `blob/v<version>` |
|
|
41
|
+
| send a `File`, `IO`, `Pathname` or plain object as a variable under `graphql: :in_process` or `:fake` | refused there too now, as it already was over the wire — **a test that "proved" an upload works starts failing** |
|
|
42
|
+
| retry a 429/503 that arrives **with** a GraphQL errors body | a `Retry-After` header wins over the configured backoff now, as it already did for a raised `ServerError` — **nothing raises** |
|
|
43
|
+
|
|
44
|
+
## Upgrading from 0.6.1
|
|
45
|
+
|
|
46
|
+
Mostly mechanical. Everything that wants your hands, or changes under you, is one
|
|
47
|
+
row below; read the left column and skip what isn't yours. A typical app ticks two
|
|
48
|
+
or three.
|
|
49
|
+
|
|
50
|
+
| applies if you… | what changed |
|
|
51
|
+
|---|---|
|
|
52
|
+
| run a Rails app that configures no logger or instrumenter | **you start logging one info line per GraphQL call** — a production log-volume change, [first bullet below](#behavior-that-changed-under-you) |
|
|
53
|
+
| wrap a gateway in `Retry` | **a 5xx/429 that arrives with an errors body retries now** — real traffic, [below](#behavior-that-changed-under-you); `retries: 0` opts out |
|
|
54
|
+
| commit a schema dump introspected through a url carrying a token | **refresh it, and rotate the token if that file was pushed** — the dump recorded the url verbatim |
|
|
55
|
+
| keep a schema dump deliberately behind your own schema class | `verify` fails on it now |
|
|
56
|
+
| check in a composed supergraph as your dump | `schema:refresh` refuses it rather than overwriting it with the API schema — recompose instead |
|
|
57
|
+
| use `@oneOf` input types and commit a `.json` dump | `@oneOf` starts being enforced client-side once you regenerate |
|
|
58
|
+
| call `result.to_json`, or `render json: result` | it is the wire shape now, not `#inspect` or your prop names |
|
|
59
|
+
| send a `File`, `IO` or `Pathname` as a variable | refused at the wire, where `JSON.generate` used to ship its `#to_s` |
|
|
60
|
+
| pin a lowercase type name in `graphql_fake` | it works now; a near-miss *keyword* raises `ArgumentError` |
|
|
61
|
+
| tag specs `graphql: false`, or set `config.default_mode = nil` | both refused — [renames](#renames) |
|
|
62
|
+
| `rescue GraphWeaver::TypeError` or `GraphWeaver::ValidationError` | both constants are gone, with no alias — [renames](#renames) |
|
|
63
|
+
| subscribe to `"graph_weaver.execute"` | the event is `"execute.graph_weaver"` — [renames](#renames) |
|
|
64
|
+
| index a hash by an `InputError`'s `#field` | it names the input field now, not the variable — **nothing raises** |
|
|
65
|
+
| read an `InputError`'s `#details[:type]` | it is the GraphQL type now, never a Ruby class — **nothing raises** |
|
|
66
|
+
| read `payload[:status]` in an instrumentation subscriber | it is a Symbol; the HTTP status moved to `:http_status` — **nothing raises** |
|
|
67
|
+
| call `respond_to?` on a result struct | it stopped answering true for props that don't exist — **nothing raises** |
|
|
68
|
+
| generate a module with a baked `client:` | a `graphql:` tag now reaches it |
|
|
69
|
+
| set `config.context`, `config.schema` or `config.router` from a `before` hook | all three refused — they are suite setup |
|
|
70
|
+
| pass a `DateTime` where the schema says `Date` | refused — pass `.to_date` |
|
|
71
|
+
| register a scalar with your own `cast:`/`serialize:` | the same guard as the built-ins, and a proc that returns a value is refused |
|
|
72
|
+
| have a field named `class`, `hash`, `display`, `to_json`, `each` or `supplied` | the prop takes a trailing underscore |
|
|
73
|
+
| have an entity `@key` that selects through a list | the kwarg is a list now, not one hash — regenerate |
|
|
74
|
+
| build a type helper with `extend_type("Widget") { … }` | its constant is named for its graph and its type — regenerate |
|
|
75
|
+
| adopt `GraphWeaver.graph` | every queries directory then needs one |
|
|
76
|
+
| write `config.graph_weaver.<anything but watch>` | refused at boot |
|
|
77
|
+
| pass `SUPERGRAPH=` to any task but `federation:*` | refused, where it was ignored — a CI step that did it goes red |
|
|
78
|
+
| compare a `Testing::Router` error hash whole in a spec | a subgraph error carries `extensions: {"service" => …}` now |
|
|
79
|
+
| pass `seed:` to `graphql_router(fake: …)` | refused |
|
|
80
|
+
| require `graph_weaver/rspec` from `spec/support/` | check the glob is uncommented — rspec-rails ships it commented out |
|
|
81
|
+
| adopt `graphql: :wire` | it needs `require "webmock/rspec"`, not just the gem |
|
|
82
|
+
|
|
83
|
+
Then five commands, in order:
|
|
25
84
|
|
|
26
85
|
```sh
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```
|
|
86
|
+
# 1. the two renames your own code holds
|
|
87
|
+
grep -rn "GraphWeaver::TypeError\|GraphWeaver::ValidationError" app lib spec
|
|
88
|
+
grep -rn "graph_weaver.execute" app lib config spec # the old event name
|
|
31
89
|
|
|
32
|
-
|
|
90
|
+
# 2. rewrite the dump: it drops a credential the url carried, and picks up
|
|
91
|
+
# isOneOf. Skip only if your dump is SDL and records no source url — and
|
|
92
|
+
# a composed supergraph refuses, since introspection can't rebuild one:
|
|
93
|
+
# `rover supergraph compose` is what rewrites that.
|
|
94
|
+
rake graph_weaver:schema:refresh
|
|
33
95
|
|
|
34
|
-
|
|
96
|
+
# 3. regenerate — also the graph name in every module, the underscored
|
|
97
|
+
# reserved props, as_json, and the client: and cast:/serialize: refusals
|
|
98
|
+
rake graph_weaver:generate
|
|
35
99
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
buying that was `GraphWeaver.auto_coerce` or `register_scalar(…, coerce: true)`,
|
|
39
|
-
and both paid for it by **widening the emitted kwarg**, which switched off the
|
|
40
|
-
static check at every call site. Delete them:
|
|
100
|
+
# 4. the renamed tag, the deleted nil, the seed: refusal
|
|
101
|
+
bundle exec rspec
|
|
41
102
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
GraphWeaver.register_scalar("Money", Money, coerce: true) # drop the coerce:
|
|
103
|
+
# 5. the gate: red while any checked-in file is still what 0.6.1 wrote
|
|
104
|
+
rake graph_weaver:verify
|
|
45
105
|
```
|
|
46
106
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```ruby
|
|
53
|
-
StargazersQuery.execute(first: "10") # srb tc error now
|
|
54
|
-
StargazersQuery.execute(first: params[:first]) # fine, and "10" becomes 10
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
`cast:` is what a loose value converts through, so a custom scalar needs nothing
|
|
58
|
-
beyond the registration it already has. Bad input raises
|
|
59
|
-
`GraphWeaver::InputError` naming the variable, the operation and the value.
|
|
60
|
-
|
|
61
|
-
Two conversions got **stricter** at the same time, and either can bite an app
|
|
62
|
-
that was passing. A numeric string is now read as a wire format rather than as
|
|
63
|
-
Ruby source, so `"010"` is ten rather than eight and `"0x1f"` and `"1_0"` are
|
|
64
|
-
refused. And a `Boolean` refuses a String outright — every rule for `"0"` and
|
|
65
|
-
`"off"` is somebody's convention, so convert at the call site.
|
|
66
|
-
|
|
67
|
-
### `nil` sends `null`
|
|
68
|
-
|
|
69
|
-
A variable passed `nil` now sends an explicit `null`; one left out is still left
|
|
70
|
-
out. That's what lets a mutation clear a field — and it changes what a kwarg fed
|
|
71
|
-
a possibly-missing value means:
|
|
72
|
-
|
|
73
|
-
```ruby
|
|
74
|
-
UpdateProfile.execute!(bio: params[:bio]) # a missing param used to omit; now it clears the bio
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
**Grep for kwargs fed straight from `params` or an optional attribute**, and
|
|
78
|
-
pass the keyword only when you mean it:
|
|
79
|
-
|
|
80
|
-
```ruby
|
|
81
|
-
UpdateProfile.execute!(**(params[:bio] ? { bio: params[:bio] } : {}))
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Non-null variables are unaffected: they can't carry `null`, so `nil` there still
|
|
85
|
-
omits and the schema default applies. Input objects get the distinction only
|
|
86
|
-
where a Hash can express it — `coerce({nickname: nil})` sends null, `coerce({})`
|
|
87
|
-
omits, and a struct built with `.new` can't tell the two apart, so `nil` there
|
|
88
|
-
still means omit.
|
|
107
|
+
**Two kinds of file answer that first grep, and only one needs your hands.** Hits
|
|
108
|
+
under your generated directory (`app/graphql/generated/` by default) are the old
|
|
109
|
+
names in machine-written code — step 3 rewrites them. Hits anywhere else are
|
|
110
|
+
yours: `CastError` and `QueryValidationError`, renamed by hand.
|
|
89
111
|
|
|
90
112
|
### Renames
|
|
91
113
|
|
|
92
114
|
| before | after |
|
|
93
115
|
|---|---|
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `Testing.config.null_chance = 0.3` | `graphql_fake(null_chance: 0.3)`, on the example that wants it |
|
|
100
|
-
| `Testing.config.mode = :literal` | `graphql_fake(values: :literal)`, likewise |
|
|
101
|
-
| `Testing::MODES` | `Testing::VALUE_STYLES` |
|
|
102
|
-
| `SchemaLoader.stale?(path)` | `SchemaLoader.diff(path).empty?` — and `diff` also names what moved |
|
|
103
|
-
|
|
104
|
-
The two `Testing.config` deletions are the ones worth a sentence. A suite-wide
|
|
105
|
-
`null_chance` answers a per-example question, so it nils an unrelated field one
|
|
106
|
-
run in ten, on a seed the failure doesn't name; move it onto the examples that
|
|
107
|
-
are *about* an empty state. (`config.default_mode` and the `graphql: :fake` tag
|
|
108
|
-
are untouched — the per-fake `mode:` became `values:` so the two can't be
|
|
109
|
-
confused for each other.) Every retry misspelling raises rather than being
|
|
110
|
-
ignored: the Hash form names its flat replacement, and a retry option passed
|
|
111
|
-
without `retries:` says so.
|
|
112
|
-
|
|
113
|
-
### The internals moved behind `Internal`
|
|
114
|
-
|
|
115
|
-
The public surface is now what the docs name, what generated code calls, and the
|
|
116
|
-
`execute` slot; everything else sits under `GraphWeaver::Internal` or went
|
|
117
|
-
`private`, and a spec diffs the two so the next accidental promotion fails CI.
|
|
118
|
-
Nothing documented moved — skip this section unless `srb tc` or a
|
|
119
|
-
`NoMethodError` says otherwise.
|
|
120
|
-
|
|
121
|
-
What a suite might plausibly have reached for: the federation query planner and
|
|
122
|
-
its IR (`Internal::Planner`), the fake-value engine (`Internal::Values`), the
|
|
123
|
-
selection walk (`Internal::Selection` — so `FakeClient` no longer answers
|
|
124
|
-
`each_field` or `gather`), the cassette matching rules (`Internal::RequestKey`),
|
|
125
|
-
subgraph detection (was `Testing::Subgraphs`), `GraphWeaver.log` /
|
|
126
|
-
`.instrument` / `.filter_variables` (`Internal::Log` — `logger=`,
|
|
127
|
-
`instrumenter=` and `filter_parameters=` are unchanged), and
|
|
128
|
-
`Transport.operation_name` / `.mutation?` / `.log_tag`, which left the class you
|
|
129
|
-
subclass for `Internal::Wire`.
|
|
130
|
-
|
|
131
|
-
Two smaller edges. `SchemaDiff::Change`, `Cassette::Check`, `Coverage::Result`
|
|
132
|
-
and `InputStruct::Field` are `Data` now rather than `Struct`, so they hand out
|
|
133
|
-
no writers — read one, build a new one to change a field. And generated modules
|
|
134
|
-
keep their own plumbing to themselves: `DEFAULT_CLIENT`, `FIELDS` and `ONE_OF`
|
|
135
|
-
are emitted `private_constant`, so **regenerate**.
|
|
116
|
+
| `graphql: false` (rspec tag) | `graphql: :live` — the opt-out is your own client, which is a mode like the other four; `false` is refused, naming it |
|
|
117
|
+
| `config.default_mode = nil` | `config.default_mode = :live`, which is now the **default** — every example has exactly one mode, and `nil` is no longer a value it reads back |
|
|
118
|
+
| `GraphWeaver::TypeError` | `GraphWeaver::CastError` — the response wouldn't cast into the generated structs; the old name shadowed a core class it doesn't descend from. No alias: the old constant is gone, so a stale `rescue` is a `NameError` |
|
|
119
|
+
| `GraphWeaver::ValidationError` | `GraphWeaver::QueryValidationError` — build time, the *query* against the schema. Your input's validation is `InputError`. No alias here either |
|
|
120
|
+
| `"graph_weaver.execute"` | `"execute.graph_weaver"` — `<event>.<namespace>`, the way every notification in this ecosystem is spelled, and what `LogSubscriber.attach_to` and an APM's namespace routing key on. Subscribe through `GraphWeaver::EXECUTE_EVENT` and there is nothing to rename; a hardcoded string silently stops matching |
|
|
136
121
|
|
|
137
122
|
### Behavior that changed under you
|
|
138
123
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
- **A
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
the
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
`
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
`
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
`
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
### Registering from Rails
|
|
305
|
-
|
|
306
|
-
A registration naming one of your own constants belongs in a `to_prepare` block
|
|
307
|
-
— the same place the in-process client goes, and for the same reason:
|
|
308
|
-
autoloading is set up after `config/initializers` run.
|
|
309
|
-
|
|
310
|
-
```ruby
|
|
311
|
-
Rails.application.config.to_prepare do
|
|
312
|
-
GraphWeaver.register_enum("Species", PetKind, fallback: PetKind::Unknown)
|
|
313
|
-
GraphWeaver.extend_type("Pet", PetHelpers)
|
|
314
|
-
end
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
Generation depends on `:environment`, which runs `to_prepare` too, so the
|
|
318
|
-
registration is in place before it emits — and at boot the generated files
|
|
319
|
-
load from a `to_prepare` block of their own, after yours.
|
|
320
|
-
|
|
321
|
-
### If you use the federation router
|
|
322
|
-
|
|
323
|
-
Detection only sees *loaded* schema classes, and Rails does not eager load for
|
|
324
|
-
rake or in the default test environment. Both are one line:
|
|
325
|
-
|
|
326
|
-
```ruby
|
|
327
|
-
config.eager_load = true # config/environments/test.rb
|
|
328
|
-
config.rake_eager_load = true # config/application.rb
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
Without them the `federation:*` tasks silently see nothing — and
|
|
332
|
-
`federation:diff` now **fails** rather than reporting a green "matches" over
|
|
333
|
-
zero subgraphs.
|
|
124
|
+
The first one reaches every Rails app that never configured logging, and it is the
|
|
125
|
+
only one here that shows up in production rather than in your code.
|
|
126
|
+
|
|
127
|
+
- **A Rails app logs one line per GraphQL call, and emits one notification.** The
|
|
128
|
+
railtie sets `GraphWeaver.instrumenter` to the `ActiveSupport::Notifications`
|
|
129
|
+
adapter and attaches `GraphWeaver::LogSubscriber`, so an app that configured
|
|
130
|
+
neither gets `GraphWeaver billing/InvoicesQuery (12.3ms) ok` at **info** — the
|
|
131
|
+
query and variables stay at debug. **To opt out, set `GraphWeaver.logger = nil`
|
|
132
|
+
or `GraphWeaver.instrumenter = nil` in `config/initializers`**, which now takes
|
|
133
|
+
effect (an app that worked around that with `config.after_initialize` can drop
|
|
134
|
+
it). In-process calls are in scope too: a bare schema class in a client slot goes
|
|
135
|
+
through the same wrapper, so it produces events and log lines where it produced
|
|
136
|
+
none. See [logging](logging.md).
|
|
137
|
+
- **A `Retry` in front of a gateway starts actually retrying.** It read only the
|
|
138
|
+
failures that *raised*, and Apollo Router answers everything it decides itself
|
|
139
|
+
with a GraphQL errors body, so `retries: 3` made one attempt. A response retries
|
|
140
|
+
now when its status is one a `ServerError` retries on (5xx, 408, 429), or when
|
|
141
|
+
its error codes are named in `retry_codes:`. **This is real traffic you weren't
|
|
142
|
+
sending** — if the inert policy was what you wanted, `retries: 0`.
|
|
143
|
+
- **A task that can't honour `SUPERGRAPH=` refuses instead of ignoring it.** The
|
|
144
|
+
flag reaches the `federation:*` tasks and nothing else, so
|
|
145
|
+
`SUPERGRAPH=public.graphql rake graph_weaver:queries:check` used to report every
|
|
146
|
+
query valid against a supergraph missing fields they select. **A CI step that
|
|
147
|
+
passes it to `generate`, `verify` or `queries:check` goes red**; drop the flag,
|
|
148
|
+
or declare the supergraph on a graph.
|
|
149
|
+
- **`InputError#field` names the input field, not the variable.** It is `#path`'s
|
|
150
|
+
last *named* segment — the slot that actually held the bad value — where it used
|
|
151
|
+
to be re-branded with the *variable* name. Nothing raises; the value just differs
|
|
152
|
+
once a refusal happens inside an input object. **Read `error.path.first` wherever
|
|
153
|
+
you wanted the variable.** An index is a position rather than a field, so
|
|
154
|
+
`execute(ids: [1, 2, "x"])` reports `#path` `["ids", 2]` and `#field` `"ids"`.
|
|
155
|
+
- **The instrumentation payload's `:status` is a Symbol, and the HTTP status moved
|
|
156
|
+
to `:http_status`.** `:status` is `:ok`, `:errors` or `:failed` — a 200 carrying
|
|
157
|
+
errors is not a success, and only a symbol says that on both sides of the seam.
|
|
158
|
+
Nothing raises: a subscriber comparing it to an Integer just stops matching. **A
|
|
159
|
+
subscriber that branched on a 4xx/5xx reads `:http_status` now**, which is nil
|
|
160
|
+
in-process. The whole payload is a documented contract —
|
|
161
|
+
[logging](logging.md#the-payload).
|
|
162
|
+
- **`respond_to?` on a result struct no longer answers true for a name that doesn't
|
|
163
|
+
exist.** It used to say true for any near miss, which broke the standard
|
|
164
|
+
duck-typing guard. **A branch that read the old answer now takes the other path**,
|
|
165
|
+
and `struct.method(:nmae)` raises Ruby's bare `NameError`; `struct.nmae` still
|
|
166
|
+
hints.
|
|
167
|
+
- **A `graphql:` tag reaches a module generated with `client:`.** The baked client
|
|
168
|
+
used to sit above the slot a tag swaps, so a bound module ran against its real
|
|
169
|
+
endpoint under `graphql: :fake`. **If a spec relied on that**, pass `client:` on
|
|
170
|
+
the call, set `MyQuery.client =`, or tag the example `graphql: :live`.
|
|
171
|
+
- **`config.context`, `config.schema` and `config.router` are suite setup.**
|
|
172
|
+
Setting any of the three once an example is running refuses, naming the
|
|
173
|
+
per-example helper (`graphql_context`, `graphql_fake(schema:)`,
|
|
174
|
+
`graphql_router(fake:)`). The tag builds an example's clients in a `before` hook
|
|
175
|
+
of its own, which rspec runs ahead of any group `before`, so a set there was read
|
|
176
|
+
too late and silently changed nothing. **Move it to an `around`, or to
|
|
177
|
+
`GraphWeaver::Testing.configure` in the spec helper.**
|
|
178
|
+
- **`result.to_json` is real JSON, and it is the wire shape.** It used to be Ruby's
|
|
179
|
+
`Object#to_json` — the `#inspect` string, quoted — while under Rails
|
|
180
|
+
`render json: result` shipped the *Ruby* prop names. Both now produce the response
|
|
181
|
+
keys, each leaf back through its scalar registration's `serialize:`, so
|
|
182
|
+
`Result.from_h(JSON.parse(result.to_json)) == result`. `#to_h` is unchanged and
|
|
183
|
+
still the Ruby view. **Anything that parsed the old output is reading something
|
|
184
|
+
different now** — and `as_json` is emitted code, so a struct generated by 0.6.1
|
|
185
|
+
raises `GraphWeaver::Error` naming this until you regenerate.
|
|
186
|
+
- **A schema dump introspected through a credentialed url still holds the token.**
|
|
187
|
+
The provenance stamp wrote the transport's url verbatim. It records the endpoint
|
|
188
|
+
bare now — userinfo and any query parameter `filter_parameters` filters are
|
|
189
|
+
dropped — and re-introspection still authenticates from the dump's `auth_env`.
|
|
190
|
+
**Run `rake graph_weaver:schema:refresh` once, and rotate the token if that file
|
|
191
|
+
was ever pushed.**
|
|
192
|
+
- **`verify` fails when the dump has fallen behind the schema class it was built
|
|
193
|
+
from.** For an app that serves its own schema the dump is an artifact derived
|
|
194
|
+
from code in the same repo, so `generate` and `verify` both called a tree up to
|
|
195
|
+
date while the live resolvers had already moved. **A dump you deliberately keep
|
|
196
|
+
behind your own schema is a red gate now** — `rake graph_weaver:schema:refresh`,
|
|
197
|
+
or ask about no dump at all with `verify_generated!(schema:)`. It costs one
|
|
198
|
+
in-process introspection per graph and never a network call.
|
|
199
|
+
- **`@oneOf` starts being enforced if your dump is `.json`.** graphql-ruby's
|
|
200
|
+
introspection query omits `isOneOf` unless asked, so every dump this gem had
|
|
201
|
+
written said "not @oneOf" for every input object. **Regenerate and the emitted
|
|
202
|
+
`ONE_OF` starts refusing calls that set two fields** — which your server was
|
|
203
|
+
refusing all along, so the failure moves from the wire into `execute`. SDL dumps,
|
|
204
|
+
inline SDL and a live class were always correct.
|
|
205
|
+
- **A fake pin is told from an option by a schema lookup, not by casing.** A
|
|
206
|
+
lowercase type could not be pinned at all (`graphql_fake("pokemon_v2_pokemon" =>
|
|
207
|
+
…)` against a Hasura API); those pins work now. The other side of it: **a keyword
|
|
208
|
+
that is a near-miss for a pin (`Persn: "Ada"`) raises `ArgumentError` from the
|
|
209
|
+
fake** rather than `GraphWeaver::Error` from the override check — the same key
|
|
210
|
+
written in the leading positional hash is unchanged, and is the spelling for a
|
|
211
|
+
schema whose vocabulary collides with an option name.
|
|
212
|
+
- **A `DateTime` given for a `Date` variable is refused.** `DateTime` is a `Date`
|
|
213
|
+
to Ruby, so it used to pass the cast untouched and go on the wire as a full
|
|
214
|
+
timestamp where the schema said `ISO8601Date`. **Pass `.to_date`.** The pairings
|
|
215
|
+
that already raised — a `Time` for a date, a `Date` for a timestamp — now raise a
|
|
216
|
+
branded `InputError` rather than Ruby's *"no implicit conversion of Time into
|
|
217
|
+
String"*, and a `DateTime` or `Time.zone.now` for a *timestamp* converts
|
|
218
|
+
losslessly where it used to raise.
|
|
219
|
+
- **A `cast:` of your own gets the same guard and the same verdict.** A registration
|
|
220
|
+
like `register_scalar("Date", Date, cast: :iso8601, serialize: :iso8601)` emitted
|
|
221
|
+
a bare `value.is_a?(Date)` pass-through, so a `DateTime` went by untouched and
|
|
222
|
+
your `serialize:` wrote a full timestamp into a date field — **pass `.to_date`
|
|
223
|
+
there too**. Anything else wrong used to arrive as Ruby's own sentence under
|
|
224
|
+
`kind: :unparseable`; the verdict is the library's now and splits the way Ruby
|
|
225
|
+
does — a `TypeError` from a codec reads `expected a Date, got 5` under
|
|
226
|
+
`:type_mismatch`, an `ArgumentError` keeps the parser's words under
|
|
227
|
+
`:unparseable`. **`#details[:type]` is the GraphQL type now, never a Ruby class.**
|
|
228
|
+
A spec matching the old message, or branching on `:unparseable` for a wrong class,
|
|
229
|
+
needs updating — and the guard is emitted into your generated files, so a
|
|
230
|
+
checked-in one keeps the old behavior until you regenerate.
|
|
231
|
+
- **A field whose name a struct already answers to now generates as `name_`.**
|
|
232
|
+
`class` becomes `class_`, and so on for `hash`, `display`, `to_json`, `each` and
|
|
233
|
+
(on an input) `supplied`. Only the Ruby name moves: the wire keeps the schema's
|
|
234
|
+
spelling in both directions, so a refusal on that field still reports `#path`
|
|
235
|
+
`["class"]`, and an input struct's `#to_h` is still the wire hash. A key you
|
|
236
|
+
aliased in the query to get past the old refusal still generates from that alias
|
|
237
|
+
— **drop the alias and regenerate** if you want the field's own name back. The
|
|
238
|
+
names that take an underscore are a list the gem owns (`T::Struct` and `Object`'s
|
|
239
|
+
public instance methods, the hooks Ruby and Rails call on an object, and the
|
|
240
|
+
gem's own mixins) rather than whatever the generating process happened to have
|
|
241
|
+
loaded, so a few more names move than 0.6.1 touched; Kernel's *private* methods
|
|
242
|
+
are not on it, so `format`, `select`, `open` and `load` stay ordinary props. A
|
|
243
|
+
federation `@key` on such a field follows the same rule instead of being refused
|
|
244
|
+
— the kwarg takes the underscore and `"class"` still goes on the wire, so
|
|
245
|
+
**regenerate if a `@key` of yours names one**. Generated source marks each rename
|
|
246
|
+
on the line above the prop, so **read the regenerate diff**.
|
|
247
|
+
- **If you adopt `GraphWeaver.graph`, every queries directory needs a graph.**
|
|
248
|
+
Declaring one replaces the implicit graph your top-level settings describe, so a
|
|
249
|
+
graph declared beside an existing `app/graphql/queries` used to leave that
|
|
250
|
+
directory unread. `generate!`, `verify_generated!` and `check_queries` refuse
|
|
251
|
+
now, naming the stray files. **Name the directory in a graph, declare a graph for
|
|
252
|
+
it, or delete it.** An app that declares no graph is unaffected.
|
|
253
|
+
- **A `client` that isn't a constant is refused at generation.** Its value is
|
|
254
|
+
spelled into every module the graph generates, so `client` given an endpoint url
|
|
255
|
+
emitted a file that doesn't parse, from a run that reported success. Declare the
|
|
256
|
+
constant and name it — `CLIENT = GraphWeaver.new(url)`, then `client "CLIENT"`.
|
|
257
|
+
- **A `cast:` or `serialize:` proc that returns a value is refused at
|
|
258
|
+
registration.** A proc there builds *source* for the generated file, so
|
|
259
|
+
`cast: ->(v) { v.to_sym }` interpolated to nothing and every response failed far
|
|
260
|
+
from the registration. It is probed once when registered now: return the source
|
|
261
|
+
(`cast: ->(v) { "Money.parse(#{v})" }`) or name a method instead.
|
|
262
|
+
- **`config.graph_weaver` refuses a key the railtie doesn't read**, at boot. It
|
|
263
|
+
takes `watch`; `config.graph_weaver.queries_paths = …` was taken silently and did
|
|
264
|
+
nothing. **Move it to `GraphWeaver.queries_paths =`.**
|
|
265
|
+
- **A router's `fake:` refuses `seed:`**, as `graphql_fake` already did. A router is
|
|
266
|
+
built once for the suite, so a seed there would pin every example to one run —
|
|
267
|
+
`rspec --seed 1234` reproduces the fabricated data along with the test order, and
|
|
268
|
+
`GraphWeaver::Testing.config.seed` is the override for a harness that isn't rspec.
|
|
269
|
+
- **Check that your `require "graph_weaver/rspec"` actually runs.** The old setup
|
|
270
|
+
put it in `spec/support/graph_weaver.rb`, and rspec-rails ships the `spec/support`
|
|
271
|
+
glob **commented out** — so if you never uncommented it, the tag did nothing and
|
|
272
|
+
every `graphql: :fake` example has been hitting the real client.
|
|
273
|
+
`rails g graph_weaver:install` writes the line into `spec/rails_helper.rb`
|
|
274
|
+
instead; **move yours there** if the glob isn't live.
|
|
275
|
+
- **`graphql: :wire`, if you adopt it, needs webmock *enabled*** — `require
|
|
276
|
+
"webmock/rspec"` in the spec helper. Having it in the Gemfile is not enough:
|
|
277
|
+
`Bundler.require` loads webmock without installing its adapters, and the tag
|
|
278
|
+
refuses before the first request.
|
|
279
|
+
- **Regenerate**, as ever. Generated modules carry a private `GRAPH` naming the
|
|
280
|
+
graph they were generated from, and a
|
|
281
|
+
[multi-schema](getting_started.md#more-than-one-schema) app whose modules predate
|
|
282
|
+
it refuses rather than guessing which schema a module belongs to. A generated
|
|
283
|
+
`execute` also makes its request through the gem now, which is what lets an event
|
|
284
|
+
name the graph; 0.6.1's modules keep working, but `verify` reports the tree out of
|
|
285
|
+
date until you regenerate. Result structs also gained `==`/`eql?`/`hash`,
|
|
286
|
+
`deconstruct_keys`, `#to_h` and `#as_json`.
|