Skip to content

Commit

Permalink
Add the contract DSL (#2419)
Browse files Browse the repository at this point in the history
Add the `contract` DSL

Resolves #2386
  • Loading branch information
dgutov authored Mar 24, 2024
1 parent c6ad84a commit 7ec1f51
Show file tree
Hide file tree
Showing 25 changed files with 440 additions and 17 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ jobs:
matrix:
ruby: ['2.7', '3.0', '3.1', '3.2', '3.3']
gemfile: [rack_2_0, rack_3_0, rails_6_0, rails_6_1, rails_7_0, rails_7_1]
integration_only: [false]
include:
- ruby: '2.7'
gemfile: rack_1_0
- ruby: '2.7'
gemfile: multi_json
- ruby: '2.7'
gemfile: multi_xml
- ruby: '3.3'
gemfile: no_dry_validation
integration_only: true
runs-on: ubuntu-latest
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
Expand All @@ -45,6 +49,7 @@ jobs:
bundler-cache: true

- name: Run tests
if: ${{ matrix.integration_only == false }}
run: bundle exec rake spec

- name: Run tests (spec/integration/eager_load)
Expand All @@ -70,6 +75,10 @@ jobs:
if: ${{ matrix.gemfile == 'rack_3_0' }}
run: bundle exec rspec spec/integration/rack/v3

- name: Run tests (spec/integration/no_dry_validation)
if: ${{ matrix.gemfile == 'no_dry_validation' }}
run: bundle exec rspec spec/integration/no_dry_validation

- name: Coveralls
uses: coverallsapp/github-action@master
with:
Expand Down
10 changes: 7 additions & 3 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ RSpec/ExpectInHook:
- 'spec/grape/api_spec.rb'
- 'spec/grape/validations/validators/values_spec.rb'

# Offense count: 47
# Offense count: 48
# Configuration parameters: Include, CustomTransform, IgnoreMethods, SpecSuffixOnly.
# Include: **/*_spec*rb*, **/spec/**/*
RSpec/FilePath:
Expand Down Expand Up @@ -278,6 +278,7 @@ RSpec/FilePath:
- 'spec/integration/eager_load/eager_load_spec.rb'
- 'spec/integration/multi_json/json_spec.rb'
- 'spec/integration/multi_xml/xml_spec.rb'
- 'spec/integration/no_dry_validation/no_dry_validation_spec.rb'
- 'spec/integration/rack/v2/headers_spec.rb'
- 'spec/integration/rack/v3/headers_spec.rb'

Expand Down Expand Up @@ -341,7 +342,7 @@ RSpec/MissingExampleGroupArgument:
Exclude:
- 'spec/grape/middleware/exception_spec.rb'

# Offense count: 788
# Offense count: 804
# Configuration parameters: Max.
RSpec/MultipleExpectations:
Exclude:
Expand Down Expand Up @@ -392,6 +393,7 @@ RSpec/MultipleExpectations:
- 'spec/grape/util/reverse_stackable_values_spec.rb'
- 'spec/grape/util/stackable_values_spec.rb'
- 'spec/grape/validations/attributes_doc_spec.rb'
- 'spec/grape/validations/contract_scope_spec.rb'
- 'spec/grape/validations/instance_behaivour_spec.rb'
- 'spec/grape/validations/params_scope_spec.rb'
- 'spec/grape/validations/types/array_coercer_spec.rb'
Expand All @@ -411,6 +413,7 @@ RSpec/MultipleExpectations:
- 'spec/grape/validations/validators/same_as_spec.rb'
- 'spec/grape/validations/validators/values_spec.rb'
- 'spec/grape/validations_spec.rb'
- 'spec/integration/no_dry_validation/no_dry_validation_spec.rb'
- 'spec/shared/versioning_examples.rb'

# Offense count: 38
Expand Down Expand Up @@ -557,7 +560,7 @@ RSpec/ScatteredSetup:
- 'spec/grape/util/inheritable_setting_spec.rb'
- 'spec/grape/validations_spec.rb'

# Offense count: 47
# Offense count: 48
# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata.
# Include: **/*_spec.rb
RSpec/SpecFilePathFormat:
Expand Down Expand Up @@ -608,6 +611,7 @@ RSpec/SpecFilePathFormat:
- 'spec/integration/eager_load/eager_load_spec.rb'
- 'spec/integration/multi_json/json_spec.rb'
- 'spec/integration/multi_xml/xml_spec.rb'
- 'spec/integration/no_dry_validation/no_dry_validation_spec.rb'
- 'spec/integration/rack/v2/headers_spec.rb'
- 'spec/integration/rack/v3/headers_spec.rb'

Expand Down
33 changes: 24 additions & 9 deletions Appraisals
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
# frozen_string_literal: true

appraise 'rails-5' do
gem 'rails', '~> 5.2'
customize_gemfiles do
{
single_quotes: true,
heading: "frozen_string_literal: true
This file was generated by Appraisal"
}
end

appraise 'rails-6' do
appraise 'rails-6-0' do
gem 'rails', '~> 6.0.0'
end

appraise 'rails-6-1' do
gem 'rails', '~> 6.1'
end

appraise 'rails-7' do
gem 'rails', '~> 7.0'
appraise 'rails-7-0' do
gem 'rails', '~> 7.0.0'
end

appraise 'rails-7-1' do
gem 'rails', '~> 7.1.0'
end

appraise 'rails-edge' do
Expand All @@ -32,14 +41,20 @@ appraise 'multi_xml' do
gem 'multi_xml', require: 'multi_xml'
end

appraise 'rack1' do
appraise 'rack_1_0' do
gem 'rack', '~> 1.0'
end

appraise 'rack2' do
gem 'rack', '~> 2.0.0'
appraise 'rack_2_0' do
gem 'rack', '~> 2.0'
end

appraise 'rack3' do
appraise 'rack_3_0' do
gem 'rack', '~> 3.0.0'
end

appraise 'no_dry_validation' do
group :development, :test do
remove_gem 'dry-validation'
end
end
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#### Features

* [#2419](https://github.com/ruby-grape/grape/pull/2419): Add the `contract` DSL - [@dgutov](https://github.com/dgutov).
* [#2371](https://github.com/ruby-grape/grape/pull/2371): Use a param value as the `default` value of other param - [@jcagarcia](https://github.com/jcagarcia).
* [#2377](https://github.com/ruby-grape/grape/pull/2377): Allow to use instance variables values inside `rescue_from` - [@jcagarcia](https://github.com/jcagarcia).
* [#2379](https://github.com/ruby-grape/grape/pull/2379): Take into account the `route_param` type in `recognize_path` - [@jcagarcia](https://github.com/jcagarcia).
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gemspec

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
- [Pass symbols for i18n translations](#pass-symbols-for-i18n-translations)
- [Overriding Attribute Names](#overriding-attribute-names)
- [With Default](#with-default)
- [Using dry-validation or dry-schema](#using-dry-validation-or-dry-schema)
- [Headers](#headers)
- [Request](#request)
- [Header Case Handling](#header-case-handling)
Expand Down Expand Up @@ -2086,6 +2087,40 @@ params do
end
```

### Using `dry-validation` or `dry-schema`

As an alternative to the `params` DSL described above, you can use a schema or `dry-validation` contract to describe an endpoint's parameters. This can be especially useful if you use the above already in some other parts of your application. If not, you'll need to add `dry-validation` or `dry-schema` to your `Gemfile`.

Then call `contract` with a contract or schema defined previously:

```rb
CreateOrdersSchema = Dry::Schema.Params do
required(:orders).array(:hash) do
required(:name).filled(:string)
optional(:volume).maybe(:integer, lt?: 9)
end
end

# ...

contract CreateOrdersSchema
```

or with a block, using the [schema definition syntax](https://dry-rb.org/gems/dry-schema/1.13/#quick-start):

```rb
contract do
required(:orders).array(:hash) do
required(:name).filled(:string)
optional(:volume).maybe(:integer, lt?: 9)
end
end
```

The latter will define a coercing schema (`Dry::Schema.Params`). When using the former approach, it's up to you to decide whether the input will need coercing.

The `params` and `contract` declarations can also be used together in the same API, e.g. to describe different parts of a nested namespace for an endpoint.

## Headers

### Request
Expand Down
1 change: 1 addition & 0 deletions gemfiles/multi_json.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'multi_json', require: 'multi_json'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
1 change: 1 addition & 0 deletions gemfiles/multi_xml.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'multi_xml', require: 'multi_xml'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
40 changes: 40 additions & 0 deletions gemfiles/no_dry_validation.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

# This file was generated by Appraisal

source 'https://rubygems.org'

group :development, :test do
gem 'bundler'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
gem 'rubocop-performance', '1.20.1', require: false
gem 'rubocop-rspec', '2.25.0', require: false
end

group :development do
gem 'appraisal'
gem 'benchmark-ips'
gem 'benchmark-memory'
gem 'guard'
gem 'guard-rspec'
gem 'guard-rubocop'
end

group :test do
gem 'grape-entity', '~> 0.6', require: false
gem 'rack-jsonp', require: 'rack/jsonp'
gem 'rack-test', '< 2.1'
gem 'rspec', '< 4'
gem 'ruby-grape-danger', '~> 0.2.0', require: false
gem 'simplecov', '~> 0.21.2'
gem 'simplecov-lcov', '~> 0.8.0'
gem 'test-prof', require: false
end

platforms :jruby do
gem 'racc'
end

gemspec path: '../'
1 change: 1 addition & 0 deletions gemfiles/rack_1_0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'rack', '~> 1.0'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rack_2_0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'rack', '~> 2.0'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rack_3_0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'rack', '~> 3.0.0'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rack_edge.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'rack', github: 'rack/rack'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rails_6_0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'rails', '~> 6.0.0'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rails_6_1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'rails', '~> 6.1'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rails_7_0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'rails', '~> 7.0.0'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/rails_7_1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
source 'https://rubygems.org'

gem 'rails', '~> 7.1.0'
gem 'tzinfo-data', require: false

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rails_edge.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'rails', github: 'rails/rails'

group :development, :test do
gem 'bundler'
gem 'dry-validation'
gem 'hashie'
gem 'rake'
gem 'rubocop', '1.59.0', require: false
Expand Down
1 change: 1 addition & 0 deletions lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ module Validations
autoload :SingleAttributeIterator
autoload :Types
autoload :ParamsScope
autoload :ContractScope
autoload :ValidatorFactory
autoload :Base, 'grape/validations/validators/base'
end
Expand Down
14 changes: 10 additions & 4 deletions lib/grape/dsl/inside_route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,17 @@ def declared(passed_params, options = {}, declared_params = nil, params_nested_p
options = options.reverse_merge(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
declared_params ||= optioned_declared_params(**options)

if passed_params.is_a?(Array)
declared_array(passed_params, options, declared_params, params_nested_path)
else
declared_hash(passed_params, options, declared_params, params_nested_path)
res = if passed_params.is_a?(Array)
declared_array(passed_params, options, declared_params, params_nested_path)
else
declared_hash(passed_params, options, declared_params, params_nested_path)
end

if (key_maps = namespace_stackable(:contract_key_map))
key_maps.each { |key_map| key_map.write(passed_params, res) }
end

res
end

private
Expand Down
Loading

0 comments on commit 7ec1f51

Please sign in to comment.