diff --git a/CHANGELOG.md b/CHANGELOG.md index 04208237c..cbee83225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * [#2554](https://github.com/ruby-grape/grape/pull/2554): Remove `Grape::Http::Headers` and `Grape::Util::Lazy::Object` - [@ericproulx](https://github.com/ericproulx). * [#2556](https://github.com/ruby-grape/grape/pull/2556): Remove unused `Grape::Request::DEFAULT_PARAMS_BUILDER` constant - [@eriklovmo](https://github.com/eriklovmo). * [#2558](https://github.com/ruby-grape/grape/pull/2558): Add Ruby's option `enable_frozen_string_literal` in CI - [@ericproulx](https://github.com/ericproulx). +* [#2557](https://github.com/ruby-grape/grape/pull/2557): Add lint! - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes diff --git a/README.md b/README.md index 7fe411835..863a8d3f3 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ - [Header](#header) - [Accept-Version Header](#accept-version-header) - [Param](#param) +- [Linting](#linting) + - [Bug in Rack::ETag under Rack 3.X](#bug-in-racketag-under-rack-3x) - [Describing Methods](#describing-methods) - [Configuration](#configuration) - [Parameters](#parameters) @@ -650,6 +652,27 @@ version 'v1', using: :param, parameter: 'v' curl http://localhost:9292/statuses/public_timeline?v=v1 +## Linting + +You can check whether your API is in conformance with the [Rack's specification](https://github.com/rack/rack/blob/main/SPEC.rdoc) by calling `lint!` at the API level or through [configuration](#configuration). + +```ruby +class Api < Grape::API + lint! +end +``` +```ruby +Grape.configure do |config| + config.lint = true +end +``` +```ruby +Grape.config.lint = true +``` + +### Bug in Rack::ETag under Rack 3.X +If you're using Rack 3.X and the `Rack::Etag` middleware (used by [Rails](https://guides.rubyonrails.org/rails_on_rack.html#inspecting-middleware-stack)), a [bug](https://github.com/rack/rack/pull/2324) related to linting has been fixed in [3.1.13](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3113---2025-04-13) and [3.0.15](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3015---2025-04-13) respectively. + ## Describing Methods You can add a description to API methods and namespaces. The description would be used by [grape-swagger][grape-swagger] to generate swagger compliant documentation. diff --git a/UPGRADING.md b/UPGRADING.md index 2ace6622d..8c1744959 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -130,7 +130,7 @@ When using together with `Grape::Extensions::Hash::ParamBuilder`, `route_param` This was a regression introduced by [#2326](https://github.com/ruby-grape/grape/pull/2326) in Grape v1.8.0. ```ruby -grape.configure do |config| +Grape.configure do |config| config.param_builder = Grape::Extensions::Hash::ParamBuilder end diff --git a/docker/Dockerfile b/docker/Dockerfile index 2bc484ecf..5cca3aa5b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -7,7 +7,7 @@ ENV RUBYOPT --enable-frozen-string-literal --yjit ENV LD_PRELOAD libjemalloc.so.2 ENV MALLOC_CONF dirty_decay_ms:1000,narenas:2,background_thread:true -RUN apk add --update --no-cache make gcc git libc-dev gcompat jemalloc && \ +RUN apk add --update --no-cache make gcc git libc-dev yaml-dev gcompat jemalloc && \ gem update --system && gem install bundler WORKDIR $LIB_PATH diff --git a/lib/grape.rb b/lib/grape.rb index a1e6f511c..4dfffc257 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -75,6 +75,7 @@ def self.deprecator configure do |config| config.param_builder = :hash_with_indifferent_access + config.lint = false config.compile_methods! end end diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index 8c32db364..4db89e5d3 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -81,6 +81,10 @@ def do_not_route_options! namespace_inheritable(:do_not_route_options, true) end + def lint! + namespace_inheritable(:lint, true) + end + def do_not_document! namespace_inheritable(:do_not_document, true) end diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 2e5136fda..e3bd6b61f 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -358,6 +358,7 @@ def build_stack(helpers) format = namespace_inheritable(:format) stack.use Rack::Head + stack.use Rack::Lint if lint? stack.use Class.new(Grape::Middleware::Error), helpers: helpers, format: format, @@ -408,5 +409,9 @@ def build_response_cookies Rack::Utils.set_cookie_header! header, name, cookie_value end end + + def lint? + namespace_inheritable(:lint) || Grape.config.lint + end end end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index f16b31ee2..b2fa81e75 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -4738,4 +4738,28 @@ def uniqe_id_route expect(last_response.body).to eq('unknown params_builder: unknown') end end + + describe '.lint!' do + let(:app) do + Class.new(described_class) do + lint! + get '/' do + status 42 + end + end + end + + around do |example| + @lint = Grape.config.lint + Grape.config.lint = false + example.run + ensure + Grape.config.lint = @lint + end + + it 'raises a Rack::Lint error' do + # Status must be an Integer >= 100 + expect { get '/' }.to raise_error(Rack::Lint::LintError) + end + end end diff --git a/spec/integration/rails/mounting_spec.rb b/spec/integration/rails/mounting_spec.rb index 3d8288b93..39d31c761 100644 --- a/spec/integration/rails/mounting_spec.rb +++ b/spec/integration/rails/mounting_spec.rb @@ -4,6 +4,7 @@ context 'rails mounted' do let(:api) do Class.new(Grape::API) do + lint! get('/test_grape') { 'rails mounted' } end end @@ -28,7 +29,7 @@ mount GrapeApi => '/' get 'up', to: lambda { |_env| - ['200', {}, ['hello world']] + [200, {}, ['hello world']] } end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 06cb282a0..4a8e2f1b0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,7 @@ end end +Grape.config.lint = true # lint all apis by default Grape::Util::Registry.include(Deregister) # issue with ruby 2.7 with ^. We need to extend it again Grape::Validations.extend(Grape::Util::Registry) if Gem::Version.new(RUBY_VERSION).release < Gem::Version.new('3.0')