Class: Axn::Configuration
- Inherits:
-
Object
- Object
- Axn::Configuration
- Extended by:
- Axn::Configurable::Settings
- Defined in:
- lib/axn/configuration.rb
Constant Summary collapse
- SIDEKIQ_JOB_TAG_SOURCES =
Which declared facet types surface as Sidekiq per-job
tagsat enqueue (PRO-2855). Sidekiq tags are ephemeral job-payload strings shown/searched in the web UI — they carry no metrics-billing cost, so high-cardinalitytags are welcome here (unlike metrics). Default is both; set %i for bounded-only, or [] to disable the sink. %i[tag dimension].freeze
- TOOL_ROOTS_BLOCKLIST =
Root-ish
tool_rootsentries that must never be accepted: both normalize to the project root itself (an empty entry cleanpaths to".", and"."is its own normalized form). Compared against the normalized entry (see .normalize_tool_root). The broad-DIRECTORY-NAME cases (actions,app,app/actions, and any absolute spelling of them) are caught by the leaf-segment rule below instead, since a blocklist of exact strings can't also catch e.g.File.expand_path("actions")or/srv/app/actions. ["", "."].freeze
- BROAD_TOOL_ROOT_LEAVES =
Leaf (final path segment) names that make a
tool_rootsentry broad regardless of how much path precedes them — including an ABSOLUTE spelling (e.g.File.expand_path("actions"),"/srv/app/actions"), which normalizes to a path that no longer equals any exact blocklist string but still resolves to (and bulk-exposes) the same directory. An entry ending inactionsis the business-actions dir; ending inappis the whole app dir. Checked against the LAST segment only, so a narrow subdir likeactions/tools(leaftools) is unaffected. %w[actions app].freeze
- ASYNC_EXCEPTION_REPORTING_OPTIONS =
Controls when on_exception is triggered in async context (Sidekiq/ActiveJob). Options:
:every_attempt - trigger on every retry attempt (includes retry context) :first_and_exhausted - trigger on first attempt and when retries exhausted (default) :only_exhausted - only trigger when retries exhausted (via death handler) %i[every_attempt first_and_exhausted only_exhausted].freeze
Instance Attribute Summary collapse
-
#ambient_context_provider ⇒ Object
Optional callable returning a Hash of ambient context data (e.g. from request-local state).
- #logger ⇒ Object
- #on_exception(e, action:, context: {}) ⇒ Object
- #rails ⇒ Object
Class Method Summary collapse
-
.broad_tool_root?(entry) ⇒ Boolean
Single source-of-truth predicate for "is this
tool_rootsentry too broad to allow" — shared byAxn::Tools::AdapterRoots.validate!(which raises on a badtool_rootsentry) and Tools::Registry (which skips + warns at resolve time). -
.normalize_tool_root(entry) ⇒ Object
Normalizes a
tool_rootsentry for BOTH the broad-entry blocklist check and directory resolution (Tools::Registry#_resolve_tool_dir): strips surrounding whitespace and any leading/trailing slashes, so" /actions/ "and"actions"compare equal, then collapses./..segments viaPathname#cleanpathso alternate spellings like"./actions","actions/.", and"actions/../actions"normalize to the same"actions"the blocklist already rejects (Pathname#cleanpath is a lexical collapse, no filesystem access).
Instance Method Summary collapse
- #_default_async_adapter ⇒ Object
- #_default_async_config ⇒ Object
- #_default_async_config_block ⇒ Object
- #async_exception_reporting ⇒ Object
- #async_exception_reporting=(value) ⇒ Object
-
#default_async? ⇒ Boolean
Whether a default async adapter is configured — the only thing a gem needs to know about the
_default_async_*trio below, which stays underscored because core reads all three of them across files. - #env ⇒ Object
-
#env=(value) ⇒ Object
Validated at ASSIGNMENT because the reader below wraps the stored value in
ActiveSupport::StringInquirer, which takes a String and nothing else — so anything it refuses has to be refused HERE. -
#set_default_async(adapter = false, **config, &block) ⇒ Object
rubocop:disable Style/OptionalBooleanParameter.
- #set_enqueue_all_async(adapter, **config, &block) ⇒ Object
Methods included from Axn::Configurable::Settings
_declared_settings, extended, overridable_config_source, setting
Methods included from Axn::Configurable::PerClassOverrides
#_validate_override_setter!, #config_namespace, #overrides, #resolve_override_for
Instance Attribute Details
#ambient_context_provider ⇒ Object
Optional callable returning a Hash of ambient context data (e.g. from request-local state).
Consulted when no explicit ambient_context: kwarg is passed to an Axn call. Falls back to
Axn::Core::AmbientContext.default_source when nil.
170 171 172 |
# File 'lib/axn/configuration.rb', line 170 def ambient_context_provider @ambient_context_provider end |
#logger ⇒ Object
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/axn/configuration.rb', line 283 def logger return @logger if @logger # Use sidekiq logger if in background resolved = begin if Axn::Util::ExecutionContext.background? && defined?(Sidekiq) Sidekiq.logger else Rails.logger end rescue NameError nil end # Memoize a real host logger, but not the stdout fallback below: `Rails.logger` is nil # until Rails runs its initialize_logger initializer, so `include Axn` at gem load (under # Bundler.require) resolves to nil here. Returning the transient fallback without caching it # keeps every `Axn.config.logger.<level>` call site working during boot and still picks up # `Rails.logger` on a later call once it exists (PRO-2891). return @logger = resolved if resolved @fallback_logger ||= Logger.new($stdout).tap { |l| l.level = Logger::INFO } end |
#on_exception(e, action:, context: {}) ⇒ Object
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/axn/configuration.rb', line 242 def on_exception(e, action:, context: {}) if action.respond_to?(:result) && action.result.respond_to?(:error) resolved_error = action.result.error # Compare with the default fallback message instead of calling default_error # to avoid triggering error message resolution multiple times # Each branch picks WHICH detail to report; none of them renders it. Rendering happens once, at the # join below, so the composition does not depend on every branch here having remembered to. detail = if resolved_error == Axn::Core::Flow::Handlers::Resolvers::MessageResolver::DEFAULT_ERROR e else resolved_error end else detail = e end # Both operands normalized at the join. `detail` is the caller's own object whenever they handed one to # `fail!` (or returned one from a declared `error` handler), so a rendered UTF-8 class name beside a raw # Latin-1 detail raised `Encoding::CompatibilityError` — and since this whole handler runs inside # `best_effort`, that lost BOTH this log line and the configured `on_exception` callback below. An # exception detail reads through the guarded message reader; anything else through the value renderer. msg = "Handled exception (#{Axn::Internal::Rendering.class_name(e)}): #{_rendered_detail(detail)}" msg = ("#" * 10) + " #{msg} " + ("#" * 10) unless Axn.config.env.production? action.log(msg) return unless @on_exception # Only pass the args and kwargs that the given block expects Axn::Internal::Callable.call_with_desired_shape(@on_exception, args: [e], kwargs: { action:, context: }) end |
#rails ⇒ Object
240 |
# File 'lib/axn/configuration.rb', line 240 def rails = @rails ||= RailsConfiguration.new |
Class Method Details
.broad_tool_root?(entry) ⇒ Boolean
Single source-of-truth predicate for "is this tool_roots entry too broad to allow" —
shared by Axn::Tools::AdapterRoots.validate! (which raises on a bad tool_roots entry)
and Tools::Registry (which skips + warns at resolve time). Validation alone can't fail-safe
an in-place mutation of the live array (adapter.config.tool_roots << "actions" never calls
the validated writer), so the registry re-checks every entry against this same predicate
before resolving it to a directory.
88 |
# File 'lib/axn/configuration.rb', line 88 def broad_tool_root?(entry) = !_broad_tool_root_reason(entry).nil? |
.normalize_tool_root(entry) ⇒ Object
Normalizes a tool_roots entry for BOTH the broad-entry blocklist check and directory
resolution (Tools::Registry#_resolve_tool_dir): strips surrounding whitespace and any
leading/trailing slashes, so " /actions/ " and "actions" compare equal, then collapses
./.. segments via Pathname#cleanpath so alternate spellings like "./actions",
"actions/.", and "actions/../actions" normalize to the same "actions" the blocklist
already rejects (Pathname#cleanpath is a lexical collapse, no filesystem access). An empty
string cleanpaths to ".", which the blocklist covers. Exposed as public API (rather than
kept private) so the registry's resolver can share this exact normalization — validate!
and the resolver must never disagree on what a given entry means.
99 100 101 102 |
# File 'lib/axn/configuration.rb', line 99 def normalize_tool_root(entry) stripped = entry.to_s.strip.gsub(%r{\A/+|/+\z}, "") Pathname(stripped).cleanpath.to_s end |
Instance Method Details
#_default_async_adapter ⇒ Object
199 |
# File 'lib/axn/configuration.rb', line 199 def _default_async_adapter = @default_async_adapter ||= false |
#_default_async_config ⇒ Object
200 |
# File 'lib/axn/configuration.rb', line 200 def _default_async_config = @default_async_config ||= {} |
#_default_async_config_block ⇒ Object
201 |
# File 'lib/axn/configuration.rb', line 201 def _default_async_config_block = @default_async_config_block |
#async_exception_reporting ⇒ Object
179 180 181 |
# File 'lib/axn/configuration.rb', line 179 def async_exception_reporting @async_exception_reporting ||= :first_and_exhausted end |
#async_exception_reporting=(value) ⇒ Object
183 184 185 186 187 188 189 190 191 192 |
# File 'lib/axn/configuration.rb', line 183 def async_exception_reporting=(value) unless ASYNC_EXCEPTION_REPORTING_OPTIONS.include?(value) raise ArgumentError, "async_exception_reporting must be one of: #{ASYNC_EXCEPTION_REPORTING_OPTIONS.join(', ')}" end @async_exception_reporting = value # Auto-register Sidekiq middleware/death handler if needed and Sidekiq is available _auto_configure_sidekiq_for_async_exception_reporting(value) end |
#default_async? ⇒ Boolean
Whether a default async adapter is configured — the only thing a gem needs to know about the
_default_async_* trio below, which stays underscored because core reads all three of them
across files. present? rather than !!, matching how Axn::Async itself tests the adapter.
197 |
# File 'lib/axn/configuration.rb', line 197 def default_async? = _default_async_adapter.present? |
#env ⇒ Object
335 336 337 338 |
# File 'lib/axn/configuration.rb', line 335 def env @env ||= ENV["RACK_ENV"].presence || ENV["RAILS_ENV"].presence || "development" ActiveSupport::StringInquirer.new(@env) end |
#env=(value) ⇒ Object
Validated at ASSIGNMENT because the reader below wraps the stored value in
ActiveSupport::StringInquirer, which takes a String and nothing else — so anything it refuses has to be
refused HERE. Accepted silently and left to the reader, a Symbol raises TypeError: no implicit conversion of Symbol into String from every LATER read instead — six sites inside the gem plus every
Axn.config.env.production? in user code — which puts the failure nowhere near the line that caused it, and
makes c.env = :production in an initializer detonate on the first action to run.
A Symbol is COERCED rather than refused: it is a reasonable thing to write and its meaning is unambiguous.
nil is accepted as the way to clear an override, since the reader's @env ||= ENV[…] fallback is what
"auto-detect the environment" means. A String subclass is a String (Rails.env is a StringInquirer
already, and c.env = Rails.env is the documented Rails wiring), so it is stored as it stands.
Anything else is a declaration error, named on the same terms as every other one: case/when decides the
type through Module#===, a C-level check running none of the value's own code, and the offender is named
by CLASS through the undispatched renderer — a value that raises from its own inspect must not replace
the verdict being reached.
324 325 326 327 328 329 330 331 332 333 |
# File 'lib/axn/configuration.rb', line 324 def env=(value) @env = case value when nil, ::String then value when ::Symbol then value.to_s else raise ArgumentError, "env must be a String or Symbol naming the environment, or nil to auto-detect it from " \ "RACK_ENV/RAILS_ENV (got a value of class #{Axn::Internal::Rendering.class_name(value)})" end end |
#set_default_async(adapter = false, **config, &block) ⇒ Object
rubocop:disable Style/OptionalBooleanParameter
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/axn/configuration.rb', line 203 def set_default_async(adapter = false, **config, &block) # rubocop:disable Style/OptionalBooleanParameter raise ArgumentError, "Cannot set default async adapter to nil as it would cause infinite recursion" if adapter.nil? @default_async_adapter = adapter unless adapter.nil? @default_async_config = config.any? ? config : {} @default_async_config_block = block_given? ? block : nil _ensure_async_exception_reporting_registered_for_adapter(adapter) _apply_async_to_enqueue_all_orchestrator # Build the dedicated Sidekiq default worker now (at boot, in every process) so it exists # and carries the default's config/block when a globally-defaulted action is enqueued or run. return unless @default_async_adapter == :sidekiq && defined?(Axn::Async::Adapters::Sidekiq) Axn::Async::Adapters::Sidekiq.configure_default_worker!(config: @default_async_config, block: @default_async_config_block) end |
#set_enqueue_all_async(adapter, **config, &block) ⇒ Object
231 232 233 234 235 236 237 238 |
# File 'lib/axn/configuration.rb', line 231 def set_enqueue_all_async(adapter, **config, &block) @enqueue_all_async_adapter = adapter @enqueue_all_async_config = config.any? ? config : {} @enqueue_all_async_config_block = block_given? ? block : nil _ensure_async_exception_reporting_registered_for_adapter(adapter) _apply_async_to_enqueue_all_orchestrator end |