graph_weaver 0.1.0 → 0.2.0

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/CHANGELOG.md +213 -0
  4. data/Gemfile.lock +32 -2
  5. data/Makefile +7 -2
  6. data/NOTES.md +1 -1
  7. data/PLAN.md +34 -1
  8. data/README.md +56 -41
  9. data/docs/cassettes.md +76 -0
  10. data/docs/errors.md +37 -9
  11. data/docs/generated_modules.md +139 -29
  12. data/docs/getting_started.md +134 -0
  13. data/docs/logging.md +33 -0
  14. data/docs/real_world.md +27 -20
  15. data/docs/scalars.md +122 -8
  16. data/docs/testing.md +44 -34
  17. data/docs/transports.md +124 -0
  18. data/graph_weaver.gemspec +7 -1
  19. data/lib/graph_weaver/client.rb +200 -0
  20. data/lib/graph_weaver/codegen/emit.rb +88 -39
  21. data/lib/graph_weaver/codegen/enum_type.rb +149 -0
  22. data/lib/graph_weaver/codegen/nodes.rb +109 -9
  23. data/lib/graph_weaver/codegen/scalar_type.rb +53 -35
  24. data/lib/graph_weaver/codegen.rb +301 -67
  25. data/lib/graph_weaver/errors.rb +37 -25
  26. data/lib/graph_weaver/hints.rb +63 -0
  27. data/lib/graph_weaver/inflect.rb +2 -1
  28. data/lib/graph_weaver/logging.rb +41 -0
  29. data/lib/graph_weaver/railtie.rb +29 -0
  30. data/lib/graph_weaver/retry.rb +97 -0
  31. data/lib/graph_weaver/rspec.rb +19 -14
  32. data/lib/graph_weaver/schema_loader.rb +156 -20
  33. data/lib/graph_weaver/selection.rb +3 -3
  34. data/lib/graph_weaver/tasks.rb +71 -0
  35. data/lib/graph_weaver/testing/cassette.rb +42 -21
  36. data/lib/graph_weaver/testing/failure.rb +22 -22
  37. data/lib/graph_weaver/testing/{fake_executor.rb → fake_client.rb} +13 -13
  38. data/lib/graph_weaver/testing/values.rb +2 -2
  39. data/lib/graph_weaver/testing.rb +32 -16
  40. data/lib/graph_weaver/transport/faraday.rb +56 -0
  41. data/lib/graph_weaver/transport/http.rb +87 -0
  42. data/lib/graph_weaver/transport.rb +120 -0
  43. data/lib/graph_weaver/version.rb +1 -1
  44. data/lib/graph_weaver.rb +208 -38
  45. metadata +73 -5
  46. data/lib/graph_weaver/faraday_executor.rb +0 -61
  47. data/lib/graph_weaver/http_executor.rb +0 -44
  48. data/lib/graph_weaver/testing/rspec.rb +0 -5
@@ -1,6 +1,8 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "date"
5
+
4
6
  class GraphWeaver::Codegen
5
7
 
6
8
  # How one GraphQL scalar maps to Ruby: the Sorbet prop type, the
@@ -15,17 +17,17 @@ class GraphWeaver::Codegen
15
17
  # from the Ruby type when it is a real class, by probing for a known
16
18
  # deserializer and pairing its serializer (see CODECS) — so the common
17
19
  # case needs no more than a class:
18
- # type: Money (defines .parse) => Money.parse(expr) / expr.to_s
19
- # type: Blob (defines .load) => Blob.load(expr) / Blob.dump(expr)
20
+ # type: Money (defines .parse) => Money.parse(expr) / expr.to_s
21
+ # type: Blob (defines .load) => Blob.load(expr) / Blob.dump(expr)
20
22
  # Probing the *deserialize* side is deliberate: every object has #to_s,
21
23
  # so inferring a serializer off it would wrongly wrap plain types (String,
22
24
  # Integer) — pairing off a deserializer the type actually defines avoids
23
25
  # that. Override with an explicit value:
24
26
  # - a Symbol names a method, so there is no string to misspell:
25
- # cast: :load => "Blob.load(expr)" (class method on type)
26
- # serialize: :to_json => "expr.to_json" (instance method)
27
+ # cast: :load => "Blob.load(expr)" (class method on type)
28
+ # serialize: :to_json => "expr.to_json" (instance method)
27
29
  # - a Proc handles anything a Symbol can't express:
28
- # cast: ->(e) { "Money.new(#{e})" }
30
+ # cast: ->(e) { "Money.new(#{e})" }
29
31
  # - :itself opts out — force identity pass-through even when a codec
30
32
  # would otherwise match (rare)
31
33
  # requires: a String or Array of paths emitted as `require`s atop the
@@ -56,23 +58,44 @@ class GraphWeaver::Codegen
56
58
 
57
59
  attr_reader :graphql_name, :type, :requires
58
60
 
59
- def initialize(graphql_name, type:, cast: nil, serialize: nil, requires: nil, coerce: false)
61
+ def initialize(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil)
60
62
  @graphql_name = graphql_name.to_s
61
63
  @klass = type.is_a?(Module) ? type : nil
62
64
  @type = type_name(type)
65
+ # requires: load BEFORE codec probing — the probe method may come
66
+ # from the required file (core Time has no .parse until the "time"
67
+ # stdlib loads)
68
+ @requires = normalize_requires(requires)
63
69
  codec = @klass && CODECS.find { |c| @klass.respond_to?(c.probe) }
64
70
  @cast = normalize_cast(cast, codec&.cast)
65
71
  @serialize = normalize_serialize(serialize, codec&.serialize)
66
- @requires = normalize_requires(requires)
67
72
  @coerce = coerce
68
73
  validate_coerce!
69
74
  end
70
75
 
76
+ # conversions applied to the four convertible built-ins when the
77
+ # global GraphWeaver.auto_coerce is on and no explicit coerce: given
78
+ AUTO_CONVERSIONS = {
79
+ "ID" => :to_s, "String" => :to_s, "Int" => :to_i, "Float" => :to_f,
80
+ }.freeze
81
+
71
82
  def cast(expr) = @cast&.call(expr)
72
83
  def cast? = !@cast.nil?
73
84
  def serialize(expr) = @serialize&.call(expr)
74
85
  def serialize? = !@serialize.nil?
75
- def coerce? = !!@coerce
86
+ def coerce? = !!effective_coerce
87
+
88
+ # Explicit coerce: always wins (false means never). Left unset, the
89
+ # global GraphWeaver.auto_coerce decides — resolved HERE, at
90
+ # generation time, so registration order doesn't matter: convertible
91
+ # built-ins get their conversion, anything with a full cast/serialize
92
+ # pair gets parse-style coercion.
93
+ def effective_coerce
94
+ return @coerce unless @coerce.nil?
95
+ return false unless GraphWeaver.auto_coerce
96
+
97
+ AUTO_CONVERSIONS.fetch(@graphql_name) { (cast? && serialize?) || nil }
98
+ end
76
99
 
77
100
  # The code that normalizes a variable input before it's serialized. Two
78
101
  # shapes: coerce: true parses a raw value into the rich type via the cast
@@ -82,17 +105,17 @@ class GraphWeaver::Codegen
82
105
  # afterward, but is identity for the conversion built-ins, so the
83
106
  # converted value goes on the wire natively (a Float, not "5.0").
84
107
  def coerce_input(expr)
85
- case @coerce
108
+ case effective_coerce
86
109
  when true then "(#{expr}.is_a?(#{@type}) ? #{expr} : #{cast(expr)})"
87
- when Symbol then "#{expr}.#{@coerce}"
110
+ when Symbol then "#{expr}.#{effective_coerce}"
88
111
  end
89
112
  end
90
113
 
91
114
  # the accepted Sorbet type for a coercible variable kwarg
92
115
  def coerce_type
93
- case @coerce
116
+ case effective_coerce
94
117
  when true then "T.any(#{@type}, String)"
95
- when Symbol then CONVERT_INPUTS.fetch(@coerce, "T.untyped")
118
+ when Symbol then CONVERT_INPUTS.fetch(effective_coerce, "T.untyped")
96
119
  end
97
120
  end
98
121
 
@@ -175,9 +198,9 @@ class GraphWeaver::Codegen
175
198
  # the accepted cast:/serialize:/requires: forms. Later registrations
176
199
  # win, so an app can override a built-in (e.g. map Date onto its own
177
200
  # type).
178
- def register_scalar(graphql_name, type:, cast: nil, serialize: nil, requires: nil, coerce: false)
201
+ def register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil)
179
202
  scalar_registry[graphql_name.to_s] =
180
- ScalarType.new(graphql_name, type:, cast:, serialize:, requires:, coerce:)
203
+ ScalarType.new(graphql_name, type, cast:, serialize:, requires:, coerce:)
181
204
  end
182
205
 
183
206
  # The ScalarType for a scalar name; unknown scalars fall back to an
@@ -185,7 +208,7 @@ class GraphWeaver::Codegen
185
208
  # scalars outside the table.
186
209
  def scalar(graphql_name)
187
210
  scalar_registry.fetch(graphql_name.to_s) do
188
- ScalarType.new(graphql_name, type: "T.untyped")
211
+ ScalarType.new(graphql_name, "T.untyped")
189
212
  end
190
213
  end
191
214
 
@@ -201,14 +224,12 @@ class GraphWeaver::Codegen
201
224
  end
202
225
 
203
226
  # Drop every custom registration and restore the built-in scalars — the
204
- # clean slate to reach for between tests, or to undo overrides. Pass
205
- # coerce: true to reload the built-ins with input coercion enabled
206
- # (Float accepts 5/"5" and .to_f's it, etc.), then register your own
207
- # scalars on top — a one-liner alternative to re-registering each
208
- # built-in by hand.
209
- def reset_scalars!(coerce: false)
227
+ # clean slate to reach for between tests, or to undo overrides. (Want
228
+ # the built-ins to coerce loose input? That's GraphWeaver.auto_coerce,
229
+ # resolved at generation time no re-registering.)
230
+ def reset_scalars!
210
231
  clear_scalars!
211
- register_builtin_scalars!(coerce:)
232
+ register_builtin_scalars!
212
233
  self
213
234
  end
214
235
 
@@ -218,19 +239,16 @@ class GraphWeaver::Codegen
218
239
  # matches nothing and leaves them identity — which is exactly why we
219
240
  # can name them with the real class constants. Date deserializes via
220
241
  # ISO-8601 (it *does* define .parse, but we want iso8601 specifically,
221
- # so it's explicit).
222
- #
223
- # coerce: true opts the convertible scalars into input coercion via a
224
- # plain conversion method (to_f/to_i/to_s); the wire value stays native
225
- # (a Float, not "5.0"). Boolean and Date have no lossless one-method
226
- # conversion, so they stay strict either way.
227
- def register_builtin_scalars!(coerce: false)
228
- register_scalar "ID", type: String, coerce: (:to_s if coerce)
229
- register_scalar "String", type: String, coerce: (:to_s if coerce)
230
- register_scalar "Int", type: Integer, coerce: (:to_i if coerce)
231
- register_scalar "Float", type: Float, coerce: (:to_f if coerce)
232
- register_scalar "Boolean", type: "T::Boolean"
233
- register_scalar "Date", type: Date, cast: :iso8601, serialize: :iso8601, requires: "date"
242
+ # so it's explicit). Input coercion is a generation-time concern:
243
+ # GraphWeaver.auto_coerce gives the convertible built-ins their
244
+ # conversion (see ScalarType::AUTO_CONVERSIONS).
245
+ def register_builtin_scalars!
246
+ register_scalar "ID", String
247
+ register_scalar "String", String
248
+ register_scalar "Int", Integer
249
+ register_scalar "Float", Float
250
+ register_scalar "Boolean", "T::Boolean"
251
+ register_scalar "Date", Date, cast: :iso8601, serialize: :iso8601, requires: "date"
234
252
  end
235
253
  end
236
254