graph_weaver 0.5.1 → 0.6.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 +432 -0
- data/Gemfile.lock +19 -19
- data/README.md +75 -54
- data/docs/cassettes.md +6 -1
- data/docs/editors.md +3 -1
- data/docs/errors.md +73 -16
- data/docs/federation.md +201 -151
- data/docs/generated_modules.md +222 -165
- data/docs/getting_started.md +106 -82
- data/docs/logging.md +34 -4
- data/docs/real_world.md +4 -4
- data/docs/scalars.md +206 -46
- data/docs/testing.md +191 -151
- data/docs/transports.md +47 -19
- data/docs/upgrading.md +210 -11
- data/lib/generators/graph_weaver/install_generator.rb +16 -1
- data/lib/graph_weaver/client.rb +46 -13
- data/lib/graph_weaver/codegen/aliases.rb +5 -4
- data/lib/graph_weaver/codegen/emit.rb +96 -39
- data/lib/graph_weaver/codegen/enum_type.rb +3 -0
- data/lib/graph_weaver/codegen/nodes.rb +42 -21
- data/lib/graph_weaver/codegen/scalar_type.rb +167 -98
- data/lib/graph_weaver/codegen/type_helpers.rb +1 -0
- data/lib/graph_weaver/codegen.rb +279 -84
- data/lib/graph_weaver/coerce.rb +113 -0
- data/lib/graph_weaver/errors.rb +30 -7
- data/lib/graph_weaver/federation.rb +6 -5
- data/lib/graph_weaver/hints.rb +76 -2
- data/lib/graph_weaver/in_process.rb +11 -8
- data/lib/graph_weaver/inflect.rb +2 -0
- data/lib/graph_weaver/input_struct.rb +115 -12
- data/lib/graph_weaver/internal/overrides.rb +101 -0
- data/lib/graph_weaver/internal/planner.rb +868 -0
- data/lib/graph_weaver/internal/schemas.rb +50 -0
- data/lib/graph_weaver/internal/selection.rb +127 -0
- data/lib/graph_weaver/{testing → internal}/subgraphs.rb +39 -41
- data/lib/graph_weaver/internal/values.rb +184 -0
- data/lib/graph_weaver/internal.rb +206 -0
- data/lib/graph_weaver/logging.rb +108 -20
- data/lib/graph_weaver/parsing.rb +5 -4
- data/lib/graph_weaver/query_module.rb +2 -0
- data/lib/graph_weaver/railtie.rb +113 -14
- data/lib/graph_weaver/representation.rb +30 -2
- data/lib/graph_weaver/response.rb +15 -0
- data/lib/graph_weaver/retry.rb +54 -22
- data/lib/graph_weaver/rspec.rb +50 -11
- data/lib/graph_weaver/schema_diff.rb +293 -0
- data/lib/graph_weaver/schema_loader.rb +96 -29
- data/lib/graph_weaver/tasks.rb +78 -29
- data/lib/graph_weaver/testing/cassette.rb +49 -65
- data/lib/graph_weaver/testing/coverage.rb +5 -4
- data/lib/graph_weaver/testing/failure.rb +10 -6
- data/lib/graph_weaver/testing/fake_client.rb +253 -60
- data/lib/graph_weaver/testing/fake_subgraph.rb +19 -8
- data/lib/graph_weaver/testing/router.rb +94 -808
- data/lib/graph_weaver/testing.rb +35 -84
- data/lib/graph_weaver/transport/faraday.rb +1 -1
- data/lib/graph_weaver/transport/http.rb +29 -12
- data/lib/graph_weaver/transport.rb +11 -34
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +203 -119
- metadata +10 -5
- data/lib/graph_weaver/schemas.rb +0 -48
- data/lib/graph_weaver/selection.rb +0 -120
- data/lib/graph_weaver/testing/values.rb +0 -98
|
@@ -12,10 +12,12 @@ class GraphWeaver::Codegen
|
|
|
12
12
|
# scalars and overrides go through the same path.
|
|
13
13
|
#
|
|
14
14
|
# cast/serialize normalize to procs that, given a Ruby expression string,
|
|
15
|
-
# return the code to inline. Left nil (the default) they are inferred
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
15
|
+
# return the code to inline. Left nil (the default) they are inferred from
|
|
16
|
+
# the Ruby type: a type the library already knows takes its codec from
|
|
17
|
+
# STDLIB, otherwise it is probed for a known deserializer whose serializer
|
|
18
|
+
# pairs with it (see CODECS), and failing that for a Kernel conversion
|
|
19
|
+
# function of its own name — so the common case needs no more than a class:
|
|
20
|
+
# type: BigDecimal (Kernel#BigDecimal) => BigDecimal(expr) / expr.to_s("F")
|
|
19
21
|
# type: Money (defines .parse) => Money.parse(expr) / expr.to_s
|
|
20
22
|
# type: Blob (defines .load) => Blob.load(expr) / Blob.dump(expr)
|
|
21
23
|
# Probing the *deserialize* side is deliberate: every object has #to_s,
|
|
@@ -25,6 +27,7 @@ class GraphWeaver::Codegen
|
|
|
25
27
|
# - a Symbol names a method, so there is no string to misspell:
|
|
26
28
|
# cast: :load => "Blob.load(expr)" (class method on type)
|
|
27
29
|
# serialize: :to_json => "expr.to_json" (instance method)
|
|
30
|
+
# - an Array is that method with arguments: serialize: [:to_s, "F"]
|
|
28
31
|
# - a Proc handles anything a Symbol can't express:
|
|
29
32
|
# cast: ->(e) { "Money.new(#{e})" }
|
|
30
33
|
# - :itself opts out — force identity pass-through even when a codec
|
|
@@ -34,85 +37,117 @@ class GraphWeaver::Codegen
|
|
|
34
37
|
class ScalarType
|
|
35
38
|
# Inferred (deserialize, serialize) codecs, tried in order; the first
|
|
36
39
|
# whose probe the Ruby type defines as a class method wins, and its
|
|
37
|
-
# serialize is paired with it. Builders take (type_name, expr) => code
|
|
38
|
-
|
|
40
|
+
# serialize is paired with it. Builders take (type_name, expr) => code;
|
|
41
|
+
# `call` is the same serialization run rather than emitted, for the
|
|
42
|
+
# testing harness (see #serialize_value).
|
|
43
|
+
Codec = Struct.new(:probe, :cast, :serialize, :call)
|
|
39
44
|
CODECS = [
|
|
40
45
|
Codec.new(:parse, # Type.parse(wire) <-> value.to_s
|
|
41
46
|
->(type, expr) { "#{type}.parse(#{expr})" },
|
|
42
|
-
->(_type, expr) { "#{expr}.to_s" }
|
|
47
|
+
->(_type, expr) { "#{expr}.to_s" },
|
|
48
|
+
->(_klass, value) { value.to_s }),
|
|
43
49
|
Codec.new(:load, # Type.load(wire) <-> Type.dump(value)
|
|
44
50
|
->(type, expr) { "#{type}.load(#{expr})" },
|
|
45
|
-
->(type, expr) { "#{type}.dump(#{expr})" }
|
|
51
|
+
->(type, expr) { "#{type}.dump(#{expr})" },
|
|
52
|
+
->(klass, value) { klass.dump(value) }),
|
|
46
53
|
].freeze
|
|
47
54
|
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"Float" =>
|
|
55
|
+
# A scalar with no `cast:` to run input through: its Ruby type is the
|
|
56
|
+
# whole rule, so key on that — a custom scalar registered as a plain
|
|
57
|
+
# String gets the same check. ID is the exception GraphQL itself names
|
|
58
|
+
# (see Coerce.id), matched by GraphQL name in #coercer.
|
|
59
|
+
COERCERS = {
|
|
60
|
+
"Integer" => "integer",
|
|
61
|
+
"Float" => "float",
|
|
62
|
+
"String" => "string",
|
|
63
|
+
"T::Boolean" => "boolean",
|
|
55
64
|
}.freeze
|
|
56
65
|
|
|
66
|
+
# What the library already knows about a Ruby type, so registering one
|
|
67
|
+
# takes nothing but the class. Consulted only where the registration is
|
|
68
|
+
# silent; an explicit cast:/serialize:/requires: wins. Two things the
|
|
69
|
+
# probe above can't work out on its own:
|
|
70
|
+
# - the wire spelling. BigDecimal#to_s writes "0.125e2", which is not
|
|
71
|
+
# what any server means by 12.5, and Date.parse reads a great deal
|
|
72
|
+
# more than the ISO 8601 a Date scalar carries.
|
|
73
|
+
# - the file to require, so the generated source stands alone.
|
|
74
|
+
# Only types whose wire form is unambiguous belong here.
|
|
75
|
+
STDLIB = {
|
|
76
|
+
"BigDecimal" => { serialize: [:to_s, "F"], requires: "bigdecimal" },
|
|
77
|
+
"Date" => { cast: :iso8601, serialize: :iso8601, requires: "date" },
|
|
78
|
+
"Time" => { cast: :parse, serialize: :iso8601, requires: "time" },
|
|
79
|
+
"DateTime" => { cast: :iso8601, serialize: :iso8601, requires: "date" },
|
|
80
|
+
}.freeze
|
|
81
|
+
|
|
82
|
+
# Everything JSON.parse can hand back. A registered type outside this set
|
|
83
|
+
# has to be BUILT from one of them, which is what a cast is for (and what
|
|
84
|
+
# Codegen#refuse_uncastable! insists on).
|
|
85
|
+
WIRE_CLASSES = [String, Integer, Float, Hash, Array, TrueClass, FalseClass].freeze
|
|
86
|
+
|
|
87
|
+
private_constant :Codec, :CODECS, :COERCERS, :STDLIB
|
|
88
|
+
|
|
57
89
|
attr_reader :graphql_name, :type, :requires
|
|
58
90
|
|
|
59
|
-
def initialize(graphql_name, type, cast: nil, serialize: nil, requires: nil
|
|
91
|
+
def initialize(graphql_name, type, cast: nil, serialize: nil, requires: nil)
|
|
60
92
|
@graphql_name = graphql_name.to_s
|
|
61
93
|
@klass = type.is_a?(Module) ? type : nil
|
|
62
94
|
@type = type_name(type)
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
# stdlib loads
|
|
66
|
-
|
|
95
|
+
known = STDLIB[@type] || {}
|
|
96
|
+
# requires: load BEFORE probing — the deserializer may arrive with the
|
|
97
|
+
# file (core Time has no .parse until the "time" stdlib loads, and
|
|
98
|
+
# Kernel#BigDecimal none until "bigdecimal" does). A path from STDLIB
|
|
99
|
+
# is the library's own, so it loads even for a type: given as a String,
|
|
100
|
+
# whose dependency we otherwise can't assume is installed.
|
|
101
|
+
@requires =
|
|
102
|
+
if requires.nil?
|
|
103
|
+
GraphWeaver::Codegen.normalize_requires!(known[:requires], load: true)
|
|
104
|
+
else
|
|
105
|
+
GraphWeaver::Codegen.normalize_requires!(requires, load: !@klass.nil?)
|
|
106
|
+
end
|
|
67
107
|
codec = @klass && CODECS.find { |c| @klass.respond_to?(c.probe) }
|
|
68
|
-
@cast = normalize_cast(cast, codec&.cast)
|
|
69
|
-
@serialize = normalize_serialize(serialize, codec&.serialize)
|
|
70
|
-
@
|
|
71
|
-
validate_coerce!
|
|
108
|
+
@cast = normalize_cast(cast || known[:cast], codec&.cast || kernel_cast)
|
|
109
|
+
@serialize = normalize_serialize(serialize || known[:serialize], codec&.serialize)
|
|
110
|
+
@serialize_value = runtime_serialize(serialize || known[:serialize], codec)
|
|
72
111
|
end
|
|
73
112
|
|
|
74
113
|
def cast(expr) = @cast&.call(expr)
|
|
75
114
|
def cast? = !@cast.nil?
|
|
76
115
|
def serialize(expr) = @serialize&.call(expr)
|
|
77
116
|
def serialize? = !@serialize.nil?
|
|
78
|
-
def coerce? =
|
|
79
|
-
|
|
80
|
-
# How this scalar coerces a variable input, or nil for not at all.
|
|
81
|
-
# coerce: says WHETHER (explicit always wins); left unset the global
|
|
82
|
-
# GraphWeaver.auto_coerce decides — resolved HERE, at generation time,
|
|
83
|
-
# so registration order doesn't matter. The scalar itself says HOW: a
|
|
84
|
-
# convertible built-in converts, anything with a full cast/serialize
|
|
85
|
-
# pair parses. Nothing left to try means it can't coerce.
|
|
86
|
-
def coercion
|
|
87
|
-
return if @coerce == false
|
|
88
|
-
return if @coerce.nil? && !GraphWeaver.auto_coerce
|
|
89
|
-
|
|
90
|
-
CONVERSIONS[@graphql_name] || (:parse if cast? && serialize?)
|
|
91
|
-
end
|
|
117
|
+
def coerce? = !coerce_input("v").nil?
|
|
92
118
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
when :parse then "(#{expr}.is_a?(#{@type}) ? #{expr} : #{cast(expr)})"
|
|
102
|
-
when Conversion then "#{expr}.#{how.via}"
|
|
103
|
-
end
|
|
119
|
+
# #serialize run rather than emitted: the wire value for a Ruby one. The
|
|
120
|
+
# testing harness reads app objects — a Time, a Money — off an object pin
|
|
121
|
+
# and has to write what the server would. A `serialize:` proc builds code
|
|
122
|
+
# and can't be run, so its value passes through and the cast complains.
|
|
123
|
+
def serialize_value(value)
|
|
124
|
+
return value if value.nil? || @serialize_value.nil?
|
|
125
|
+
|
|
126
|
+
@serialize_value.call(value)
|
|
104
127
|
end
|
|
105
128
|
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
# The code that normalizes a loose input — a Rails param — into this
|
|
130
|
+
# scalar's Ruby type before it is serialized, or nil for nothing to do.
|
|
131
|
+
# `cast:` is the how: it already knows how to build the Ruby object from
|
|
132
|
+
# a wire value, guarded so an already-typed value passes through. A
|
|
133
|
+
# scalar without one falls back to its Ruby type's check, which is what
|
|
134
|
+
# `.checked(:never)` on the generated sig gives up.
|
|
135
|
+
def coerce_input(expr)
|
|
136
|
+
if cast?
|
|
137
|
+
"(#{expr}.is_a?(#{@type}) ? #{expr} : #{cast(expr)})"
|
|
138
|
+
elsif (fn = coercer)
|
|
139
|
+
"GraphWeaver::Coerce.#{fn}(#{expr})"
|
|
111
140
|
end
|
|
112
141
|
end
|
|
113
142
|
|
|
114
143
|
private
|
|
115
144
|
|
|
145
|
+
def coercer
|
|
146
|
+
return "id" if @graphql_name == "ID" && @type == "String"
|
|
147
|
+
|
|
148
|
+
COERCERS[@type]
|
|
149
|
+
end
|
|
150
|
+
|
|
116
151
|
def type_name(type)
|
|
117
152
|
case type
|
|
118
153
|
when Module
|
|
@@ -124,6 +159,17 @@ class GraphWeaver::Codegen
|
|
|
124
159
|
end
|
|
125
160
|
end
|
|
126
161
|
|
|
162
|
+
# Kernel's conversion functions are how a wire value becomes one of these
|
|
163
|
+
# — BigDecimal defines neither .parse nor .load, but Kernel#BigDecimal has
|
|
164
|
+
# read a decimal string all along. Only for a type the wire can't already
|
|
165
|
+
# be: Kernel#String and Kernel#Array wrap a value rather than convert it.
|
|
166
|
+
def kernel_cast
|
|
167
|
+
return unless Kernel.private_method_defined?(@type.to_sym)
|
|
168
|
+
return if WIRE_CLASSES.any? { |native| native.name == @type }
|
|
169
|
+
|
|
170
|
+
->(type, expr) { "#{type}(#{expr})" }
|
|
171
|
+
end
|
|
172
|
+
|
|
127
173
|
# nil infers via the matched codec; :itself opts out (identity); a
|
|
128
174
|
# Symbol is a class method on the type — Money.parse(expr)
|
|
129
175
|
def normalize_cast(cast, inferred)
|
|
@@ -136,37 +182,35 @@ class GraphWeaver::Codegen
|
|
|
136
182
|
end
|
|
137
183
|
end
|
|
138
184
|
|
|
139
|
-
# nil infers via the matched codec; :itself opts out (identity); a
|
|
140
|
-
#
|
|
185
|
+
# nil infers via the matched codec; :itself opts out (identity); a Symbol
|
|
186
|
+
# is an instance method on the value — expr.to_s — and an Array is that
|
|
187
|
+
# method with arguments: [:to_s, "F"] => expr.to_s("F")
|
|
141
188
|
def normalize_serialize(serialize, inferred)
|
|
142
189
|
case serialize
|
|
143
190
|
when :itself then nil
|
|
144
191
|
when nil then inferred && ->(expr) { inferred.call(@type, expr) }
|
|
145
192
|
when Proc then serialize
|
|
146
193
|
when Symbol then ->(expr) { "#{expr}.#{serialize}" }
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
194
|
+
when Array
|
|
195
|
+
method, *args = serialize
|
|
196
|
+
unless method.is_a?(Symbol)
|
|
197
|
+
# a syntax error in the generated file otherwise
|
|
198
|
+
raise ArgumentError, "serialize: an Array is [method, *arguments], got #{serialize.inspect}"
|
|
199
|
+
end
|
|
150
200
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
GraphWeaver::Codegen.normalize_requires!(requires, load: !@klass.nil?)
|
|
201
|
+
->(expr) { "#{expr}.#{method}(#{args.map(&:inspect).join(", ")})" }
|
|
202
|
+
else raise ArgumentError, "serialize: must be a Symbol, Array, Proc, :itself, or nil, got #{serialize.inspect}"
|
|
203
|
+
end
|
|
155
204
|
end
|
|
156
205
|
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
raise ArgumentError,
|
|
166
|
-
"coerce: true needs a cast and a serialize (#{@graphql_name} has neither, " \
|
|
167
|
-
"so there is nothing to coerce)"
|
|
168
|
-
else
|
|
169
|
-
raise ArgumentError, "coerce: must be true or false, got #{@coerce.inspect}"
|
|
206
|
+
# The runnable half of normalize_serialize: a Symbol (or Symbol with
|
|
207
|
+
# arguments) names a method — :itself included, which is identity either
|
|
208
|
+
# way — an inferred codec knows its own call, and a Proc emits code there
|
|
209
|
+
# is no way to run.
|
|
210
|
+
def runtime_serialize(serialize, codec)
|
|
211
|
+
case serialize
|
|
212
|
+
when Symbol, Array then ->(value) { value.public_send(*serialize) }
|
|
213
|
+
when nil then codec && ->(value) { codec.call.call(@klass, value) }
|
|
170
214
|
end
|
|
171
215
|
end
|
|
172
216
|
end
|
|
@@ -200,18 +244,19 @@ class GraphWeaver::Codegen
|
|
|
200
244
|
# the accepted cast:/serialize:/requires: forms. Later registrations
|
|
201
245
|
# win, so an app can override a built-in (e.g. map Date onto its own
|
|
202
246
|
# type).
|
|
203
|
-
def register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil
|
|
247
|
+
def register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil)
|
|
204
248
|
scalar_registry[graphql_name.to_s] =
|
|
205
|
-
ScalarType.new(graphql_name, type, cast:, serialize:, requires
|
|
249
|
+
ScalarType.new(graphql_name, type, cast:, serialize:, requires:)
|
|
206
250
|
end
|
|
207
251
|
|
|
208
|
-
# The ScalarType for a scalar
|
|
209
|
-
#
|
|
210
|
-
# scalars
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
252
|
+
# The ScalarType in play for a scalar, most specific first: the
|
|
253
|
+
# `Type.field` registration when `coordinate` names one, then the
|
|
254
|
+
# scalar-name registration. Unknown scalars fall back to an untyped
|
|
255
|
+
# pass-through (T.untyped, no cast) — the prior behavior for scalars
|
|
256
|
+
# outside the table.
|
|
257
|
+
def scalar(graphql_name, coordinate = nil)
|
|
258
|
+
(coordinate && scalar_registry[coordinate.to_s]) ||
|
|
259
|
+
scalar_registry.fetch(graphql_name.to_s) { ScalarType.new(graphql_name, "T.untyped") }
|
|
215
260
|
end
|
|
216
261
|
|
|
217
262
|
def scalar_registry
|
|
@@ -226,33 +271,57 @@ class GraphWeaver::Codegen
|
|
|
226
271
|
end
|
|
227
272
|
|
|
228
273
|
# Drop every custom registration and restore the built-in scalars — the
|
|
229
|
-
# clean slate to reach for between tests, or to undo overrides.
|
|
230
|
-
# the built-ins to coerce loose input? That's GraphWeaver.auto_coerce,
|
|
231
|
-
# resolved at generation time — no re-registering.)
|
|
274
|
+
# clean slate to reach for between tests, or to undo overrides.
|
|
232
275
|
def reset_scalars!
|
|
233
276
|
clear_scalars!
|
|
234
277
|
register_builtin_scalars!
|
|
235
278
|
self
|
|
236
279
|
end
|
|
237
280
|
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
281
|
+
# Pre-registered scalars — ordinary entries in the one registry, so a
|
|
282
|
+
# later register_scalar overrides any of them.
|
|
283
|
+
#
|
|
284
|
+
# The five the spec names stay pass-through: their Ruby classes (String,
|
|
285
|
+
# Integer) define neither .parse nor .load, so inference matches nothing
|
|
286
|
+
# and leaves them identity — which is exactly why we can name them with
|
|
287
|
+
# the real class constants. Float is the exception: JSON has one number
|
|
288
|
+
# type, so a whole Float arrives as `1` from every encoder that drops the
|
|
289
|
+
# trailing zero (graphql-js and Go both do), and Coerce.float widens that
|
|
290
|
+
# without accepting the garbage `.to_f` would silently turn into 0.0.
|
|
291
|
+
#
|
|
292
|
+
# The rest are names, not guesses: graphql-ruby ships all but DateTime as
|
|
293
|
+
# its own scalars, and this library runs a graphql-ruby schema in-process.
|
|
294
|
+
# DateTime is what GitHub, Shopify and most hand-written schemas call an
|
|
295
|
+
# ISO 8601 timestamp; a schema that means something else by it fails
|
|
296
|
+
# loudly (the cast raises, naming the field) and is one register_scalar
|
|
297
|
+
# away. Date and datetime are told apart by their Ruby type — a Date cast
|
|
298
|
+
# to Time would invent a midnight the server never sent.
|
|
246
299
|
def register_builtin_scalars!
|
|
247
300
|
register_scalar "ID", String
|
|
248
301
|
register_scalar "String", String
|
|
249
302
|
register_scalar "Int", Integer
|
|
250
|
-
register_scalar "Float", Float
|
|
303
|
+
register_scalar "Float", Float, cast: ->(expr) { "GraphWeaver::Coerce.float(#{expr})" }
|
|
251
304
|
register_scalar "Boolean", "T::Boolean"
|
|
252
|
-
register_scalar "Date", Date
|
|
305
|
+
register_scalar "Date", Date
|
|
306
|
+
register_scalar "ISO8601Date", Date
|
|
307
|
+
register_scalar "ISO8601DateTime", Time
|
|
308
|
+
register_scalar "DateTime", Time
|
|
309
|
+
# graphql-ruby writes a BigInt as a string, since JSON numbers stop
|
|
310
|
+
# being exact at 2^53 — so read either spelling and write the one the
|
|
311
|
+
# server does.
|
|
312
|
+
register_scalar "BigInt", Integer,
|
|
313
|
+
cast: ->(expr) { "GraphWeaver::Coerce.integer(#{expr})" }, serialize: :to_s
|
|
314
|
+
# untyped on purpose: registering it says so, rather than leaving JSON
|
|
315
|
+
# in the "unregistered custom scalars" report every generation
|
|
316
|
+
register_scalar "JSON", "T.untyped"
|
|
253
317
|
end
|
|
318
|
+
private :register_builtin_scalars!
|
|
254
319
|
end
|
|
255
320
|
|
|
321
|
+
# codegen's own record of a registration; users get one back from
|
|
322
|
+
# `.scalar` but never name the class
|
|
323
|
+
private_constant :ScalarType
|
|
324
|
+
|
|
256
325
|
register_builtin_scalars!
|
|
257
326
|
|
|
258
327
|
# Pre-registered rather than user intent, so generation doesn't hold a schema
|
|
@@ -82,6 +82,7 @@ class GraphWeaver::Codegen
|
|
|
82
82
|
# arbitrary text that could inject code
|
|
83
83
|
ALIAS_NAME = /\A[a-zA-Z_]\w*[?!]?\z/
|
|
84
84
|
ALIAS_SEGMENT = /\A[a-zA-Z_]\w*\z/
|
|
85
|
+
private_constant :ALIAS_NAME, :ALIAS_SEGMENT
|
|
85
86
|
|
|
86
87
|
# { accessor => { segments:, optional: } } from a path string (accessor
|
|
87
88
|
# named after the last segment), an array of such, or an { accessor => path }
|