Module: NRSER::NicerError
- Included in:
- AbstractMethodError, ArgumentError, ConflictError, TypeError, Types::CheckError, Types::FromStringError
- Defined in:
- lib/nrser/errors/nicer_error.rb
Overview
A mixin for Exception and utilities to make life better… even when things go wrong.
“Nicer” errors do a few things:
-
**‘message` is a splat/`Array`**
Accept an Array ‘message` instead of just a string, dumping non-string values and joining everything together.
This lets you deal with printing/dumping all in one place instead of ad-hoc’ing ‘#to_s`, `#inspect`, `#pretty_inspect`, etc. all over the place (though you can still dump values yourself of course since string pass right through).
Write things like:
MyError.new "The value", value, "sucks, it should be", expected
This should cut down the amount of typing when raising as well, which is always welcome.
It also allows for a future where we get smarter about dumping things, offer configuration options, switch on environments (slow, rich dev versus fast, concise prod), etc.
-
**“Extended” Messages**
The normal message that we talked about in (1) - that we call the *summary message* or super-message (since it gets passed up to the built-in Exception’s ‘#initialize`) - is intended to be:
-
Very concise
-
A single line well under 80 characters if possible.
-
This just seems like how Ruby exception messages were meant to be, I guess, and in many situations it’s all you would want or need (production, when it just gets rescued anyways, there’s no one there to read it, etc.).
-
-
Cheap to render.
-
We may be trying to do lot very quickly on a production system.
-
However - especially when developing - it can be really nice to add considerably more detail and feedback to errors.
To support this important use case as well, ‘NicerError` introduces the idea of an *extended message* that does not need to be rendered and output along with the summary/super-message.
It’s rendering is done on-demand, so systems that are not configured to use it will pay a minimal cost for it’s existence.
> See #extended_message.
The extended message is composed of:
-
Text details, optionally rendered via Binding#erb when a binding is provided.
-
A context of name and value pairs to dump.
Both are provided as optional keyword parameters to #initialize.
-
Constant Summary collapse
- DEFAULT_COLUMN_WIDTH =
Default column width
78
Class Method Summary collapse
-
.column_width ⇒ Fixnum
Column width to format for (just summary/super-message at the moment).
Instance Method Summary collapse
-
#add_extended_message? ⇒ Boolean
Should we add the extended message to #to_s output?.
-
#context ⇒ Hash<Symbol, *>
Any additional context values to add to extended messages provided to #initialize.
- #context_section ⇒ String?
-
#default_message ⇒ String
Main message to use when none provided to #initialize.
- #details ⇒ Object
-
#details_section ⇒ String?
Render details (first time only, then cached) and return the string.
-
#extended_message ⇒ String
Return the extended message, rendering if necessary (cached after first call).
-
#format_message(*message) ⇒ String
Format the main message by converting args to strings and joining them.
-
#format_message_segment(segment) ⇒ String
Format a segment of the error message.
-
#initialize(*message, binding: nil, details: nil, **context) ⇒ Object
Construct a nicer error.
-
#to_s(extended: nil) ⇒ String
Get the message or the extended message.
Class Method Details
.column_width ⇒ Fixnum
Implement terminal width detection like Thor?
Column width to format for (just summary/super-message at the moment).
104 105 106 |
# File 'lib/nrser/errors/nicer_error.rb', line 104 def self.column_width DEFAULT_COLUMN_WIDTH end |
Instance Method Details
#add_extended_message? ⇒ Boolean
Just returns ‘true` for now… should be configurable in the future.
Should we add the extended message to #to_s output?
293 294 295 |
# File 'lib/nrser/errors/nicer_error.rb', line 293 def true end |
#context ⇒ Hash<Symbol, *>
Any additional context values to add to extended messages provided to #initialize.
200 201 202 |
# File 'lib/nrser/errors/nicer_error.rb', line 200 def context @context end |
#context_section ⇒ String?
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/nrser/errors/nicer_error.rb', line 245 def context_section lazy_var :@context_section do if context.empty? nil else "# Context:\n\n" + context.map { |name, value| name_str = name.to_s value_str = PP.pp \ value, ''.dup, (NRSER::NicerError.column_width - name_str.length - 2) if value_str.lines.count > 1 "#{ name_str }:\n\n#{ value_str.indent 4 }\n" else "#{ name_str }: #{ value_str }\n" end }.join end end end |
#default_message ⇒ String
Main message to use when none provided to #initialize.
190 191 192 |
# File 'lib/nrser/errors/nicer_error.rb', line 190 def "(no message)" end |
#details ⇒ Object
205 206 207 |
# File 'lib/nrser/errors/nicer_error.rb', line 205 def details @details end |
#details_section ⇒ String?
Render details (first time only, then cached) and return the string.
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/nrser/errors/nicer_error.rb', line 214 def details_section lazy_var :@details_section do # No details if we have nothing to work with if details.nil? nil else contents = case details when Proc details.call when String details else details.to_s end if contents.empty? nil else if @binding contents = binding.erb contents end "# Details\n\n" + contents end end end end |
#extended_message ⇒ String
Return the extended message, rendering if necessary (cached after first call).
274 275 276 277 278 279 280 281 282 283 |
# File 'lib/nrser/errors/nicer_error.rb', line 274 def @extended_message ||= begin sections = [] sections << details_section unless details_section.nil? sections << context_section unless context_section.nil? joined = sections.join "\n\n" end end |
#format_message(*message) ⇒ String
Format the main message by converting args to strings and joining them.
181 182 183 |
# File 'lib/nrser/errors/nicer_error.rb', line 181 def * .map( &method( :format_message_segment ) ).join( ' ' ) end |
#format_message_segment(segment) ⇒ String
Format a segment of the error message.
Strings are simply returned. Other things are inspected (for now).
162 163 164 165 166 167 168 169 |
# File 'lib/nrser/errors/nicer_error.rb', line 162 def segment return segment.to_summary if segment.respond_to?( :to_summary ) return segment if String === segment # TODO Do better! segment.inspect end |
#initialize(*message, binding: nil, details: nil, **context) ⇒ Object
Construct a nicer error.
137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/nrser/errors/nicer_error.rb', line 137 def initialize *, binding: nil, details: nil, **context @binding = binding @context = context @details = details = if .empty? = * super end |
#to_s(extended: nil) ⇒ String
This is a bit weird, having to do with what I can tell about the built-in errors and how they handle their message - they have no instance variables, and seem to rely on ‘#to_s` to get the message out of C-land, however that works.
Exception#message just forwards here, so I overrode that with #message to just get the summary/super-message from this method.
Get the message or the extended message.
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/nrser/errors/nicer_error.rb', line 318 def to_s extended: nil # The way to get the superclass' message = super() # If `extended` is explicitly `false` then just return that return if extended == false # Otherwise, see if the extended message was explicitly requested, # of if we're configured to provide it as well. # # Either way, don't add it it's empty. # if (extended || ) && !.empty? + "\n\n" + else end end |