Class: GraphWeaver::Transport
- Inherits:
-
Object
- Object
- GraphWeaver::Transport
- Extended by:
- T::Helpers, T::Sig
- Defined in:
- lib/graph_weaver/transport.rb,
lib/graph_weaver/transport/http.rb,
lib/graph_weaver/transport/faraday.rb
Overview
Base class for the bundled network transports — Transport::HTTP (zero-dependency net/http, loaded by default) and Transport::Faraday (opt-in). A transport speaks GraphQL-over-HTTP and satisfies the same execute(query, variables:) => => ..., "errors" => ... contract as a schema class or a fake — anything in a client slot.
The base class owns the shared flow — encode the request, reclassify network-level failures as TransportError, raise ServerError on a non-2xx status, parse the body — so a subclass only implements post: take the request body, return [status, body].
Defined Under Namespace
Constant Summary collapse
- REQUEST_MUTEX =
"[req 3 FilteredPokemon]" — a per-process request id plus the operation name (when the document declares one)
Mutex.new
- LOG_QUERY_LIMIT =
keep debug readable: a 100-line introspection query would drown the log — the INFO introspection line already carries the timing
600
Instance Attribute Summary collapse
-
#url ⇒ Object
readonly
the endpoint this transport talks to — recorded into cached schema dumps as provenance (see SchemaLoader.introspect).
Class Method Summary collapse
Instance Method Summary collapse
- #execute(query, variables: {}) ⇒ Object
-
#inspect ⇒ Object
(also: #to_s)
never leak Authorization headers through logs/exceptions — a transport inspects as its class + endpoint, nothing more.
Instance Attribute Details
#url ⇒ Object (readonly)
the endpoint this transport talks to — recorded into cached schema dumps as provenance (see SchemaLoader.introspect)
26 27 28 |
# File 'lib/graph_weaver/transport.rb', line 26 def url @url end |
Class Method Details
.log_tag(query) ⇒ Object
100 101 102 103 104 |
# File 'lib/graph_weaver/transport.rb', line 100 def self.log_tag(query) id = REQUEST_MUTEX.synchronize { @request_count = (@request_count || 0) + 1 } name = query[/\A\s*(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/, 1] "[req #{id}#{" #{name}" if name}]" end |
.truncate_for_log(query) ⇒ Object
109 110 111 112 113 |
# File 'lib/graph_weaver/transport.rb', line 109 def self.truncate_for_log(query) return query if query.length <= LOG_QUERY_LIMIT "#{query[0, LOG_QUERY_LIMIT]}... (truncated, #{query.bytesize} bytes total)" end |
Instance Method Details
#execute(query, variables: {}) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/graph_weaver/transport.rb', line 28 def execute(query, variables: {}) # tag pairs this request's log lines (threads interleave), and names # the operation so the log says WHICH query, not just the url tag = GraphWeaver.logger && GraphWeaver::Transport.log_tag(query) # full query + variables at debug only — they can carry PII GraphWeaver.log(:debug) do "POST #{url} #{tag} variables=#{JSON.generate(variables)}\n#{GraphWeaver::Transport.truncate_for_log(query)}" end encoded = begin JSON.generate(query:, variables:) rescue JSON::GeneratorError => e # a value with no JSON form (NaN, Infinity, binary) — the caller's # bug, surfaced under the umbrella instead of a raw JSON:: error raise GraphWeaver::Error, "variables are not JSON-serializable: #{e.}" end status, body = begin GraphWeaver.log_timed(:debug, "POST #{url} #{tag} completed") do post(encoded) end rescue *GraphWeaver.transport_errors.to_a => e # never got a response — DNS, connection refused/reset, TLS, timeout raise GraphWeaver::TransportError, "#{e.class}: #{e.}" end GraphWeaver.log(:debug) { "HTTP #{status} #{tag} from #{url} (#{body.to_s.bytesize} bytes)" } parsed = parse_body(body) # reached the server, but it returned a non-2xx status. Per # graphql-over-http, routers (Apollo Server/Router) send request # errors as 4xx WITH a GraphQL errors body — those flow into the # envelope so QueryError machinery sees the structured errors; only # a body that isn't GraphQL (proxy pages, HTML 500s) is a ServerError. unless (200..299).cover?(status) return parsed if parsed.is_a?(Hash) && parsed.key?("errors") raise GraphWeaver::ServerError.new(status:, body: body.to_s) end unless parsed # a 200 that isn't GraphQL — an HTML error page from a proxy, a # captive portal: the server misbehaved, classify it that way raise GraphWeaver::ServerError.new(status:, body: "non-JSON response: #{body.to_s[0, 500]}") end parsed end |
#inspect ⇒ Object Also known as: to_s
never leak Authorization headers through logs/exceptions — a transport inspects as its class + endpoint, nothing more
91 92 93 |
# File 'lib/graph_weaver/transport.rb', line 91 def inspect "#<#{self.class.name} url=#{url.inspect}>" end |