Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ AllCops:
- benchmark/**/*

plugins:
- rubocop-exception_messages
- rubocop-performance
- rubocop-rspec

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
* [#2900](https://github.com/ruby-grape/grape/pull/2900): Remove the deprecations announced in 3.2 and 3.3: `Grape::Router.normalize_path`, Hash access on middleware `Options` and their `DEFAULT_OPTIONS` constants, the positional options Hash for `auth`/`http_basic`/`desc`, a Hash returned from a `rescue_from` handler, and `@option` on validators (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
* [#2896](https://github.com/ruby-grape/grape/pull/2896): Bring test suite line coverage to 100% - [@dblock](https://github.com/dblock).
* [#2897](https://github.com/ruby-grape/grape/pull/2897): Improve test suite branch coverage - [@dblock](https://github.com/dblock).
* [#2909](https://github.com/ruby-grape/grape/pull/2909): Standardize bare Ruby exception messages (`ArgumentError`, etc.) to lowercase, unpunctuated, matching Ruby's own core/stdlib style, and wrap interpolated values in backticks via `rubocop-exception_messages` - [@dblock](https://github.com/dblock).
* Your contribution here.

#### Fixes
Expand Down
16 changes: 16 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop), run `

Make sure that `bundle exec rake` completes without errors.

##### Exception Messages

When you `raise` a bare Ruby exception (`ArgumentError`, `NotImplementedError`, etc.) for programmer errors — invalid DSL usage, misconfiguration and the like — write the message as a lowercase fragment with no trailing period, matching Ruby's own core exceptions (e.g. `TypeError: no implicit conversion from nil to integer`). This reads naturally when Ruby prints it after the exception class name and colon in a backtrace:

```ruby
raise ArgumentError, 'a block is required'
```

rather than:

```ruby
raise ArgumentError, 'A block is required.'
```

This does not apply to `Grape::Exceptions::*` messages meant to be rendered in an API response body, which are already lowercase and unpunctuated by convention and typically come from locale YAML rather than inline strings.

#### Write Documentation

Document any external behavior in the [README](README.md).
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ group :development, :test do
gem 'bundler'
gem 'rake'
gem 'rubocop', '1.88.0', require: false
gem 'rubocop-exception_messages', '0.2.0', require: false
gem 'rubocop-performance', '1.26.1', require: false
gem 'rubocop-rspec', '3.10.2', require: false
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/dry_types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def initialize
def self.wrapped_dry_types_const_get(dry_type, type)
dry_type.const_get(type.name, false)
rescue NameError
raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])
raise ArgumentError, "type `#{type}` should support coercion via `[]`" unless type.respond_to?(:[])
end
end
end
2 changes: 1 addition & 1 deletion lib/grape/dsl/entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def present(*args, root: nil, with: nil, **options)
if key
representation = body&.merge(key => representation) || { key => representation }
elsif entity_class.present? && body
raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
raise ArgumentError, "representation of type `#{representation.class}` cannot be merged" unless representation.respond_to?(:merge)

representation = body.merge(representation)
end
Expand Down
6 changes: 3 additions & 3 deletions lib/grape/dsl/inside_route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def status(status = nil)
when Symbol, Integer
@status = Rack::Utils.status_code(status)
else
raise ArgumentError, 'Status code must be Integer or Symbol.'
raise ArgumentError, 'status code must be Integer or Symbol'
end
end

Expand Down Expand Up @@ -128,7 +128,7 @@ def return_no_content
def sendfile(value = nil)
return stream if value.nil?

raise ArgumentError, 'Argument must be a file path' unless value.is_a?(String)
raise ArgumentError, 'argument must be a file path' unless value.is_a?(String)

file_body = Grape::ServeStream::FileBody.new(value)
@stream = Grape::ServeStream::StreamResponse.new(file_body)
Expand Down Expand Up @@ -191,7 +191,7 @@ def context
def stream_body(value)
return Grape::ServeStream::FileBody.new(value) if value.is_a?(String)

raise ArgumentError, 'Stream object must respond to :each.' unless value.respond_to?(:each)
raise ArgumentError, 'stream object must respond to :each' unless value.respond_to?(:each)

value
end
Expand Down
6 changes: 3 additions & 3 deletions lib/grape/dsl/request_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def default_error_formatter(new_formatter_name = nil)
# the call had never been made. Reject it here, where the mistake is.
def error_formatter(format, options = nil, with: nil)
formatter = with || options
raise ArgumentError, "error_formatter #{format.inspect} requires a formatter, given positionally or as `with:`" if formatter.nil?
raise ArgumentError, "error_formatter `#{format.inspect}` requires a formatter, given positionally or as `with:`" if formatter.nil?

inheritable_setting.add_error_formatter(format.to_sym, formatter)
end
Expand Down Expand Up @@ -109,7 +109,7 @@ def default_error_status(new_status = nil)
def rescue_from(*args, with: nil, rescue_subclasses: true, backtrace: false, original_exception: false, &block)
handler = extract_handler(args, with:, block:)
meta_selector = (args & META_RESCUE_SELECTORS).first
raise ArgumentError, "rescue_from #{meta_selector.inspect} does not accept additional arguments" if meta_selector && args.size > 1
raise ArgumentError, "rescue_from `#{meta_selector.inspect}` does not accept additional arguments" if meta_selector && args.size > 1

case meta_selector
when :all
Expand Down Expand Up @@ -163,7 +163,7 @@ def extract_handler(args, with:, block:)
case with
when Proc, Symbol then with
when String then with.to_sym
else raise ArgumentError, "with: #{with.class}, expected Symbol, String or Proc"
else raise ArgumentError, "with: `#{with.class}`, expected Symbol, String or Proc"
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/grape/dsl/routing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def route_param(param, requirements: nil, type: nil, **, &)
# The param is named here, so the constraint is its own: nest whatever
# it is, not just a Regexp. A Hash would name the param twice, or key a
# capture this namespace does not introduce, and belongs on +namespace+.
raise ArgumentError, "route_param :#{param} constrains :#{param}; pass the constraint itself, or a Hash of requirements to the enclosing namespace" if requirements.respond_to?(:to_hash)
raise ArgumentError, "route_param :`#{param}` constrains :`#{param}`; pass the constraint itself, or a Hash of requirements to the enclosing namespace" if requirements.respond_to?(:to_hash)

param_requirements = requirements ? { param.to_sym => requirements } : requirements

Expand All @@ -283,7 +283,7 @@ def versions
def validate_requirements!(requirements)
return if requirements.nil? || requirements.respond_to?(:to_hash)

raise ArgumentError, "requirements must be a Hash of param name => constraint, got #{requirements.class}"
raise ArgumentError, "requirements must be a Hash of param name => constraint, got `#{requirements.class}`"
end

# Compose a route's params: the declared params (+params do … end+) deep-merged
Expand Down
4 changes: 2 additions & 2 deletions lib/grape/dsl/validations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def params(&)
# subclass, allowing to define the schema inline. When the
# +contract+ parameter is a schema, it will be used as a parent. Optional.
def contract(contract = nil, &block)
raise ArgumentError, 'Either contract or block must be provided' unless contract || block
raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)
raise ArgumentError, 'either contract or block must be provided' unless contract || block
raise ArgumentError, 'cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)

Grape::Validations::ContractScope.new(self, contract, &block)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/validations/params_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def require_required_and_optional_fields(context, using:, except: nil)
end
required_fields.each do |field|
field_opts = using[field]
raise ArgumentError, "required field not exist: #{field}" unless field_opts
raise ArgumentError, "required field not exist: `#{field}`" unless field_opts

requires(field, **field_opts)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/validations/types/dry_type_coercer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def collection_coercer_for(type)
when Set
SetCoercer
else
raise ArgumentError, "Unknown type: #{type}"
raise ArgumentError, "unknown type: `#{type}`"
end
end

Expand Down
4 changes: 2 additions & 2 deletions lib/grape/validations/validators/length_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def initialize(attrs, options, required, scope, opts)
@min, @max, @is = options.values_at(:min, :max, :is)
validate_boundary!(:min, @min)
validate_boundary!(:max, @max)
raise ArgumentError, "min #{@min} cannot be greater than max #{@max}" if @min && @max && @min > @max
raise ArgumentError, "min `#{@min}` cannot be greater than max `#{@max}`" if @min && @max && @min > @max

return if @is.nil?
raise ArgumentError, 'is must be an integer greater than zero' unless @is.is_a?(Integer) && @is.positive?
Expand Down Expand Up @@ -42,7 +42,7 @@ def validate_param!(attr_name, params)
private

def validate_boundary!(name, val)
raise ArgumentError, "#{name} must be an integer greater than or equal to zero" if !val.nil? && (!val.is_a?(Integer) || val.negative?)
raise ArgumentError, "`#{name}` must be an integer greater than or equal to zero" if !val.nil? && (!val.is_a?(Integer) || val.negative?)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ def first = raise('#first should not have been called')
get { params[:id].to_json }
end
end
end.to raise_error(ArgumentError, /route_param :id constrains :id/)
end.to raise_error(ArgumentError, /route_param :`id` constrains :`id`/)
end

# The declared type still owns what the endpoint sees: validation runs
Expand Down Expand Up @@ -5311,7 +5311,7 @@ def uniqe_id_route
rescue_from :all do
error!(context.env, 400)
end
get { raise ArgumentError, 'Oops!' }
get { raise ArgumentError, 'oops' }
end
end

Expand Down
6 changes: 3 additions & 3 deletions spec/grape/dsl/inside_route_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def header(key = nil, val = nil)

it 'raises error if status is not a integer or symbol' do
expect { subject.status Object.new }
.to raise_error(ArgumentError, 'Status code must be Integer or Symbol.')
.to raise_error(ArgumentError, 'status code must be Integer or Symbol')
end
end

Expand Down Expand Up @@ -230,7 +230,7 @@ def header(key = nil, val = nil)
let(:file_object) { double('StreamerObject', each: nil) }

it 'raises an error that only a file path is supported' do
expect { subject.sendfile file_object }.to raise_error(ArgumentError, /Argument must be a file path/)
expect { subject.sendfile file_object }.to raise_error(ArgumentError, /argument must be a file path/)
end
end
end
Expand Down Expand Up @@ -392,7 +392,7 @@ def header(key = nil, val = nil)
subject.present 'dummy1', with: entity_mock_one
expect do
subject.present 'dummy2', with: entity_mock_two
end.to raise_error ArgumentError, 'Representation of type String cannot be merged.'
end.to raise_error ArgumentError, 'representation of type `String` cannot be merged'
end
end
end
Expand Down
10 changes: 5 additions & 5 deletions spec/grape/dsl/request_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
end

it 'raises when no formatter is given' do
expect { subject.error_formatter format }.to raise_error(ArgumentError, 'error_formatter "txt" requires a formatter, given positionally or as `with:`')
expect { subject.error_formatter format }.to raise_error(ArgumentError, 'error_formatter `"txt"` requires a formatter, given positionally or as `with:`')
expect(subject.inheritable_setting.error_formatters).to be_nil
end

Expand Down Expand Up @@ -141,7 +141,7 @@
end

it 'abort if :with option value is not Symbol, String or Proc' do
expect { subject.rescue_from :all, with: 1234 }.to raise_error(ArgumentError, "with: #{integer_class_name}, expected Symbol, String or Proc")
expect { subject.rescue_from :all, with: 1234 }.to raise_error(ArgumentError, "with: `#{integer_class_name}`, expected Symbol, String or Proc")
end

it 'abort if both :with option and block are passed' do
Expand Down Expand Up @@ -198,17 +198,17 @@
describe 'meta selector mixed with exception classes' do
it 'raises ArgumentError for :all + exception class' do
expect { subject.rescue_from :all, StandardError }
.to raise_error(ArgumentError, 'rescue_from :all does not accept additional arguments')
.to raise_error(ArgumentError, 'rescue_from `:all` does not accept additional arguments')
end

it 'raises ArgumentError for :grape_exceptions + exception class' do
expect { subject.rescue_from :grape_exceptions, StandardError }
.to raise_error(ArgumentError, 'rescue_from :grape_exceptions does not accept additional arguments')
.to raise_error(ArgumentError, 'rescue_from `:grape_exceptions` does not accept additional arguments')
end

it 'raises ArgumentError for :internal_grape_exceptions + exception class' do
expect { subject.rescue_from :internal_grape_exceptions, StandardError }
.to raise_error(ArgumentError, 'rescue_from :internal_grape_exceptions does not accept additional arguments')
.to raise_error(ArgumentError, 'rescue_from `:internal_grape_exceptions` does not accept additional arguments')
end
end

Expand Down
6 changes: 3 additions & 3 deletions spec/grape/dsl/routing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class << self
# on the first request rather than here.
it 'rejects requirements that are not a Hash' do
expect { subject.route(:any, '/', requirements: Integer) }
.to raise_error(ArgumentError, 'requirements must be a Hash of param name => constraint, got Class')
.to raise_error(ArgumentError, 'requirements must be a Hash of param name => constraint, got `Class`')
end

it 'does not duplicate identical endpoints' do
Expand Down Expand Up @@ -233,7 +233,7 @@ class << self

it 'rejects requirements that are not a Hash' do
expect { subject.namespace(:foo, requirements: Integer) {} }
.to raise_error(ArgumentError, 'requirements must be a Hash of param name => constraint, got Class')
.to raise_error(ArgumentError, 'requirements must be a Hash of param name => constraint, got `Class`')
end

it 'calls #joined_space_path on Namespace' do
Expand Down Expand Up @@ -326,7 +326,7 @@ class << self
# does not introduce — Mustermann resolves that to no constraint at all.
it 'rejects a Hash of requirements' do
expect { subject.route_param('foo', requirements: { foo: Integer }, &proc {}) }
.to raise_error(ArgumentError, 'route_param :foo constrains :foo; pass the constraint itself, or a Hash of requirements to the enclosing namespace')
.to raise_error(ArgumentError, 'route_param :`foo` constrains :`foo`; pass the constraint itself, or a Hash of requirements to the enclosing namespace')
end
end

Expand Down
4 changes: 2 additions & 2 deletions spec/grape/dsl/validations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
describe '.contract' do
context 'when contract is nil and blockless' do
it 'raises an ArgumentError' do
expect { dummy_class.contract }.to raise_error(ArgumentError, 'Either contract or block must be provided')
expect { dummy_class.contract }.to raise_error(ArgumentError, 'either contract or block must be provided')
end
end

Expand Down Expand Up @@ -69,7 +69,7 @@ def schema; end
end

it 'raises an ArgumentError' do
expect { dummy_class.contract(my_contract.new) { :my_block } }.to raise_error(ArgumentError, 'Cannot inherit from contract, only schema')
expect { dummy_class.contract(my_contract.new) { :my_block } }.to raise_error(ArgumentError, 'cannot inherit from contract, only schema')
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions spec/grape/middleware/error_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def self.call(_env)
end

rescue_from :all do |e|
raise custom_error_class, "wrapped(#{e.message})"
raise custom_error_class, "wrapped(`#{e.message}`)"
end

get('/') { raise ArgumentError, 'oops' }
Expand All @@ -267,7 +267,7 @@ def self.call(_env)

it 'redispatches to the registered handler' do
expect(response.status).to eq(422)
expect(response.body).to eq('custom-handled: wrapped(oops)')
expect(response.body).to eq('custom-handled: wrapped(`oops`)')
end
end

Expand Down
2 changes: 1 addition & 1 deletion spec/grape/validations/types/dry_type_coercer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
end

it 'raises an ArgumentError for any other type' do
expect { described_class.collection_coercer_for({}) }.to raise_error(ArgumentError, /Unknown type/)
expect { described_class.collection_coercer_for({}) }.to raise_error(ArgumentError, /unknown type/)
end
end
end
2 changes: 1 addition & 1 deletion spec/grape/validations/types/primitive_coercer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@

it 'raises error on init' do
expect(Grape::DryTypes::Params.constants).not_to include(type.name.to_sym)
expect { subject }.to raise_error(/type Complex should support coercion/)
expect { subject }.to raise_error(/type `Complex` should support coercion/)
end
end

Expand Down
6 changes: 3 additions & 3 deletions spec/grape/validations/types_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ def self.parse; end

it 'raises for an Array of it' do
expect { described_class.build_coercer(Array[UncoercibleType]) }
.to raise_error(ArgumentError, 'type UncoercibleType should support coercion via `[]`')
.to raise_error(ArgumentError, 'type `UncoercibleType` should support coercion via `[]`')
end

it 'raises for a Set of it' do
expect { described_class.build_coercer(Set[UncoercibleType]) }
.to raise_error(ArgumentError, 'type UncoercibleType should support coercion via `[]`')
.to raise_error(ArgumentError, 'type `UncoercibleType` should support coercion via `[]`')
end

it 'raises while the params block is evaluated' do
Expand All @@ -121,7 +121,7 @@ def self.parse; end
params { requires :foos, type: Array[UncoercibleType] }
get('/foos') { 'never reached' }
end
end.to raise_error(ArgumentError, 'type UncoercibleType should support coercion via `[]`')
end.to raise_error(ArgumentError, 'type `UncoercibleType` should support coercion via `[]`')
end

# A one-argument `parse` makes it a custom type, which ::custom? accepts
Expand Down
Loading
Loading