graph_weaver 0.0.1 → 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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/CHANGELOG.md +279 -0
  4. data/Gemfile.lock +75 -17
  5. data/Makefile +11 -1
  6. data/NOTES.md +1 -1
  7. data/PLAN.md +85 -22
  8. data/README.md +86 -26
  9. data/docs/cassettes.md +76 -0
  10. data/docs/errors.md +134 -0
  11. data/docs/generated_modules.md +229 -0
  12. data/docs/getting_started.md +134 -0
  13. data/docs/logging.md +33 -0
  14. data/docs/real_world.md +53 -0
  15. data/docs/scalars.md +183 -0
  16. data/docs/testing.md +103 -0
  17. data/docs/transports.md +124 -0
  18. data/graph_weaver.gemspec +10 -1
  19. data/lib/graph_weaver/client.rb +200 -0
  20. data/lib/graph_weaver/codegen/emit.rb +286 -0
  21. data/lib/graph_weaver/codegen/enum_type.rb +149 -0
  22. data/lib/graph_weaver/codegen/nodes.rb +356 -0
  23. data/lib/graph_weaver/codegen/scalar_type.rb +256 -0
  24. data/lib/graph_weaver/codegen.rb +396 -401
  25. data/lib/graph_weaver/errors.rb +322 -0
  26. data/lib/graph_weaver/hints.rb +63 -0
  27. data/lib/graph_weaver/inflect.rb +19 -0
  28. data/lib/graph_weaver/logging.rb +41 -0
  29. data/lib/graph_weaver/railtie.rb +29 -0
  30. data/lib/graph_weaver/response.rb +55 -0
  31. data/lib/graph_weaver/retry.rb +97 -0
  32. data/lib/graph_weaver/rspec.rb +59 -0
  33. data/lib/graph_weaver/schema_loader.rb +208 -8
  34. data/lib/graph_weaver/selection.rb +68 -0
  35. data/lib/graph_weaver/tasks.rb +71 -0
  36. data/lib/graph_weaver/testing/cassette.rb +224 -0
  37. data/lib/graph_weaver/testing/failure.rb +109 -0
  38. data/lib/graph_weaver/testing/fake_client.rb +228 -0
  39. data/lib/graph_weaver/testing/values.rb +98 -0
  40. data/lib/graph_weaver/testing.rb +106 -0
  41. data/lib/graph_weaver/transport/faraday.rb +56 -0
  42. data/lib/graph_weaver/transport/http.rb +87 -0
  43. data/lib/graph_weaver/transport.rb +120 -0
  44. data/lib/graph_weaver/version.rb +1 -1
  45. data/lib/graph_weaver.rb +268 -4
  46. metadata +132 -2
  47. data/lib/graph_weaver/http_executor.rb +0 -31
@@ -0,0 +1,120 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "sorbet-runtime"
6
+
7
+ require_relative "errors"
8
+
9
+ # Base class for the bundled network transports — Transport::HTTP
10
+ # (zero-dependency net/http, loaded by default) and Transport::Faraday
11
+ # (opt-in). A transport speaks GraphQL-over-HTTP and satisfies the
12
+ # same execute(query, variables:) => {"data" => ..., "errors" => ...}
13
+ # contract as a schema class or a fake — anything in a client slot.
14
+ #
15
+ # The base class owns the shared flow — encode the request, reclassify
16
+ # network-level failures as TransportError, raise ServerError on a
17
+ # non-2xx status, parse the body — so a subclass only implements post:
18
+ # take the request body, return [status, body].
19
+ class GraphWeaver::Transport
20
+ extend T::Sig
21
+ extend T::Helpers
22
+ abstract!
23
+
24
+ # the endpoint this transport talks to — recorded into cached schema
25
+ # dumps as provenance (see SchemaLoader.introspect)
26
+ attr_reader :url
27
+
28
+ def execute(query, variables: {})
29
+ # tag pairs this request's log lines (threads interleave), and names
30
+ # the operation so the log says WHICH query, not just the url
31
+ tag = GraphWeaver.logger && GraphWeaver::Transport.log_tag(query)
32
+
33
+ # full query + variables at debug only — they can carry PII
34
+ GraphWeaver.log(:debug) do
35
+ "POST #{url} #{tag} variables=#{JSON.generate(variables)}\n#{GraphWeaver::Transport.truncate_for_log(query)}"
36
+ end
37
+
38
+ encoded = begin
39
+ JSON.generate(query:, variables:)
40
+ rescue JSON::GeneratorError => e
41
+ # a value with no JSON form (NaN, Infinity, binary) — the caller's
42
+ # bug, surfaced under the umbrella instead of a raw JSON:: error
43
+ raise GraphWeaver::Error, "variables are not JSON-serializable: #{e.message}"
44
+ end
45
+
46
+ status, body = begin
47
+ GraphWeaver.log_timed(:debug, "POST #{url} #{tag} completed") do
48
+ post(encoded)
49
+ end
50
+ rescue *GraphWeaver.transport_errors.to_a => e
51
+ # never got a response — DNS, connection refused/reset, TLS, timeout
52
+ raise GraphWeaver::TransportError, "#{e.class}: #{e.message}"
53
+ end
54
+
55
+ GraphWeaver.log(:debug) { "HTTP #{status} #{tag} from #{url} (#{body.to_s.bytesize} bytes)" }
56
+
57
+ parsed = parse_body(body)
58
+
59
+ # reached the server, but it returned a non-2xx status. Per
60
+ # graphql-over-http, routers (Apollo Server/Router) send request
61
+ # errors as 4xx WITH a GraphQL errors body — those flow into the
62
+ # envelope so QueryError machinery sees the structured errors; only
63
+ # a body that isn't GraphQL (proxy pages, HTML 500s) is a ServerError.
64
+ unless (200..299).cover?(status)
65
+ return parsed if parsed.is_a?(Hash) && parsed.key?("errors")
66
+
67
+ raise GraphWeaver::ServerError.new(status:, body: body.to_s)
68
+ end
69
+
70
+ unless parsed
71
+ # a 200 that isn't GraphQL — an HTML error page from a proxy, a
72
+ # captive portal: the server misbehaved, classify it that way
73
+ raise GraphWeaver::ServerError.new(status:, body: "non-JSON response: #{body.to_s[0, 500]}")
74
+ end
75
+
76
+ parsed
77
+ end
78
+
79
+ # the parsed body, or nil when it isn't JSON (a caller's connection may
80
+ # already parse via middleware — pass that through)
81
+ private def parse_body(body)
82
+ return body unless body.is_a?(String)
83
+
84
+ JSON.parse(body)
85
+ rescue JSON::ParserError
86
+ nil
87
+ end
88
+
89
+ # never leak Authorization headers through logs/exceptions — a
90
+ # transport inspects as its class + endpoint, nothing more
91
+ def inspect
92
+ "#<#{self.class.name} url=#{url.inspect}>"
93
+ end
94
+ alias to_s inspect
95
+
96
+ # "[req 3 FilteredPokemon]" — a per-process request id plus the
97
+ # operation name (when the document declares one)
98
+ REQUEST_MUTEX = Mutex.new
99
+
100
+ def self.log_tag(query)
101
+ id = REQUEST_MUTEX.synchronize { @request_count = (@request_count || 0) + 1 }
102
+ name = query[/\A\s*(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/, 1]
103
+ "[req #{id}#{" #{name}" if name}]"
104
+ end
105
+
106
+ # keep debug readable: a 100-line introspection query would drown the
107
+ # log — the INFO introspection line already carries the timing
108
+ LOG_QUERY_LIMIT = 600
109
+ def self.truncate_for_log(query)
110
+ return query if query.length <= LOG_QUERY_LIMIT
111
+
112
+ "#{query[0, LOG_QUERY_LIMIT]}... (truncated, #{query.bytesize} bytes total)"
113
+ end
114
+
115
+ private
116
+
117
+ # POST the JSON body to the endpoint; return [status code, raw body].
118
+ sig { abstract.params(body: String).returns([Integer, T.untyped]) }
119
+ def post(body); end
120
+ end
@@ -1,3 +1,3 @@
1
1
  module GraphWeaver
2
- VERSION = "0.0.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/graph_weaver.rb CHANGED
@@ -1,11 +1,275 @@
1
1
  require "graphql"
2
2
  require "sorbet-runtime"
3
3
 
4
+ require_relative "graph_weaver/logging"
5
+ require_relative "graph_weaver/errors"
6
+ require_relative "graph_weaver/hints"
7
+ require_relative "graph_weaver/response"
8
+ require_relative "graph_weaver/inflect"
4
9
  require_relative "graph_weaver/codegen"
5
- require_relative "graph_weaver/http_executor"
10
+ require_relative "graph_weaver/client"
11
+ require_relative "graph_weaver/transport/http"
12
+ require_relative "graph_weaver/retry"
6
13
  require_relative "graph_weaver/schema_loader"
7
14
  require_relative "graph_weaver/version"
15
+ require_relative "graph_weaver/railtie" if defined?(::Rails::Railtie)
8
16
 
9
- # opt-in: require "graph_weaver/directive_defaults_patch" to fix
10
- # graphql-ruby dropping directive argument defaults when loading SDL
11
- # (needed for Apollo supergraph SDL until rmosolgo/graphql-ruby#5659 ships)
17
+ # opt-in extras:
18
+ # require "graph_weaver/transport/faraday" # Faraday transport
19
+ # require "graph_weaver/directive_defaults_patch" # fix graphql-ruby
20
+ # dropping directive argument defaults when loading SDL (needed for
21
+ # Apollo supergraph SDL until rmosolgo/graphql-ruby#5659 ships)
22
+ module GraphWeaver
23
+ class << self
24
+ # A client for one GraphQL server — transport, schema, and scoped
25
+ # scalars in one object (see Client):
26
+ #
27
+ # github = GraphWeaver.new("https://api.github.com/graphql", auth: token, cache: true)
28
+ # RepoQuery = github.parse("queries/repo.graphql")
29
+ #
30
+ # The first argument is a url or any schema source (a live schema
31
+ # class, or a path/SDL/introspection dump).
32
+ def new(source, **options, &middleware)
33
+ Client.new(source, **options, &middleware)
34
+ end
35
+
36
+ # The app's default client — how generated modules find their server:
37
+ #
38
+ # GraphWeaver.client = GraphWeaver.new(url, auth: token)
39
+ #
40
+ # Accepts a Client or anything satisfying the execute contract (a
41
+ # schema class, a fake — testing's auto_fake swaps one in per
42
+ # example). Generated modules resolve per call -> per module
43
+ # (MyQuery.client=) -> baked constant -> here.
44
+ attr_accessor :client
45
+
46
+ # the default client, when one is required
47
+ def client!
48
+ @client or raise Error, "no client configured — set GraphWeaver.client= or pass a client"
49
+ end
50
+
51
+ # The transport behind a client-or-transport value: a Client resolves
52
+ # to its own transport, anything else already speaks execute.
53
+ # Generated modules call this on every execute, so any slot in the
54
+ # resolution chain can hold either kind.
55
+ def resolve_transport(target)
56
+ target.is_a?(Client) ? target.transport! : target
57
+ end
58
+
59
+ # Conventional locations, factory_bot-style: used as defaults by
60
+ # generate!, verify_generated!, load_generated!, and the rake tasks;
61
+ # override the accessors or pass paths.
62
+ attr_writer :queries_path, :generated_path, :schema_path
63
+
64
+ def queries_path = @queries_path || "app/graphql/queries"
65
+ def generated_path = @generated_path || "app/graphql/generated"
66
+ def schema_path = @schema_path || "app/graphql/schema.json"
67
+
68
+ # Generate every .graphql query in a directory into checked-in Ruby
69
+ # files. Paths default to the conventions above; schema: defaults to
70
+ # the dump at schema_path (any supported extension):
71
+ #
72
+ # GraphWeaver.generate! # queries_path -> generated_path
73
+ #
74
+ # person.graphql => person_query.rb defining PersonQuery. Returns the
75
+ # written paths. Pair with a freshness spec (docs/generated_modules.md).
76
+ def generate!(schema: nil, queries: queries_path, output: generated_path, client: nil)
77
+ schema ||= locate_schema!
78
+ FileUtils.mkdir_p(output)
79
+
80
+ each_query(queries, schema:, client:).map do |base, source|
81
+ target = File.join(output, "#{base}_query.rb")
82
+ File.write(target, source)
83
+ log(:info) { "generated #{target}" }
84
+ target
85
+ end
86
+ end
87
+
88
+ # The freshness guard: raise unless every generated file matches what
89
+ # the current schema + queries + scalar registrations would produce.
90
+ # One line in a spec, or `rake graph_weaver:verify` in CI:
91
+ #
92
+ # it "generated queries are current" do
93
+ # GraphWeaver.verify_generated!
94
+ # end
95
+ def verify_generated!(schema: nil, queries: queries_path, output: generated_path, client: nil)
96
+ schema ||= locate_schema!
97
+ stale = each_query(queries, schema:, client:).filter_map do |base, source|
98
+ target = File.join(output, "#{base}_query.rb")
99
+ target unless File.exist?(target) && File.read(target) == source
100
+ end
101
+
102
+ unless stale.empty?
103
+ raise Error, "stale generated queries — regenerate (rake graph_weaver:generate): #{stale.join(", ")}"
104
+ end
105
+
106
+ true
107
+ end
108
+
109
+ # Load the generated modules — one line in an initializer or spec
110
+ # helper (loading happens only when you call this; skip it and
111
+ # require files yourself if you'd rather):
112
+ #
113
+ # GraphWeaver.load_generated!
114
+ #
115
+ # In Rails, prefer this over autoloading: Zeitwerk would expect
116
+ # Generated::PersonQuery from generated/person_query.rb, and
117
+ # generated code only changes on regeneration anyway (restart, like
118
+ # a schema migration).
119
+ def load_generated!(path = generated_path)
120
+ files = Dir[File.join(path, "**/*.rb")].sort
121
+ files.each { |file| require File.expand_path(file) }
122
+ log(:info) { "loaded #{files.size} generated module(s) from #{path}" }
123
+ files
124
+ end
125
+
126
+ # the conventional schema dump, required
127
+ def locate_schema!
128
+ SchemaLoader.locate or raise Error,
129
+ "no schema dump at #{schema_path} (.json/.graphql/.gql) — pass schema:, or cache one: GraphWeaver.new(url, cache: true).schema"
130
+ end
131
+ private :locate_schema!
132
+
133
+ # (base, generated_source) per .graphql file in a directory
134
+ def each_query(queries, schema:, client:)
135
+ Dir[File.join(queries, "*.graphql")].sort.map do |path|
136
+ base = File.basename(path, ".graphql")
137
+ source = Codegen.generate(
138
+ schema:,
139
+ query: File.read(path),
140
+ module_name: "#{Inflect.camelize(base)}Query",
141
+ client:,
142
+ )
143
+ [base, source]
144
+ end
145
+ end
146
+ private :each_query
147
+
148
+ # Default input coercion for scalars that don't say coerce: themselves,
149
+ # resolved lazily at generation time (so set it any time before you
150
+ # generate — no reset_scalars! ordering dance):
151
+ #
152
+ # GraphWeaver.auto_coerce = true
153
+ #
154
+ # Convertible built-ins take their conversion (Int accepts 5/"5"),
155
+ # and any scalar with a full cast/serialize pair (Date, your Money)
156
+ # accepts its raw wire form. An explicit coerce: true/false/Symbol on
157
+ # a registration always wins.
158
+ attr_accessor :auto_coerce
159
+
160
+ # Teach the generator how a GraphQL custom scalar deserializes into a
161
+ # rich Ruby object (and serializes back onto the wire when used as a
162
+ # variable):
163
+ #
164
+ # GraphWeaver.register_scalar("Money", Money, requires: "bigdecimal")
165
+ #
166
+ # A field typed `Money` then generates `const :price, T.nilable(Money)`
167
+ # and casts with `Money.parse(...)` in from_h. Pass a real class as
168
+ # type: and cast:/serialize: are inferred from it — .parse/#to_s, or
169
+ # .load/.dump — by probing the deserialize side (see ScalarType::CODECS).
170
+ # Override with a Symbol method name (safest — no string to misspell), a
171
+ # Proc(expr) => code string, or :itself to force pass-through. requires:
172
+ # (a String or Array) names files the generated code needs — validated,
173
+ # and actually required to confirm it resolves when type: is a real class.
174
+ # coerce: true makes a variable of this scalar accept the value OR its
175
+ # raw input (e.g. "12.00"), running the latter through the cast before
176
+ # serializing — it raises on bad input, so some safety survives. Built-in
177
+ # scalars are pre-registered the same way, so this also overrides them.
178
+ # Call before generating.
179
+ def register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil)
180
+ Codegen.register_scalar(graphql_name, type, cast:, serialize:, requires:, coerce:)
181
+ end
182
+
183
+ # Map a GraphQL enum onto an app-owned T::Enum, so generated code
184
+ # speaks YOUR enum — casting wire values in, serializing members out:
185
+ #
186
+ # GraphWeaver.register_enum("Species", PetKind)
187
+ #
188
+ # The mapping is inferred by name ("CAT" <-> PetKind::Cat); map: pins
189
+ # renames, fallback: absorbs unknown wire values on cast (inputs stay
190
+ # strict), requires: names files the generated code should require.
191
+ # Generation fails naming any schema value that doesn't resolve —
192
+ # exhaustiveness checked ahead of runtime. Global; client.register_enum
193
+ # scopes to one client.
194
+ def register_enum(graphql_name, type, map: nil, fallback: nil, requires: nil)
195
+ Codegen.register_enum(graphql_name, type, map:, fallback:, requires:)
196
+ end
197
+
198
+ # Bulk, inference-only form: register_enums("Species" => PetKind, ...)
199
+ def register_enums(mappings)
200
+ Codegen.register_enums(mappings)
201
+ end
202
+
203
+ # Include app-owned helper modules into every struct generated from a
204
+ # GraphQL type — derived values live as methods next to the honest
205
+ # wire data, and srb tc checks them against each query's selection:
206
+ #
207
+ # GraphWeaver.register_type("Pet", PetHelpers)
208
+ #
209
+ # Or build the mixin inline with a block (module_eval'd into an
210
+ # auto-named module — quick, but invisible to srb tc):
211
+ #
212
+ # GraphWeaver.register_type("Pet") do
213
+ # def display_name = "#{name} the pet"
214
+ # end
215
+ #
216
+ # Additive (repeated and client-scoped registrations stack). Global;
217
+ # client.register_type scopes to one client.
218
+ def register_type(graphql_name, *mixins, requires: nil, &block)
219
+ Codegen.register_type(graphql_name, *mixins, requires:, &block)
220
+ end
221
+
222
+ # Restore the built-in scalars, dropping every custom registration —
223
+ # the clean slate to reach for between tests or to undo overrides.
224
+ # (Coercible built-ins are auto_coerce's job, not a reset flavor.)
225
+ def reset_scalars!
226
+ Codegen.reset_scalars!
227
+ end
228
+
229
+ # Empty the scalar registry entirely, built-ins included (see
230
+ # reset_scalars! to restore the defaults).
231
+ def clear_scalars!
232
+ Codegen.clear_scalars!
233
+ end
234
+
235
+ # Parse a query into a typed query module:
236
+ #
237
+ # PersonQuery = GraphWeaver.parse(schema:, query: "queries/person.graphql")
238
+ #
239
+ # query is a .graphql/.gql path (module name derived from the file
240
+ # name) or a raw query string (name derived from the operation name,
241
+ # falling back to "Query" for anonymous operations — collisions are
242
+ # impossible since each parse gets its own container). Pass name: to
243
+ # override, client: to bake the module's default client/transport.
244
+ def parse(schema:, query:, name: nil, client: nil, scalars: nil, enums: nil, types: nil)
245
+ if query.end_with?(".graphql", ".gql")
246
+ name ||= "#{Inflect.camelize(File.basename(query, ".*"))}Query"
247
+ query = File.read(query)
248
+ end
249
+
250
+ Codegen.parse(schema:, query:, module_name: name, client:, scalars:, enums:, types:)
251
+ end
252
+
253
+ # One-shot dynamic execution — a throwaway client, no build step:
254
+ #
255
+ # GraphWeaver.execute(schema, "query($id: ID!) { ... }", id: "1") # => Response
256
+ # GraphWeaver.execute!(url, "query { viewer { login } }") # => Result (or raise)
257
+ #
258
+ # The first argument is a url or schema source, exactly as
259
+ # GraphWeaver.new; this is Client#execute on a client you don't keep.
260
+ # (A url source introspects the schema on every call — keep a client
261
+ # for more than one query.) Variables are plain kwargs, as on a
262
+ # generated module (nothing reserved). execute returns the
263
+ # Response envelope, execute! the typed result, raising QueryError on
264
+ # top-level errors.
265
+ def execute(source, query, **variables)
266
+ client = source.is_a?(Client) ? source : Client.new(source)
267
+ client.execute(query, **variables)
268
+ end
269
+
270
+ # execute + data! — the typed result, or a raised QueryError. See execute.
271
+ def execute!(source, query, **variables)
272
+ execute(source, query, **variables).data!
273
+ end
274
+ end
275
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graph_weaver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pepper
@@ -37,6 +37,34 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: apollo-federation
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: bigdecimal
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
40
68
  - !ruby/object:Gem::Dependency
41
69
  name: debug
42
70
  requirement: !ruby/object:Gem::Requirement
@@ -51,6 +79,62 @@ dependencies:
51
79
  - - ">="
52
80
  - !ruby/object:Gem::Version
53
81
  version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: faker
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: faraday
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rake
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: redcarpet
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
54
138
  - !ruby/object:Gem::Dependency
55
139
  name: rspec
56
140
  requirement: !ruby/object:Gem::Requirement
@@ -121,12 +205,27 @@ dependencies:
121
205
  - - ">="
122
206
  - !ruby/object:Gem::Version
123
207
  version: '0'
208
+ - !ruby/object:Gem::Dependency
209
+ name: yard
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ type: :development
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
124
222
  description: A typed GraphQL client for Ruby — generate Sorbet T::Structs from queries,
125
223
  with federation, extensibility, and testing in mind
126
224
  executables: []
127
225
  extensions: []
128
226
  extra_rdoc_files: []
129
227
  files:
228
+ - ".yardopts"
130
229
  - CHANGELOG.md
131
230
  - Gemfile
132
231
  - Gemfile.lock
@@ -135,12 +234,43 @@ files:
135
234
  - NOTES.md
136
235
  - PLAN.md
137
236
  - README.md
237
+ - docs/cassettes.md
238
+ - docs/errors.md
239
+ - docs/generated_modules.md
240
+ - docs/getting_started.md
241
+ - docs/logging.md
242
+ - docs/real_world.md
243
+ - docs/scalars.md
244
+ - docs/testing.md
245
+ - docs/transports.md
138
246
  - graph_weaver.gemspec
139
247
  - lib/graph_weaver.rb
248
+ - lib/graph_weaver/client.rb
140
249
  - lib/graph_weaver/codegen.rb
250
+ - lib/graph_weaver/codegen/emit.rb
251
+ - lib/graph_weaver/codegen/enum_type.rb
252
+ - lib/graph_weaver/codegen/nodes.rb
253
+ - lib/graph_weaver/codegen/scalar_type.rb
141
254
  - lib/graph_weaver/directive_defaults_patch.rb
142
- - lib/graph_weaver/http_executor.rb
255
+ - lib/graph_weaver/errors.rb
256
+ - lib/graph_weaver/hints.rb
257
+ - lib/graph_weaver/inflect.rb
258
+ - lib/graph_weaver/logging.rb
259
+ - lib/graph_weaver/railtie.rb
260
+ - lib/graph_weaver/response.rb
261
+ - lib/graph_weaver/retry.rb
262
+ - lib/graph_weaver/rspec.rb
143
263
  - lib/graph_weaver/schema_loader.rb
264
+ - lib/graph_weaver/selection.rb
265
+ - lib/graph_weaver/tasks.rb
266
+ - lib/graph_weaver/testing.rb
267
+ - lib/graph_weaver/testing/cassette.rb
268
+ - lib/graph_weaver/testing/failure.rb
269
+ - lib/graph_weaver/testing/fake_client.rb
270
+ - lib/graph_weaver/testing/values.rb
271
+ - lib/graph_weaver/transport.rb
272
+ - lib/graph_weaver/transport/faraday.rb
273
+ - lib/graph_weaver/transport/http.rb
144
274
  - lib/graph_weaver/version.rb
145
275
  homepage: https://github.com/dpep/graph_weaver
146
276
  licenses:
@@ -1,31 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require "json"
5
- require "sorbet-runtime"
6
- require "net/http"
7
- require "uri"
8
-
9
- # Minimal HTTP transport satisfying the generated modules' executor
10
- # interface: execute(query, variables:) => {"data" => ..., "errors" => ...}
11
- class GraphWeaver::HttpExecutor
12
- def initialize(url, headers: {})
13
- @uri = URI(url)
14
- @headers = headers
15
- end
16
-
17
- def execute(query, variables: {})
18
- request = Net::HTTP::Post.new(@uri, { "Content-Type" => "application/json" }.merge(@headers))
19
- request.body = JSON.generate(query:, variables:)
20
-
21
- response = Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https") do |http|
22
- http.request(request)
23
- end
24
-
25
- unless response.is_a?(Net::HTTPSuccess)
26
- raise "HTTP #{response.code}: #{response.body}"
27
- end
28
-
29
- JSON.parse(T.must(response.body))
30
- end
31
- end