Module: Restless::Injection
- Defined in:
- lib/restless/injection.rb
Overview
CONTRACT.md section 10. What the SDK adds to the customer's own responses.
Class Method Summary collapse
-
.apply_body(body, content_type, debug) ⇒ Object
INJECT-003.
-
.build(status:, request_id:, prefix: nil, recovery: nil, method: nil, path: nil, portal_url: nil) ⇒ Object
INJECT-001..004, INJECT-006.
-
.headers(request_id:, prefix: nil, portal_url: nil) ⇒ Object
INJECT-002.
-
.recovery_slug(method = nil, path = nil) ⇒ Object
INJECT-005.
Class Method Details
.apply_body(body, content_type, debug) ⇒ Object
INJECT-003. Merge debug into the response body ONLY when the body is a
JSON OBJECT (not an array, not a scalar) and the content type says JSON.
Returns the original body untouched on any parse failure: SAFETY-001 outranks everything here.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/restless/injection.rb', line 98 def apply_body(body, content_type, debug) return body if body.nil? || body.empty? || debug.nil? return body unless Text.full_lower(content_type.to_s).include?("application/json") begin parsed = JSON.parse(body, max_nesting: false) rescue StandardError return body end return body unless parsed.is_a?(Hash) JSON.generate(parsed.merge("debug" => debug)) rescue StandardError body end |
.build(status:, request_id:, prefix: nil, recovery: nil, method: nil, path: nil, portal_url: nil) ⇒ Object
INJECT-001..004, INJECT-006. Returns the headers to set plus the debug
object to merge into a JSON body (nil when there is nothing to merge).
portal_url is the project's public portal origin, published by the
server. It is NOT the ingest base URL, which serves /v1/* and would
404 both paths, and there is deliberately no fallback to it: with no
portal origin we emit x-debug alone. A caller cannot tell a broken URL
from a missing one, and one fetched 404 teaches an agent to stop
following the link.
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 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/restless/injection.rb', line 54 def build(status:, request_id:, prefix: nil, recovery: nil, method: nil, path: nil, portal_url: nil) hdrs = headers(request_id: request_id, prefix: prefix, portal_url: portal_url) # INJECT-001. The body object is 4xx/5xx only: a successful body is the # caller's data, not ours to reshape. With no portal origin there is no # URL to put in one either (INJECT-006). return { headers: hdrs, debug: nil } if status < 400 || portal_url.nil? || portal_url.empty? log_url = hdrs["x-log-url"] debug_cmd = hdrs["x-debug"] # Per-request "dig-in" URL the calling agent (often an AI) can fetch for # concrete next steps. Deliberately LEGIBLE: it ends in `<slug>.md` so it # reads as documentation rather than a tracking blob. The first segment # is the same public request id already in `debug.log`, so the dashboard # can correlate the follow-up without any new tracking token. slug = recovery_slug(method, path) dig_in = "For the accepted parameters and next steps, " \ "fetch #{portal_url}/p/#{request_id}/#{slug}.md" # INJECT-004: a cached recovery message precedes the dig-in line, # separated by a blank line. recovery_text = if recovery.nil? || recovery.empty? dig_in else "#{recovery}\n\n#{dig_in}" end { headers: hdrs, debug: { "log" => log_url, "cli" => debug_cmd, "recovery" => recovery_text } } end |
.headers(request_id:, prefix: nil, portal_url: nil) ⇒ Object
INJECT-002. The debug response headers, which ship on every status.
x-log-url is omitted with no portal origin; x-debug carries no URL,
so it always ships.
38 39 40 41 42 43 |
# File 'lib/restless/injection.rb', line 38 def headers(request_id:, prefix: nil, portal_url: nil) display = RequestId.format_request_id(request_id, prefix) out = { "x-debug" => "npx api debug #{display}" } out["x-log-url"] = "#{portal_url}/logs/#{request_id}" unless portal_url.nil? || portal_url.empty? out end |
.recovery_slug(method = nil, path = nil) ⇒ Object
INJECT-005. Legible URL slug for the recovery dig-in path, derived from
method + route pattern: GET /car/{id} becomes get-car-id.
The server resolves it back to an OpenAPI operation by matching the same
scheme, so this MUST stay in sync with the app's recoverySlug
(INJECT-007).
19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/restless/injection.rb', line 19 def recovery_slug(method = nil, path = nil) m = Text.full_lower(method.to_s) # JavaScript's `trim` strips the PRIM-002 set; Ruby's `strip` strips a # different one in both directions. p = Text.ws_trim(path.to_s) return "unknown" if m.empty? || p.empty? flat = p.gsub(%r{[/{}:]+}, "-") .gsub(/[^a-zA-Z0-9\-]/, "") .gsub(/-+/, "-") # `\A` / `\z`, not `^` / `$`: Ruby's are line anchors (PRIM-005). flat = flat.sub(/\A-/, "").sub(/-\z/, "") flat.empty? ? m : "#{m}-#{flat}" end |