graph_weaver 0.5.0 → 0.6.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +537 -0
  3. data/Gemfile.lock +19 -19
  4. data/README.md +74 -53
  5. data/docs/cassettes.md +29 -4
  6. data/docs/editors.md +3 -1
  7. data/docs/errors.md +75 -16
  8. data/docs/federation.md +206 -155
  9. data/docs/generated_modules.md +223 -166
  10. data/docs/getting_started.md +106 -82
  11. data/docs/logging.md +35 -5
  12. data/docs/scalars.md +119 -24
  13. data/docs/testing.md +196 -155
  14. data/docs/transports.md +47 -19
  15. data/docs/upgrading.md +243 -22
  16. data/graph_weaver.gemspec +16 -2
  17. data/lib/generators/graph_weaver/install_generator.rb +31 -16
  18. data/lib/graph_weaver/client.rb +52 -15
  19. data/lib/graph_weaver/codegen/aliases.rb +15 -8
  20. data/lib/graph_weaver/codegen/emit.rb +107 -42
  21. data/lib/graph_weaver/codegen/enum_type.rb +4 -3
  22. data/lib/graph_weaver/codegen/nodes.rb +42 -21
  23. data/lib/graph_weaver/codegen/scalar_type.rb +87 -83
  24. data/lib/graph_weaver/codegen/type_helpers.rb +2 -3
  25. data/lib/graph_weaver/codegen.rb +382 -105
  26. data/lib/graph_weaver/coerce.rb +113 -0
  27. data/lib/graph_weaver/errors.rb +57 -13
  28. data/lib/graph_weaver/federation.rb +10 -22
  29. data/lib/graph_weaver/hints.rb +76 -2
  30. data/lib/graph_weaver/in_process.rb +11 -8
  31. data/lib/graph_weaver/inflect.rb +2 -0
  32. data/lib/graph_weaver/input_struct.rb +115 -12
  33. data/lib/graph_weaver/internal/overrides.rb +101 -0
  34. data/lib/graph_weaver/internal/planner.rb +868 -0
  35. data/lib/graph_weaver/internal/schemas.rb +50 -0
  36. data/lib/graph_weaver/internal/selection.rb +127 -0
  37. data/lib/graph_weaver/{testing → internal}/subgraphs.rb +45 -43
  38. data/lib/graph_weaver/internal/values.rb +181 -0
  39. data/lib/graph_weaver/internal.rb +206 -0
  40. data/lib/graph_weaver/logging.rb +108 -20
  41. data/lib/graph_weaver/parsing.rb +6 -13
  42. data/lib/graph_weaver/query_module.rb +2 -0
  43. data/lib/graph_weaver/railtie.rb +113 -14
  44. data/lib/graph_weaver/representation.rb +30 -2
  45. data/lib/graph_weaver/response.rb +15 -0
  46. data/lib/graph_weaver/retry.rb +54 -22
  47. data/lib/graph_weaver/rspec.rb +63 -18
  48. data/lib/graph_weaver/schema_diff.rb +293 -0
  49. data/lib/graph_weaver/schema_loader.rb +126 -35
  50. data/lib/graph_weaver/tasks.rb +88 -36
  51. data/lib/graph_weaver/testing/cassette.rb +131 -78
  52. data/lib/graph_weaver/testing/coverage.rb +11 -15
  53. data/lib/graph_weaver/testing/failure.rb +14 -8
  54. data/lib/graph_weaver/testing/fake_client.rb +253 -60
  55. data/lib/graph_weaver/testing/fake_subgraph.rb +19 -8
  56. data/lib/graph_weaver/testing/router.rb +147 -840
  57. data/lib/graph_weaver/testing.rb +40 -83
  58. data/lib/graph_weaver/transport/faraday.rb +1 -1
  59. data/lib/graph_weaver/transport/http.rb +29 -12
  60. data/lib/graph_weaver/transport.rb +11 -34
  61. data/lib/graph_weaver/version.rb +1 -1
  62. data/lib/graph_weaver.rb +221 -118
  63. metadata +17 -13
  64. data/CLAUDE.md +0 -161
  65. data/DECISIONS.md +0 -309
  66. data/Makefile +0 -23
  67. data/NOTES.md +0 -182
  68. data/PLAN.md +0 -115
  69. data/REVIEW.md +0 -946
  70. data/lib/graph_weaver/schemas.rb +0 -46
  71. data/lib/graph_weaver/selection.rb +0 -120
  72. data/lib/graph_weaver/testing/values.rb +0 -98
@@ -0,0 +1,101 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../internal"
5
+
6
+ module GraphWeaver
7
+ module Internal
8
+ # What a pin is: the key it may be written under, and how its value is
9
+ # read. Both doors need the answer — Testing.configure checks the
10
+ # suite-wide ones while the block that set them is still on the stack,
11
+ # and a fake built with its own checks them then.
12
+ module Overrides
13
+ class << self
14
+ # A pin key names something in the schema: a type ("Money",
15
+ # "Person"), a "Type.field" coordinate, or a bare field name
16
+ # matching that field on any type. Anything else is a typo that
17
+ # would silently fabricate random data instead of pinning a value.
18
+ def validate!(schema, overrides)
19
+ overrides.each do |key, value|
20
+ validate_arity!(key, value)
21
+ validate_key!(schema, key.to_s)
22
+ end
23
+ end
24
+
25
+ # A pin that's a proc is handed the seeded Random when it takes one
26
+ # and called bare when it doesn't, so a varying pin still reproduces
27
+ # under `rspec --seed`.
28
+ def resolve(value, rng)
29
+ return value unless value.is_a?(Proc)
30
+
31
+ value.arity.zero? ? value.call : value.call(rng)
32
+ end
33
+
34
+ private
35
+
36
+ # A proc taking anything else can't be called at fabrication time,
37
+ # and the ArgumentError it would raise there names no pin.
38
+ def validate_arity!(key, value)
39
+ return unless value.is_a?(Proc) && value.arity > 1
40
+
41
+ raise GraphWeaver::Error, "the pin for #{key.inspect} takes no arguments, or one — the " \
42
+ "seeded Random (-> (rng) { ... }); this one takes #{value.arity}"
43
+ end
44
+
45
+ def validate_key!(schema, key)
46
+ type_name, field_name = key.split(".", 2)
47
+ # introspection fields (__typename) are real but absent from #fields
48
+ return if (field_name || type_name).start_with?("__")
49
+
50
+ if field_name.nil?
51
+ return if (type = schema.get_type(type_name)) && pinnable!(schema, key, type)
52
+
53
+ known = field_names(schema)
54
+ return if known.include?(type_name)
55
+
56
+ # a leading capital names a type, as it does wherever a pin is written
57
+ dictionary = type_name.match?(/\A[A-Z]/) ? schema.types.keys : known
58
+ bad!(key, "matches no type or field in this schema", dictionary, type_name)
59
+ end
60
+
61
+ type = schema.get_type(type_name)
62
+ unless type.respond_to?(:fields)
63
+ bad!(key, "names no object type in this schema", schema.types.keys, type_name)
64
+ end
65
+ return if type.fields.key?(field_name)
66
+
67
+ bad!(key, "is not a field of #{type_name}", type.fields.keys, field_name)
68
+ end
69
+
70
+ # A type pin says what every value of that type is, and the fake only
71
+ # ever holds a concrete one: at an interface or union the walk has
72
+ # already picked a member, so a pin keyed on the abstract name would
73
+ # match nothing and leave the example green.
74
+ def pinnable!(schema, key, type)
75
+ return true if %w[SCALAR ENUM OBJECT].include?(type.kind.name)
76
+
77
+ advice = if type.kind.abstract?
78
+ members = schema.possible_types(type).map { |member| member.graphql_name.inspect }.sort
79
+ "pin the concrete type — #{members.join(", ")}"
80
+ else
81
+ "only output types are fabricated"
82
+ end
83
+ raise GraphWeaver::Error, "override key #{key.inspect} names #{type.kind.name.downcase.tr("_", " ")} " \
84
+ "#{type.graphql_name}, and a pin fabricates a scalar, enum or object: #{advice}"
85
+ end
86
+
87
+ def bad!(key, problem, dictionary, term)
88
+ suggestion = Util.did_you_mean(dictionary, term)
89
+ hint = suggestion ? " — did you mean '#{suggestion}'?" : ""
90
+ raise GraphWeaver::Error, "override key #{key.inspect} #{problem}#{hint}"
91
+ end
92
+
93
+ # Every output field name in the schema — walked only when a bare key
94
+ # asks for it.
95
+ def field_names(schema)
96
+ schema.types.each_value.flat_map { |type| type.respond_to?(:fields) ? type.fields.keys : [] }.uniq
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end