diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b80d225e..66a809b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * [#2683](https://github.com/ruby-grape/grape/pull/2683): Introduce `Grape::Util::Lazy::Base` for unified lazy-type dispatch - [@ericproulx](https://github.com/ericproulx). * [#2685](https://github.com/ruby-grape/grape/pull/2685): Skip `run_filters` and `endpoint_run_filters.grape` instrumentation when the filter list is empty - [@ericproulx](https://github.com/ericproulx). * [#2684](https://github.com/ruby-grape/grape/pull/2684): Readability refactors: case/when, guard clauses, small cleanups - [@ericproulx](https://github.com/ericproulx). +* [#2687](https://github.com/ruby-grape/grape/pull/2687): Skip backtrace capture on internal validation exceptions - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes diff --git a/lib/grape/exceptions/validation.rb b/lib/grape/exceptions/validation.rb index 2d68e9f57..97be9929e 100644 --- a/lib/grape/exceptions/validation.rb +++ b/lib/grape/exceptions/validation.rb @@ -3,6 +3,8 @@ module Grape module Exceptions class Validation < Base + EMPTY_BACKTRACE = [].freeze + attr_reader :params, :message_key def initialize(params:, message: nil, status: nil, headers: nil) @@ -16,6 +18,10 @@ def initialize(params:, message: nil, status: nil, headers: nil) end super(status:, message:, headers:) + # Pre-seed the backtrace so Ruby's raise skips capture. Validation errors are + # a hot path (raised per bad attribute) and end up as 400 Bad Request responses; + # backtraces here point into Grape internals and have no diagnostic value. + set_backtrace(EMPTY_BACKTRACE) end # Remove all the unnecessary stuff from Grape::Exceptions::Base like status diff --git a/lib/grape/exceptions/validation_array_errors.rb b/lib/grape/exceptions/validation_array_errors.rb index d7815b1f6..9c903cc7b 100644 --- a/lib/grape/exceptions/validation_array_errors.rb +++ b/lib/grape/exceptions/validation_array_errors.rb @@ -3,11 +3,15 @@ module Grape module Exceptions class ValidationArrayErrors < Base + EMPTY_BACKTRACE = [].freeze + attr_reader :errors def initialize(errors) super() @errors = errors + # Skip backtrace capture — see Grape::Exceptions::Validation for rationale. + set_backtrace(EMPTY_BACKTRACE) end end end