Exception: CLI::Kit::Config::ConfigWriteError
- Inherits:
-
SystemCallError
- Object
- SystemCallError
- CLI::Kit::Config::ConfigWriteError
- Defined in:
- lib/cli/kit/config.rb
Overview
Raised when the config file cannot be written (e.g. read-only
directory, read-only filesystem, nix/home-manager-managed symlink
pointing into /nix/store).
Inherits from SystemCallError so existing rescue SystemCallError
handlers around Config#set continue to match. The message contains
only the keys that actually changed; unchanged keys (which may
include sensitive values such as API tokens) are intentionally
excluded so the failure message can never leak secrets through
stderr or exception reports.
Instance Attribute Summary collapse
-
#config_path ⇒ Object
readonly
: String.
-
#new_content ⇒ Object
readonly
: String.
-
#old_content ⇒ Object
readonly
: String.
Class Method Summary collapse
-
.===(other) ⇒ Object
Ruby's
SystemCallError.===uses errno-based matching, which meansrescue ConfigWriteErrorwould otherwise fail to catch a ConfigWriteError instance (it would matchErrno::EACCESorErrno::EPERMinstead, based on the wrapped errno). -
.new(config_path, old_content, new_content, cause) ⇒ Object
SystemCallError.newhas a factory-style signature defined in Sorbet's stdlib RBI (+(String | Integer?) -> SystemCallError+) which doesn't match this subclass's four-argument constructor.
Instance Method Summary collapse
-
#diff ⇒ Object
A line-by-line diff of only the sections/keys that changed between
old_contentandnew_content. -
#errno ⇒ Object
Preserve the original errno from the wrapped Errno so callers that inspect
errnocontinue to see the real underlying code. -
#initialize(config_path, old_content, new_content, cause) ⇒ ConfigWriteError
constructor
rubocop:disable Lint/MissingSuper
SystemCallError#initializehas a factory-style signature that cannot be invoked from a plain subclass (it expects a matching Errno constant on the class).
Constructor Details
#initialize(config_path, old_content, new_content, cause) ⇒ ConfigWriteError
rubocop:disable Lint/MissingSuper
SystemCallError#initialize has a factory-style signature that
cannot be invoked from a plain subclass (it expects a matching
Errno constant on the class). We bypass it deliberately and
initialize via Exception so we just get a message-only
exception that rescue SystemCallError still catches via
inheritance. super would not work here.
: (String config_path, String old_content, String new_content, SystemCallError cause) -> void
69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/cli/kit/config.rb', line 69 def initialize(config_path, old_content, new_content, cause) @config_path = config_path @old_content = old_content @new_content = new_content @wrapped_errno = cause.errno = <<~MSG.rstrip Could not write to #{config_path}: #{cause.} Attempted changes (unchanged keys omitted): #{diff} MSG Exception.instance_method(:initialize).bind(self).call() end |
Instance Attribute Details
#config_path ⇒ Object (readonly)
: String
53 54 55 |
# File 'lib/cli/kit/config.rb', line 53 def config_path @config_path end |
#new_content ⇒ Object (readonly)
: String
59 60 61 |
# File 'lib/cli/kit/config.rb', line 59 def new_content @new_content end |
#old_content ⇒ Object (readonly)
: String
56 57 58 |
# File 'lib/cli/kit/config.rb', line 56 def old_content @old_content end |
Class Method Details
.===(other) ⇒ Object
Ruby's SystemCallError.=== uses errno-based matching,
which means rescue ConfigWriteError would otherwise fail
to catch a ConfigWriteError instance (it would match
Errno::EACCES or Errno::EPERM instead, based on the
wrapped errno). Override with plain class-hierarchy
matching so rescue ConfigWriteError works as callers
expect. rescue SystemCallError still matches via normal
inheritance.
: (untyped other) -> bool
33 34 35 |
# File 'lib/cli/kit/config.rb', line 33 def ===(other) other.is_a?(self) end |
.new(config_path, old_content, new_content, cause) ⇒ Object
SystemCallError.new has a factory-style signature defined
in Sorbet's stdlib RBI (+(String | Integer?) ->
SystemCallError+) which doesn't match this subclass's
four-argument constructor. Override .new here so type
checkers and callers see the real signature, and allocate
the instance manually to bypass +SystemCallError+'s
factory behaviour.
: (String config_path, String old_content, String new_content, SystemCallError cause) -> ConfigWriteError
45 46 47 48 49 |
# File 'lib/cli/kit/config.rb', line 45 def new(config_path, old_content, new_content, cause) instance = allocate instance.__send__(:initialize, config_path, old_content, new_content, cause) instance end |
Instance Method Details
#diff ⇒ Object
A line-by-line diff of only the sections/keys that changed
between old_content and new_content. Unchanged keys are
omitted so that sensitive values stored elsewhere in the config
are never included in the failure message.
: -> String
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/cli/kit/config.rb', line 96 def diff old_ini = CLI::Kit::Ini.new(config: @old_content).tap(&:parse).ini new_ini = CLI::Kit::Ini.new(config: @new_content).tap(&:parse).ini lines = [] (old_ini.keys | new_ini.keys).each do |section| old_section = old_ini[section] || {} new_section = new_ini[section] || {} changes = [] (old_section.keys | new_section.keys).each do |key| old_val = old_section[key] new_val = new_section[key] next if old_val == new_val changes << "- #{key} = #{old_val}" if old_val changes << "+ #{key} = #{new_val}" if new_val end next if changes.empty? prefix = if old_section.empty? '+ ' elsif new_section.empty? '- ' else ' ' end lines << "#{prefix}#{section}" lines.concat(changes) end lines.join("\n") end |
#errno ⇒ Object
Preserve the original errno from the wrapped Errno so callers
that inspect errno continue to see the real underlying code.
: -> Integer?
87 88 89 |
# File 'lib/cli/kit/config.rb', line 87 def errno @wrapped_errno end |