Class: Axn::Webhooks::Outbound::Config
- Inherits:
-
Object
- Object
- Axn::Webhooks::Outbound::Config
- Extended by:
- Configurable::Settings
- Defined in:
- lib/axn/webhooks/outbound/config.rb
Overview
The resolved, immutable outbound declaration. One per process (a single outbound block).
Defined Under Namespace
Classes: Resolution
Constant Summary collapse
- DEFAULT_MAX_ATTEMPTS =
8- DEFAULT_BACKOFF =
Equal jitter (half fixed, half random): a fan-out event whose receiver is down would otherwise have every failing target retry in lockstep, converging on the same instant.
lambda do |attempt| base = [30 * (3**(attempt - 1)), 6 * 3600].min ((base / 2.0) + (rand * base / 2.0)).round end
- DEFAULT_OPEN_TIMEOUT =
5- DEFAULT_READ_TIMEOUT =
10- TIMEOUT_VALIDATE =
Forwarded straight to Net::HTTP (see Transport), which calls
.zero?/compares on whatever it's given — an unvalidated non-Numeric (e.g. a String fromENV.fetch("OPEN_TIMEOUT")) would otherwise raise NoMethodError mid-delivery instead of failing at boot (Codex P2 finding). ->(v) { (v.is_a?(Numeric) && v.positive?) || "must be a positive Numeric" }
- SAFE_TO_INSPECT =
Scalars a rejected target's
#inspectis safe to show verbatim inredact_targetbelow: a bare-value target (the common malformed cases -- nil, a wrong-type url, a stray Integer) carries no OTHER fields that could hide a credential. NOT String: a webhook URL commonly embeds a credential itself (HTTP Basic userinfo, a signed/token query param) -- seeredact_target's dedicated String handling. [Numeric, Symbol, NilClass, TrueClass, FalseClass].freeze
- HASH_KEY_NAME =
A plausible field-name typo (
:secret,:api_key-- whatSubscriber.coerce's own "unknown key(s)" message exists to surface) is a short, simple identifier -- used byredact_hash_keybelow to decide whether a Hash row's key is safe to show as-is. /\A[A-Za-z_][A-Za-z0-9_]{0,49}\z/
Instance Attribute Summary collapse
-
#signer ⇒ Object
readonly
Returns the value of attribute signer.
Class Method Summary collapse
-
.url_problem(url) ⇒ Object
The problem with
urlas an outbound target, or nil when there is none.
Instance Method Summary collapse
- #events ⇒ Object
-
#initialize(signer:, events:, default_subscribers:, max_attempts:, backoff:, transport:, vendor: nil, user_agent: nil, open_timeout: nil, read_timeout: nil, allowed_hosts: nil, allow_url: nil, headers: nil) ⇒ Config
constructor
rubocop:disable Metrics/ParameterLists -- one kwarg per DSL setting, mirroring
DSL#__config__'s call site 1:1; a Hash-options refactor would ripple through every caller for no real gain. -
#resolve_subscribers(event) ⇒ Object
A DECLARED per-event
to:always wins, even when it resolves to zero targets — a static Array as-is (including[]), or a lambda->(event){…}invoked (arity-aware, matching Resolvers.resolve) and its result wrapped in Array (nil -> []). -
#targets_for(event) ⇒ Object
Back-compat convenience for callers that only want the resolved URLs and don't care about a malformed row -- e.g.
-
#vendor_for(event) ⇒ Object
A per-event
vendor:overrides the block-level default; same precedence astype:. - #wire_type(event) ⇒ Object
Constructor Details
#initialize(signer:, events:, default_subscribers:, max_attempts:, backoff:, transport:, vendor: nil, user_agent: nil, open_timeout: nil, read_timeout: nil, allowed_hosts: nil, allow_url: nil, headers: nil) ⇒ Config
rubocop:disable Metrics/ParameterLists -- one kwarg per DSL setting, mirroring DSL#__config__'s
call site 1:1; a Hash-options refactor would ripple through every caller for no real gain.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/axn/webhooks/outbound/config.rb', line 109 def initialize(signer:, events:, default_subscribers:, max_attempts:, backoff:, transport:, vendor: nil, user_agent: nil, open_timeout: nil, read_timeout: nil, allowed_hosts: nil, allow_url: nil, headers: nil) # rubocop:enable Metrics/ParameterLists @signer = signer @events = events # { Symbol => { to:, type:, vendor: } } @default_subscribers = default_subscribers self.max_attempts = max_attempts unless max_attempts.nil? self.backoff = backoff unless backoff.nil? self.transport = transport unless transport.nil? self.vendor = vendor unless vendor.nil? self.user_agent = user_agent unless user_agent.nil? self.open_timeout = open_timeout unless open_timeout.nil? self.read_timeout = read_timeout unless read_timeout.nil? self.allowed_hosts = allowed_hosts unless allowed_hosts.nil? self.headers = headers unless headers.nil? self.allow_url = allow_url unless allow_url.nil? @events.each { |name, spec| validate_event!(name, spec) } deep_freeze! end |
Instance Attribute Details
#signer ⇒ Object (readonly)
Returns the value of attribute signer.
133 134 135 |
# File 'lib/axn/webhooks/outbound/config.rb', line 133 def signer @signer end |
Class Method Details
.url_problem(url) ⇒ Object
The problem with url as an outbound target, or nil when there is none. A predicate
rather than a raiser, because its two callers disagree on the error class: a declaration
mistake at boot is an ArgumentError (see validate_url!), while a bad one-off emit(to:)
URL is an Axn::Webhooks::Error a caller may rescue at runtime (see Outbound::Emit). Shape
only (no host policy) -- Outbound::Emit#resolve_targets applies allowed_hosts/
allow_url itself via TargetPolicy.check! for the one-off to: override; this predicate
stays a pure URL-shape check so validate_url!'s narrower boot-time contract doesn't drift.
100 101 102 103 104 105 |
# File 'lib/axn/webhooks/outbound/config.rb', line 100 def self.url_problem(url) TargetPolicy.parse_url!(url) nil rescue Axn::Webhooks::InvalidTarget => e e. end |
Instance Method Details
#events ⇒ Object
135 |
# File 'lib/axn/webhooks/outbound/config.rb', line 135 def events = @events.keys |
#resolve_subscribers(event) ⇒ Object
A DECLARED per-event to: always wins, even when it resolves to zero targets — a static
Array as-is (including []), or a lambda ->(event){…} invoked (arity-aware, matching
Resolvers.resolve) and its result wrapped in Array (nil -> []). The block-level
subscribers resolver is ONLY consulted when the event declared no to: at all
(spec.nil?) — never as a fallback for a declared resolver returning nil.
A static to: Array is validated ONCE, at boot (validate_event!) -- so its entries are
only ever re-COERCED into Subscribers here, never re-checked against TargetPolicy.
Re-running it on every resolution would be wasted work for a target that can never
change, and would actually BREAK a stateful/rate-limited allow_url predicate: it could
reject an already-boot-validated static target later, contradicting the documented
once-at-boot contract (Codex P2 finding). Every OTHER raw entry -- from a callable to:
or the subscribers fallback, neither checkable at boot since they depend on runtime
state -- goes through the SAME TargetPolicy a static Array was checked against, so a
runtime resolver can never see a looser bar than a hand-written Array does.
167 168 169 170 171 172 173 |
# File 'lib/axn/webhooks/outbound/config.rb', line 167 def resolve_subscribers(event) spec = fetch(event) return static_resolution(spec) if spec[:to].is_a?(Array) raw = spec[:to].nil? ? call_resolver(@default_subscribers, event) : resolve_to(spec[:to], event) check_targets(raw) end |
#targets_for(event) ⇒ Object
Back-compat convenience for callers that only want the resolved URLs and don't care about
a malformed row -- e.g. today's Emit fan-out. Silently drops rejections; a caller that
needs to know about (or report) them wants resolve_subscribers directly. Frozen: for a
static to: Array, resolve_subscribers used to return the SAME frozen Array deep_freeze!
already produced; going through Subscriber/.map builds a fresh one, which .map never
freezes on its own -- targets_for's own immutability contract (asserted in
config_immutability_spec.rb) has to be re-established here explicitly.
182 183 184 |
# File 'lib/axn/webhooks/outbound/config.rb', line 182 def targets_for(event) resolve_subscribers(event).subscribers.map(&:url).freeze end |
#vendor_for(event) ⇒ Object
A per-event vendor: overrides the block-level default; same precedence as type:.
142 143 144 |
# File 'lib/axn/webhooks/outbound/config.rb', line 142 def vendor_for(event) fetch(event)[:vendor] || vendor end |
#wire_type(event) ⇒ Object
137 138 139 |
# File 'lib/axn/webhooks/outbound/config.rb', line 137 def wire_type(event) (fetch(event)[:type] || event).to_s end |