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
103 104 105 106 107 |
# File 'lib/graph_weaver/transport.rb', line 103 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
112 113 114 115 116 |
# File 'lib/graph_weaver/transport.rb', line 112 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 78 79 80 |
# 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) # only a body carrying actual GraphQL errors flows through — a 4xx with # `"errors": null` (or []) isn't a structured error response, so the # status stays the signal return parsed if parsed.is_a?(Hash) && parsed["errors"].is_a?(Array) && parsed["errors"].any? raise GraphWeaver::ServerError.new(status:, body: body.to_s) end unless parsed.is_a?(Hash) # a 200 that isn't a GraphQL object — an HTML error page from a proxy, a # captive portal, or a bare JSON array/string: the server misbehaved raise GraphWeaver::ServerError.new(status:, body: "non-GraphQL 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
94 95 96 |
# File 'lib/graph_weaver/transport.rb', line 94 def inspect "#<#{self.class.name} url=#{url.inspect}>" end |