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:, base_url:, prefix: nil, recovery: nil, method: nil, path: nil, docs_url: nil) ⇒ Object
INJECT-001..004, INJECT-006.
-
.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.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/restless/injection.rb', line 83 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:, base_url:, prefix: nil, recovery: nil, method: nil, path: nil, docs_url: nil) ⇒ Object
INJECT-001..004, INJECT-006. Returns the headers to set plus the debug
object to merge into a JSON body, or nil when nothing should be injected.
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 |
# File 'lib/restless/injection.rb', line 36 def build(status:, request_id:, base_url:, prefix: nil, recovery: nil, method: nil, path: nil, docs_url: nil) return nil if status < 400 # INJECT-001 display = RequestId.format_request_id(request_id, prefix) # INJECT-006: the server-supplied docsUrl when one has been learned, # else the configured base URL. One batch of staleness after a # docs-domain change is accepted. log_host = docs_url.nil? || docs_url.empty? ? base_url : docs_url log_url = "#{log_host}/logs/#{request_id}" debug_cmd = "npx api debug #{display}" # 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 #{log_host}/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: { "x-log-url" => log_url, # INJECT-002 "x-debug" => debug_cmd }, debug: { "log" => log_url, "cli" => debug_cmd, "recovery" => recovery_text } } 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 |