Module: Rubino::Boot::ConfigGuard
- Defined in:
- lib/rubino/boot/config_guard.rb
Overview
Loads configuration at process startup, turning a malformed/corrupt config.yml into a clean, actionable boot abort instead of a raw Ruby + Psych double backtrace (CFG-1).
The entrypoint (‘exe/rubino`) calls Config::Loader#load for EVERY command, before Thor dispatch. Any Config::ConfigError (or a Psych::SyntaxError that escapes the loader) used to propagate all the way out of `exe/rubino:16`, so a single typo in config.yml killed the process with a stack trace — even `rubino doctor`, whose graceful corruption handler (#259) was never reached because boot died first.
ConfigGuard.load! runs the load behind a rescue that writes a single-line diagnostic (what’s wrong + the config path + how to fix it) to $stderr and exits non-zero — boot abort, not exception, mirroring EncryptionKey.validate!. doctor’s own handling still works: doctor re-loads via the Loader and reports corruption itself, so a clean boot here does not mask it.
Class Method Summary collapse
-
.load!(loader: Config::Loader.new, stderr: $stderr, argv: []) ⇒ Object
The Loader normalizes every malformed config shape into a Config::ConfigError at the source.
-
.meta_command?(argv) ⇒ Boolean
‘–version`/`-v`/`version` and `–help`/`-h`/`help` are pure-meta: they print static text and exit, so a config-issue warning on them is pure noise.
-
.warn_config_issues(loader, stderr) ⇒ Object
Emits a one-line-per-issue config WARNING to stderr (F8), or nothing when the config is clean.
Class Method Details
.load!(loader: Config::Loader.new, stderr: $stderr, argv: []) ⇒ Object
The Loader normalizes every malformed config shape into a Config::ConfigError at the source. The remaining classes here are a defensive backstop: should any raw Psych/IO failure ever slip past the loader (a new shape, a refactor), it still becomes a clean boot abort rather than a double backtrace on every command (CFG-R2).
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/rubino/boot/config_guard.rb', line 28 def self.load!(loader: Config::Loader.new, stderr: $stderr, argv: []) loader.load # LOAD-time schema validation (F8): a HAND-EDITED config.yml with an # unknown key or a wrong-typed value used to load SILENTLY (the validator # only ran at `config set` time) and only blow up later. Surface those as # a clear, NON-FATAL warning here — the boot chokepoint every command # already passes through — so the user is told at startup instead of # discovering it as a runtime crash / provider 4xx. Never fatal: a # warning must not block a usable config, and a probe hiccup is ignored. # Pure-meta commands (version/help) never need a configured model, so the # config-issue warning is noise on `rubino --version`/`--help` — skip it. warn_config_issues(loader, stderr) unless (argv) nil rescue Config::ConfigError, Psych::Exception, SystemCallError, IOError => e stderr.puts "rubino: config error — #{e.}" stderr.puts "rubino: fix #{loader.config_path}, restore a backup, or re-run 'rubino setup'." exit 1 end |
.meta_command?(argv) ⇒ Boolean
‘–version`/`-v`/`version` and `–help`/`-h`/`help` are pure-meta: they print static text and exit, so a config-issue warning on them is pure noise. True when the invocation is one of those (the meta flag/word is the FIRST token, matching how Commands.start dispatches them).
51 52 53 54 |
# File 'lib/rubino/boot/config_guard.rb', line 51 def self.(argv) first = Array(argv).first.to_s %w[--version -v version --help -h help].include?(first) end |
.warn_config_issues(loader, stderr) ⇒ Object
Emits a one-line-per-issue config WARNING to stderr (F8), or nothing when the config is clean. Best-effort — any failure here is swallowed so a validation hiccup can never break boot.
59 60 61 62 63 64 65 66 67 68 |
# File 'lib/rubino/boot/config_guard.rb', line 59 def self.warn_config_issues(loader, stderr) issues = Config::Validator.warnings(loader.raw_config) return if issues.empty? stderr.puts "rubino: warning: #{loader.config_path} has #{issues.size} " \ "config issue#{"s" if issues.size != 1} (run `rubino doctor` for details):" issues.first(5).each { |msg| stderr.puts "rubino: - #{msg}" } rescue StandardError nil end |