grape 1.3.2 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/LICENSE +1 -1
- data/README.md +18 -4
- data/UPGRADING.md +59 -0
- data/lib/grape/api/instance.rb +5 -4
- data/lib/grape/dsl/inside_route.rb +37 -14
- data/lib/grape/router.rb +1 -10
- data/lib/grape/router/attribute_translator.rb +23 -2
- data/lib/grape/router/route.rb +1 -19
- data/lib/grape/validations/types/array_coercer.rb +14 -5
- data/lib/grape/validations/types/build_coercer.rb +1 -5
- data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
- data/lib/grape/validations/types/primitive_coercer.rb +9 -2
- data/lib/grape/validations/types/set_coercer.rb +6 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
- data/lib/grape/validations/validators/coerce.rb +1 -10
- data/lib/grape/validations/validators/default.rb +0 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/instance_spec.rb +50 -0
- data/spec/grape/endpoint_spec.rb +18 -5
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +5 -1
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/validators/coerce_spec.rb +73 -13
- data/spec/grape/validations/validators/default_spec.rb +121 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +5 -5
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d63fb79e412ead32064ad4994e171e65962131a04afb20d12957942c744f4df8
|
4
|
+
data.tar.gz: 9a9f4fb654e346eabb8a0b902d5e49ae54dc567797789c98c76f2a2fe2e2daa9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b75afa355e6a2b8200d72a2c4407bc78646648832a94d576bff06bdebde90d54b1897df09a560665bd9f58e735f9832a110b63bbcaabe14a4e2ee3c97e743b57
|
7
|
+
data.tar.gz: 18a4ea057230ae0e6cfe16f92e5043339b66026a94d29bd8c897d2482577cb279ec4e202f41a643605a3ea09c748fa159fddc36977a3c4828b5b64daef080272
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,24 @@
|
|
1
|
+
### 1.3.3 (2020/05/23)
|
2
|
+
|
3
|
+
#### Features
|
4
|
+
|
5
|
+
* [#2048](https://github.com/ruby-grape/grape/issues/2034): Grape Enterprise support is now available [via TideLift](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) - [@dblock](https://github.com/dblock).
|
6
|
+
* [#2039](https://github.com/ruby-grape/grape/pull/2039): Travis - update rails versions - [@ericproulx](https://github.com/ericproulx).
|
7
|
+
* [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx).
|
8
|
+
* [#2050](https://github.com/ruby-grape/grape/pull/2050): Refactor route public_send to AttributeTranslator - [@ericproulx](https://github.com/ericproulx).
|
9
|
+
|
10
|
+
#### Fixes
|
11
|
+
|
12
|
+
* [#2049](https://github.com/ruby-grape/grape/pull/2049): Coerce an empty string to nil in case of the bool type - [@dnesteryuk](https://github.com/dnesteryuk).
|
13
|
+
* [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami).
|
14
|
+
* [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx).
|
15
|
+
* [#2054](https://github.com/ruby-grape/grape/pull/2054): Coercing of nested arrays - [@dnesteryuk](https://github.com/dnesteryuk).
|
16
|
+
* [#2050](https://github.com/ruby-grape/grape/pull/2053): Fix broken multiple mounts - [@Jack12816](https://github.com/Jack12816).
|
17
|
+
|
1
18
|
### 1.3.2 (2020/04/12)
|
2
19
|
|
3
20
|
#### Features
|
21
|
+
|
4
22
|
* [#2020](https://github.com/ruby-grape/grape/pull/2020): Reduce array allocation - [@ericproulx](https://github.com/ericproulx).
|
5
23
|
* [#2015](https://github.com/ruby-grape/grape/pull/2014): Reduce MatchData allocation - [@ericproulx](https://github.com/ericproulx).
|
6
24
|
* [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx).
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2010-
|
1
|
+
Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors.
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
data/README.md
CHANGED
@@ -12,6 +12,7 @@
|
|
12
12
|
- [What is Grape?](#what-is-grape)
|
13
13
|
- [Stable Release](#stable-release)
|
14
14
|
- [Project Resources](#project-resources)
|
15
|
+
- [Grape for Enterprise](#grape-for-enterprise)
|
15
16
|
- [Installation](#installation)
|
16
17
|
- [Basic Usage](#basic-usage)
|
17
18
|
- [Mounting](#mounting)
|
@@ -141,6 +142,7 @@
|
|
141
142
|
- [format_response.grape](#format_responsegrape)
|
142
143
|
- [Monitoring Products](#monitoring-products)
|
143
144
|
- [Contributing to Grape](#contributing-to-grape)
|
145
|
+
- [Security](#security)
|
144
146
|
- [License](#license)
|
145
147
|
- [Copyright](#copyright)
|
146
148
|
|
@@ -154,7 +156,7 @@ content negotiation, versioning and much more.
|
|
154
156
|
|
155
157
|
## Stable Release
|
156
158
|
|
157
|
-
You're reading the documentation for the stable release of Grape, 1.3.
|
159
|
+
You're reading the documentation for the stable release of Grape, **1.3.3**.
|
158
160
|
|
159
161
|
## Project Resources
|
160
162
|
|
@@ -163,6 +165,14 @@ You're reading the documentation for the stable release of Grape, 1.3.2.
|
|
163
165
|
* Need help? Try [Grape Google Group](http://groups.google.com/group/ruby-grape) or [Gitter](https://gitter.im/ruby-grape/grape)
|
164
166
|
* [Follow us on Twitter](https://twitter.com/grapeframework)
|
165
167
|
|
168
|
+
## Grape for Enterprise
|
169
|
+
|
170
|
+
Available as part of the Tidelift Subscription.
|
171
|
+
|
172
|
+
The maintainers of Grape are working with Tidelift to deliver commercial support and maintenance. Save time, reduce risk, and improve code health, while paying the maintainers of Grape. Click [here](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) for more details.
|
173
|
+
|
174
|
+
In 2020, we plan to use the money towards gathering Grape contributors for dinner in New York City.
|
175
|
+
|
166
176
|
## Installation
|
167
177
|
|
168
178
|
Ruby 2.4 or newer is required.
|
@@ -3850,7 +3860,7 @@ Grape integrates with following third-party tools:
|
|
3850
3860
|
* **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem
|
3851
3861
|
* **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/)
|
3852
3862
|
* **[AppSignal](https://www.appsignal.com)** - [appsignal-ruby](https://github.com/appsignal/appsignal-ruby) gem, [documentation](http://docs.appsignal.com/getting-started/supported-frameworks.html#grape)
|
3853
|
-
* **[ElasticAPM](https://www.elastic.co/products/apm) - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape)
|
3863
|
+
* **[ElasticAPM](https://www.elastic.co/products/apm)** - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape)
|
3854
3864
|
|
3855
3865
|
## Contributing to Grape
|
3856
3866
|
|
@@ -3859,10 +3869,14 @@ features and discuss issues.
|
|
3859
3869
|
|
3860
3870
|
See [CONTRIBUTING](CONTRIBUTING.md).
|
3861
3871
|
|
3872
|
+
## Security
|
3873
|
+
|
3874
|
+
See [SECURITY](SECURITY.md) for details.
|
3875
|
+
|
3862
3876
|
## License
|
3863
3877
|
|
3864
|
-
MIT License. See LICENSE for details.
|
3878
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
3865
3879
|
|
3866
3880
|
## Copyright
|
3867
3881
|
|
3868
|
-
Copyright (c) 2010-
|
3882
|
+
Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors.
|
data/UPGRADING.md
CHANGED
@@ -1,6 +1,65 @@
|
|
1
1
|
Upgrading Grape
|
2
2
|
===============
|
3
3
|
|
4
|
+
### Upgrading to >= 1.4.0
|
5
|
+
|
6
|
+
#### Nil values for structures
|
7
|
+
|
8
|
+
Nil values always been a special case when dealing with types especially with the following structures:
|
9
|
+
- Array
|
10
|
+
- Hash
|
11
|
+
- Set
|
12
|
+
|
13
|
+
The behaviour for these structures has change through out the latest releases. For instance:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class Api < Grape::API
|
17
|
+
params do
|
18
|
+
require :my_param, type: Array[Integer]
|
19
|
+
end
|
20
|
+
|
21
|
+
get 'example' do
|
22
|
+
params[:my_param]
|
23
|
+
end
|
24
|
+
get '/example', params: { my_param: nil }
|
25
|
+
# 1.3.1 = []
|
26
|
+
# 1.3.2 = nil
|
27
|
+
end
|
28
|
+
```
|
29
|
+
For now on, `nil` values stay `nil` values for all types, including arrays, sets and hashes.
|
30
|
+
|
31
|
+
If you want to have the same behavior as 1.3.1, apply a `default` validator
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class Api < Grape::API
|
35
|
+
params do
|
36
|
+
require :my_param, type: Array[Integer], default: []
|
37
|
+
end
|
38
|
+
|
39
|
+
get 'example' do
|
40
|
+
params[:my_param]
|
41
|
+
end
|
42
|
+
get '/example', params: { my_param: nil } # => []
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
#### Default validator
|
47
|
+
|
48
|
+
Default validator is now applied for `nil` values.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class Api < Grape::API
|
52
|
+
params do
|
53
|
+
requires :my_param, type: Integer, default: 0
|
54
|
+
end
|
55
|
+
|
56
|
+
get 'example' do
|
57
|
+
params[:my_param]
|
58
|
+
end
|
59
|
+
get '/example', params: { my_param: nil } #=> before: nil, after: 0
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
4
63
|
### Upgrading to >= 1.3.0
|
5
64
|
|
6
65
|
#### Ruby
|
data/lib/grape/api/instance.rb
CHANGED
@@ -205,11 +205,12 @@ module Grape
|
|
205
205
|
route_settings[:requirements] = route.requirements
|
206
206
|
route_settings[:path] = route.origin
|
207
207
|
route_settings[:methods] ||= []
|
208
|
-
route_settings[:methods]
|
208
|
+
if route.request_method == '*' || route_settings[:methods].include?('*')
|
209
|
+
route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS
|
210
|
+
else
|
211
|
+
route_settings[:methods] << route.request_method
|
212
|
+
end
|
209
213
|
route_settings[:endpoint] = route.app
|
210
|
-
|
211
|
-
# using the :any shorthand produces [nil] for route methods, substitute all manually
|
212
|
-
route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS if route_settings[:methods].include?('*')
|
213
214
|
end
|
214
215
|
end
|
215
216
|
|
@@ -28,36 +28,38 @@ module Grape
|
|
28
28
|
# Methods which should not be available in filters until the before filter
|
29
29
|
# has completed
|
30
30
|
module PostBeforeFilter
|
31
|
-
def declared(passed_params, options = {}, declared_params = nil)
|
31
|
+
def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
|
32
32
|
options = options.reverse_merge(include_missing: true, include_parent_namespaces: true)
|
33
33
|
declared_params ||= optioned_declared_params(**options)
|
34
34
|
|
35
35
|
if passed_params.is_a?(Array)
|
36
|
-
declared_array(passed_params, options, declared_params)
|
36
|
+
declared_array(passed_params, options, declared_params, params_nested_path)
|
37
37
|
else
|
38
|
-
declared_hash(passed_params, options, declared_params)
|
38
|
+
declared_hash(passed_params, options, declared_params, params_nested_path)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
-
def declared_array(passed_params, options, declared_params)
|
44
|
+
def declared_array(passed_params, options, declared_params, params_nested_path)
|
45
45
|
passed_params.map do |passed_param|
|
46
|
-
declared(passed_param || {}, options, declared_params)
|
46
|
+
declared(passed_param || {}, options, declared_params, params_nested_path)
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
def declared_hash(passed_params, options, declared_params)
|
50
|
+
def declared_hash(passed_params, options, declared_params, params_nested_path)
|
51
51
|
declared_params.each_with_object(passed_params.class.new) do |declared_param, memo|
|
52
52
|
if declared_param.is_a?(Hash)
|
53
53
|
declared_param.each_pair do |declared_parent_param, declared_children_params|
|
54
|
+
params_nested_path_dup = params_nested_path.dup
|
55
|
+
params_nested_path_dup << declared_parent_param.to_s
|
54
56
|
next unless options[:include_missing] || passed_params.key?(declared_parent_param)
|
55
57
|
|
56
58
|
passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
|
57
59
|
memo_key = optioned_param_key(declared_parent_param, options)
|
58
60
|
|
59
|
-
memo[memo_key] = handle_passed_param(
|
60
|
-
declared(passed_children_params, options, declared_children_params)
|
61
|
+
memo[memo_key] = handle_passed_param(passed_children_params, params_nested_path_dup) do
|
62
|
+
declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
|
61
63
|
end
|
62
64
|
end
|
63
65
|
else
|
@@ -77,19 +79,34 @@ module Grape
|
|
77
79
|
end
|
78
80
|
end
|
79
81
|
|
80
|
-
def handle_passed_param(
|
81
|
-
|
82
|
+
def handle_passed_param(passed_children_params, params_nested_path, &_block)
|
83
|
+
if should_be_empty_hash?(passed_children_params, params_nested_path)
|
84
|
+
{}
|
85
|
+
elsif should_be_empty_array?(passed_children_params, params_nested_path)
|
86
|
+
[]
|
87
|
+
else
|
88
|
+
yield
|
89
|
+
end
|
82
90
|
end
|
83
91
|
|
84
|
-
def should_be_empty_array?(
|
85
|
-
|
92
|
+
def should_be_empty_array?(passed_children_params, params_nested_path)
|
93
|
+
passed_children_params.empty? && declared_param_is_array?(params_nested_path)
|
86
94
|
end
|
87
95
|
|
88
|
-
def declared_param_is_array?(
|
89
|
-
key =
|
96
|
+
def declared_param_is_array?(params_nested_path)
|
97
|
+
key = route_options_params_key(params_nested_path)
|
90
98
|
route_options_params[key] && route_options_params[key][:type] == 'Array'
|
91
99
|
end
|
92
100
|
|
101
|
+
def should_be_empty_hash?(passed_children_params, params_nested_path)
|
102
|
+
passed_children_params.empty? && declared_param_is_hash?(params_nested_path)
|
103
|
+
end
|
104
|
+
|
105
|
+
def declared_param_is_hash?(params_nested_path)
|
106
|
+
key = route_options_params_key(params_nested_path)
|
107
|
+
route_options_params[key] && route_options_params[key][:type] == 'Hash'
|
108
|
+
end
|
109
|
+
|
93
110
|
def route_options_params
|
94
111
|
options[:route_options][:params] || {}
|
95
112
|
end
|
@@ -98,6 +115,12 @@ module Grape
|
|
98
115
|
options[:stringify] ? declared_param.to_s : declared_param.to_sym
|
99
116
|
end
|
100
117
|
|
118
|
+
def route_options_params_key(params_nested_path)
|
119
|
+
key = params_nested_path[0]
|
120
|
+
key += '[' + params_nested_path[1..-1].join('][') + ']' if params_nested_path.size > 1
|
121
|
+
key
|
122
|
+
end
|
123
|
+
|
101
124
|
def optioned_declared_params(**options)
|
102
125
|
declared_params = if options[:include_parent_namespaces]
|
103
126
|
# Declared params including parent namespaces
|
data/lib/grape/router.rb
CHANGED
@@ -7,15 +7,6 @@ module Grape
|
|
7
7
|
class Router
|
8
8
|
attr_reader :map, :compiled
|
9
9
|
|
10
|
-
class Any < AttributeTranslator
|
11
|
-
attr_reader :pattern, :index
|
12
|
-
def initialize(pattern, index, **attributes)
|
13
|
-
@pattern = pattern
|
14
|
-
@index = index
|
15
|
-
super(attributes)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
10
|
class NormalizePathCache < Grape::Util::Cache
|
20
11
|
def initialize
|
21
12
|
@cache = Hash.new do |h, path|
|
@@ -64,7 +55,7 @@ module Grape
|
|
64
55
|
|
65
56
|
def associate_routes(pattern, **options)
|
66
57
|
@neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
|
67
|
-
@neutral_map <<
|
58
|
+
@neutral_map << Grape::Router::AttributeTranslator.new(options.merge(pattern: pattern, index: @neutral_map.length))
|
68
59
|
end
|
69
60
|
|
70
61
|
def call(env)
|
@@ -6,10 +6,31 @@ module Grape
|
|
6
6
|
class AttributeTranslator
|
7
7
|
attr_reader :attributes, :request_method, :requirements
|
8
8
|
|
9
|
+
ROUTE_ATTRIBUTES = %i[
|
10
|
+
prefix
|
11
|
+
version
|
12
|
+
settings
|
13
|
+
format
|
14
|
+
description
|
15
|
+
http_codes
|
16
|
+
headers
|
17
|
+
entity
|
18
|
+
details
|
19
|
+
requirements
|
20
|
+
request_method
|
21
|
+
namespace
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
ROUTER_ATTRIBUTES = %i[pattern index].freeze
|
25
|
+
|
9
26
|
def initialize(attributes = {})
|
10
27
|
@attributes = attributes
|
11
|
-
|
12
|
-
|
28
|
+
end
|
29
|
+
|
30
|
+
(ROUTER_ATTRIBUTES + ROUTE_ATTRIBUTES).each do |attr|
|
31
|
+
define_method attr do
|
32
|
+
attributes[attr]
|
33
|
+
end
|
13
34
|
end
|
14
35
|
|
15
36
|
def to_h
|
data/lib/grape/router/route.rb
CHANGED
@@ -18,6 +18,7 @@ module Grape
|
|
18
18
|
|
19
19
|
extend Forwardable
|
20
20
|
def_delegators :pattern, :path, :origin
|
21
|
+
delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes
|
21
22
|
|
22
23
|
def method_missing(method_id, *arguments)
|
23
24
|
match = ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
|
@@ -34,25 +35,6 @@ module Grape
|
|
34
35
|
ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
|
35
36
|
end
|
36
37
|
|
37
|
-
%i[
|
38
|
-
prefix
|
39
|
-
version
|
40
|
-
settings
|
41
|
-
format
|
42
|
-
description
|
43
|
-
http_codes
|
44
|
-
headers
|
45
|
-
entity
|
46
|
-
details
|
47
|
-
requirements
|
48
|
-
request_method
|
49
|
-
namespace
|
50
|
-
].each do |method_name|
|
51
|
-
define_method method_name do
|
52
|
-
attributes.public_send method_name
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
38
|
def route_method
|
57
39
|
warn_route_methods(:method, caller(1).shift, :request_method)
|
58
40
|
request_method
|
@@ -6,7 +6,7 @@ module Grape
|
|
6
6
|
module Validations
|
7
7
|
module Types
|
8
8
|
# Coerces elements in an array. It might be an array of strings or integers or
|
9
|
-
#
|
9
|
+
# an array of arrays of integers.
|
10
10
|
#
|
11
11
|
# It could've been possible to use an +of+
|
12
12
|
# method (https://dry-rb.org/gems/dry-types/1.2/array-with-member/)
|
@@ -14,16 +14,17 @@ module Grape
|
|
14
14
|
# behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
|
15
15
|
# maintains Virtus behavior in coercing.
|
16
16
|
class ArrayCoercer < DryTypeCoercer
|
17
|
+
register_collection Array
|
18
|
+
|
17
19
|
def initialize(type, strict = false)
|
18
20
|
super
|
19
21
|
|
20
22
|
@coercer = scope::Array
|
21
|
-
@
|
23
|
+
@subtype = type.first
|
22
24
|
end
|
23
25
|
|
24
26
|
def call(_val)
|
25
27
|
collection = super
|
26
|
-
|
27
28
|
return collection if collection.is_a?(InvalidValue)
|
28
29
|
|
29
30
|
coerce_elements collection
|
@@ -31,11 +32,15 @@ module Grape
|
|
31
32
|
|
32
33
|
protected
|
33
34
|
|
35
|
+
attr_reader :subtype
|
36
|
+
|
34
37
|
def coerce_elements(collection)
|
38
|
+
return if collection.nil?
|
39
|
+
|
35
40
|
collection.each_with_index do |elem, index|
|
36
41
|
return InvalidValue.new if reject?(elem)
|
37
42
|
|
38
|
-
coerced_elem =
|
43
|
+
coerced_elem = elem_coercer.call(elem)
|
39
44
|
|
40
45
|
return coerced_elem if coerced_elem.is_a?(InvalidValue)
|
41
46
|
|
@@ -45,11 +50,15 @@ module Grape
|
|
45
50
|
collection
|
46
51
|
end
|
47
52
|
|
48
|
-
# This method
|
53
|
+
# This method maintains logic which was defined by Virtus for arrays.
|
49
54
|
# Virtus doesn't allow nil in arrays.
|
50
55
|
def reject?(val)
|
51
56
|
val.nil?
|
52
57
|
end
|
58
|
+
|
59
|
+
def elem_coercer
|
60
|
+
@elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict)
|
61
|
+
end
|
53
62
|
end
|
54
63
|
end
|
55
64
|
end
|
@@ -60,12 +60,8 @@ module Grape
|
|
60
60
|
Types::CustomTypeCollectionCoercer.new(
|
61
61
|
Types.map_special(type.first), type.is_a?(Set)
|
62
62
|
)
|
63
|
-
elsif type.is_a?(Array)
|
64
|
-
ArrayCoercer.new type, strict
|
65
|
-
elsif type.is_a?(Set)
|
66
|
-
SetCoercer.new type, strict
|
67
63
|
else
|
68
|
-
|
64
|
+
DryTypeCoercer.coercer_instance_for(type, strict)
|
69
65
|
end
|
70
66
|
end
|
71
67
|
|
@@ -17,8 +17,41 @@ module Grape
|
|
17
17
|
# but check its type. More information there
|
18
18
|
# https://dry-rb.org/gems/dry-types/1.2/built-in-types/
|
19
19
|
class DryTypeCoercer
|
20
|
+
class << self
|
21
|
+
# Registers a collection coercer which could be found by a type,
|
22
|
+
# see +collection_coercer_for+ method below. This method is meant for inheritors.
|
23
|
+
def register_collection(type)
|
24
|
+
DryTypeCoercer.collection_coercers[type] = self
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns a collection coercer which corresponds to a given type.
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# collection_coercer_for(Array)
|
31
|
+
# #=> Grape::Validations::Types::ArrayCoercer
|
32
|
+
def collection_coercer_for(type)
|
33
|
+
collection_coercers[type]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns an instance of a coercer for a given type
|
37
|
+
def coercer_instance_for(type, strict = false)
|
38
|
+
return PrimitiveCoercer.new(type, strict) if type.class == Class
|
39
|
+
|
40
|
+
# in case of a collection (Array[Integer]) the type is an instance of a collection,
|
41
|
+
# so we need to figure out the actual type
|
42
|
+
collection_coercer_for(type.class).new(type, strict)
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def collection_coercers
|
48
|
+
@collection_coercers ||= {}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
20
52
|
def initialize(type, strict = false)
|
21
53
|
@type = type
|
54
|
+
@strict = strict
|
22
55
|
@scope = strict ? DryTypes::Strict : DryTypes::Params
|
23
56
|
end
|
24
57
|
|
@@ -27,6 +60,8 @@ module Grape
|
|
27
60
|
#
|
28
61
|
# @param val [Object]
|
29
62
|
def call(val)
|
63
|
+
return if val.nil?
|
64
|
+
|
30
65
|
@coercer[val]
|
31
66
|
rescue Dry::Types::CoercionError => _e
|
32
67
|
InvalidValue.new
|
@@ -34,7 +69,7 @@ module Grape
|
|
34
69
|
|
35
70
|
protected
|
36
71
|
|
37
|
-
attr_reader :scope, :type
|
72
|
+
attr_reader :scope, :type, :strict
|
38
73
|
end
|
39
74
|
end
|
40
75
|
end
|
@@ -36,7 +36,7 @@ module Grape
|
|
36
36
|
|
37
37
|
def call(val)
|
38
38
|
return InvalidValue.new if reject?(val)
|
39
|
-
return nil if val.nil?
|
39
|
+
return nil if val.nil? || treat_as_nil?(val)
|
40
40
|
return '' if val == ''
|
41
41
|
|
42
42
|
super
|
@@ -46,7 +46,7 @@ module Grape
|
|
46
46
|
|
47
47
|
attr_reader :type
|
48
48
|
|
49
|
-
# This method
|
49
|
+
# This method maintains logic which was defined by Virtus. For example,
|
50
50
|
# dry-types is ok to convert an array or a hash to a string, it is supported,
|
51
51
|
# but Virtus wouldn't accept it. So, this method only exists to not introduce
|
52
52
|
# breaking changes.
|
@@ -55,6 +55,13 @@ module Grape
|
|
55
55
|
(val.is_a?(String) && type == Hash) ||
|
56
56
|
(val.is_a?(Hash) && type == String)
|
57
57
|
end
|
58
|
+
|
59
|
+
# Dry-Types treats an empty string as invalid. However, Grape considers an empty string as
|
60
|
+
# absence of a value and coerces it into nil. See a discussion there
|
61
|
+
# https://github.com/ruby-grape/grape/pull/2045
|
62
|
+
def treat_as_nil?(val)
|
63
|
+
val == '' && type == Grape::API::Boolean
|
64
|
+
end
|
58
65
|
end
|
59
66
|
end
|
60
67
|
end
|
@@ -1,18 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'set'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'array_coercer'
|
5
5
|
|
6
6
|
module Grape
|
7
7
|
module Validations
|
8
8
|
module Types
|
9
9
|
# Takes the given array and converts it to a set. Every element of the set
|
10
10
|
# is also coerced.
|
11
|
-
class SetCoercer <
|
11
|
+
class SetCoercer < ArrayCoercer
|
12
|
+
register_collection Set
|
13
|
+
|
12
14
|
def initialize(type, strict = false)
|
13
15
|
super
|
14
16
|
|
15
|
-
@
|
17
|
+
@coercer = nil
|
16
18
|
end
|
17
19
|
|
18
20
|
def call(value)
|
@@ -25,7 +27,7 @@ module Grape
|
|
25
27
|
|
26
28
|
def coerce_elements(collection)
|
27
29
|
collection.each_with_object(Set.new) do |elem, memo|
|
28
|
-
coerced_elem =
|
30
|
+
coerced_elem = elem_coercer.call(elem)
|
29
31
|
|
30
32
|
return coerced_elem if coerced_elem.is_a?(InvalidValue)
|
31
33
|
|
@@ -67,21 +67,12 @@ module Grape
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def coerce_value(val)
|
70
|
-
|
70
|
+
converter.call(val)
|
71
71
|
# Some custom types might fail, so it should be treated as an invalid value
|
72
72
|
rescue StandardError
|
73
73
|
Types::InvalidValue.new
|
74
74
|
end
|
75
75
|
|
76
|
-
def coerce_nil(val)
|
77
|
-
# define default values for structures, the dry-types lib which is used
|
78
|
-
# for coercion doesn't accept nil as a value, so it would fail
|
79
|
-
return [] if type == Array
|
80
|
-
return Set.new if type == Set
|
81
|
-
return {} if type == Hash
|
82
|
-
val
|
83
|
-
end
|
84
|
-
|
85
76
|
# Type to which the parameter will be coerced.
|
86
77
|
#
|
87
78
|
# @return [Class]
|
data/lib/grape/version.rb
CHANGED
@@ -51,4 +51,54 @@ describe Grape::API::Instance do
|
|
51
51
|
expect(an_instance.top_level_setting.parent).to be_nil
|
52
52
|
end
|
53
53
|
end
|
54
|
+
|
55
|
+
context 'with multiple moutes' do
|
56
|
+
let(:first) do
|
57
|
+
Class.new(Grape::API::Instance) do
|
58
|
+
namespace(:some_namespace) do
|
59
|
+
route :any, '*path' do
|
60
|
+
error!('Not found! (1)', 404)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
let(:second) do
|
66
|
+
Class.new(Grape::API::Instance) do
|
67
|
+
namespace(:another_namespace) do
|
68
|
+
route :any, '*path' do
|
69
|
+
error!('Not found! (2)', 404)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
let(:root_api) do
|
75
|
+
first_instance = first
|
76
|
+
second_instance = second
|
77
|
+
Class.new(Grape::API) do
|
78
|
+
mount first_instance
|
79
|
+
mount first_instance
|
80
|
+
mount second_instance
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'does not raise a FrozenError on first instance' do
|
85
|
+
expect { patch '/some_namespace/anything' }.not_to \
|
86
|
+
raise_error
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'responds the correct body at the first instance' do
|
90
|
+
patch '/some_namespace/anything'
|
91
|
+
expect(last_response.body).to eq 'Not found! (1)'
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'does not raise a FrozenError on second instance' do
|
95
|
+
expect { get '/another_namespace/other' }.not_to \
|
96
|
+
raise_error
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'responds the correct body at the second instance' do
|
100
|
+
get '/another_namespace/foobar'
|
101
|
+
expect(last_response.body).to eq 'Not found! (2)'
|
102
|
+
end
|
103
|
+
end
|
54
104
|
end
|
data/spec/grape/endpoint_spec.rb
CHANGED
@@ -296,9 +296,12 @@ describe Grape::Endpoint do
|
|
296
296
|
optional :seventh
|
297
297
|
end
|
298
298
|
end
|
299
|
+
optional :nested_arr, type: Array do
|
300
|
+
optional :eighth
|
301
|
+
end
|
299
302
|
end
|
300
|
-
optional :
|
301
|
-
optional :
|
303
|
+
optional :arr, type: Array do
|
304
|
+
optional :nineth
|
302
305
|
end
|
303
306
|
end
|
304
307
|
end
|
@@ -390,7 +393,7 @@ describe Grape::Endpoint do
|
|
390
393
|
|
391
394
|
get '/declared?first=present&nested[fourth]=1'
|
392
395
|
expect(last_response.status).to eq(200)
|
393
|
-
expect(JSON.parse(last_response.body)['nested'].keys.size).to eq
|
396
|
+
expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 4
|
394
397
|
end
|
395
398
|
|
396
399
|
it 'builds nested params when given array' do
|
@@ -421,7 +424,7 @@ describe Grape::Endpoint do
|
|
421
424
|
|
422
425
|
get '/declared?first=present'
|
423
426
|
expect(last_response.status).to eq(200)
|
424
|
-
expect(JSON.parse(last_response.body)['nested']).to
|
427
|
+
expect(JSON.parse(last_response.body)['nested']).to eq({})
|
425
428
|
end
|
426
429
|
|
427
430
|
it 'to be an array when include_missing is true' do
|
@@ -431,7 +434,17 @@ describe Grape::Endpoint do
|
|
431
434
|
|
432
435
|
get '/declared?first=present'
|
433
436
|
expect(last_response.status).to eq(200)
|
434
|
-
expect(JSON.parse(last_response.body)['
|
437
|
+
expect(JSON.parse(last_response.body)['arr']).to be_a(Array)
|
438
|
+
end
|
439
|
+
|
440
|
+
it 'to be an array when nested and include_missing is true' do
|
441
|
+
subject.get '/declared' do
|
442
|
+
declared(params, include_missing: true)
|
443
|
+
end
|
444
|
+
|
445
|
+
get '/declared?first=present&nested[fourth]=1'
|
446
|
+
expect(last_response.status).to eq(200)
|
447
|
+
expect(JSON.parse(last_response.body)['nested']['nested_arr']).to be_a(Array)
|
435
448
|
end
|
436
449
|
|
437
450
|
it 'to be nil when include_missing is false' do
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Grape::Validations::Types::ArrayCoercer do
|
6
|
+
subject { described_class.new(type) }
|
7
|
+
|
8
|
+
describe '#call' do
|
9
|
+
context 'an array of primitives' do
|
10
|
+
let(:type) { Array[String] }
|
11
|
+
|
12
|
+
it 'coerces elements in the array' do
|
13
|
+
expect(subject.call([10, 20])).to eq(%w[10 20])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'an array of arrays' do
|
18
|
+
let(:type) { Array[Array[Integer]] }
|
19
|
+
|
20
|
+
it 'coerces elements in the nested array' do
|
21
|
+
expect(subject.call([%w[10 20]])).to eq([[10, 20]])
|
22
|
+
expect(subject.call([['10'], ['20']])).to eq([[10], [20]])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'an array of sets' do
|
27
|
+
let(:type) { Array[Set[Integer]] }
|
28
|
+
|
29
|
+
it 'coerces elements in the nested set' do
|
30
|
+
expect(subject.call([%w[10 20]])).to eq([Set[10, 20]])
|
31
|
+
expect(subject.call([['10'], ['20']])).to eq([Set[10], Set[20]])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -7,7 +7,7 @@ describe Grape::Validations::Types::PrimitiveCoercer do
|
|
7
7
|
|
8
8
|
subject { described_class.new(type, strict) }
|
9
9
|
|
10
|
-
describe '
|
10
|
+
describe '#call' do
|
11
11
|
context 'Boolean' do
|
12
12
|
let(:type) { Grape::API::Boolean }
|
13
13
|
|
@@ -26,6 +26,10 @@ describe Grape::Validations::Types::PrimitiveCoercer do
|
|
26
26
|
it 'returns an error when the given value cannot be coerced' do
|
27
27
|
expect(subject.call(123)).to be_instance_of(Grape::Validations::Types::InvalidValue)
|
28
28
|
end
|
29
|
+
|
30
|
+
it 'coerces an empty string to nil' do
|
31
|
+
expect(subject.call('')).to be_nil
|
32
|
+
end
|
29
33
|
end
|
30
34
|
|
31
35
|
context 'String' do
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Grape::Validations::Types::SetCoercer do
|
6
|
+
subject { described_class.new(type) }
|
7
|
+
|
8
|
+
describe '#call' do
|
9
|
+
context 'a set of primitives' do
|
10
|
+
let(:type) { Set[String] }
|
11
|
+
|
12
|
+
it 'coerces elements to the set' do
|
13
|
+
expect(subject.call([10, 20])).to eq(Set['10', '20'])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'a set of sets' do
|
18
|
+
let(:type) { Set[Set[Integer]] }
|
19
|
+
|
20
|
+
it 'coerces elements in the nested set' do
|
21
|
+
expect(subject.call([%w[10 20]])).to eq(Set[Set[10, 20]])
|
22
|
+
expect(subject.call([['10'], ['20']])).to eq(Set[Set[10], Set[20]])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'a set of sets of arrays' do
|
27
|
+
let(:type) { Set[Set[Array[Integer]]] }
|
28
|
+
|
29
|
+
it 'coerces elements in the nested set' do
|
30
|
+
expect(subject.call([[['10'], ['20']]])).to eq(Set[Set[Array[10], Array[20]]])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -424,6 +424,79 @@ describe Grape::Validations::CoerceValidator do
|
|
424
424
|
expect(last_response.status).to eq(200)
|
425
425
|
expect(last_response.body).to eq(integer_class_name)
|
426
426
|
end
|
427
|
+
|
428
|
+
context 'nil values' do
|
429
|
+
context 'primitive types' do
|
430
|
+
Grape::Validations::Types::PRIMITIVES.each do |type|
|
431
|
+
it 'respects the nil value' do
|
432
|
+
subject.params do
|
433
|
+
requires :param, type: type
|
434
|
+
end
|
435
|
+
subject.get '/nil_value' do
|
436
|
+
params[:param].class
|
437
|
+
end
|
438
|
+
|
439
|
+
get '/nil_value', param: nil
|
440
|
+
expect(last_response.status).to eq(200)
|
441
|
+
expect(last_response.body).to eq('NilClass')
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
context 'structures types' do
|
447
|
+
Grape::Validations::Types::STRUCTURES.each do |type|
|
448
|
+
it 'respects the nil value' do
|
449
|
+
subject.params do
|
450
|
+
requires :param, type: type
|
451
|
+
end
|
452
|
+
subject.get '/nil_value' do
|
453
|
+
params[:param].class
|
454
|
+
end
|
455
|
+
|
456
|
+
get '/nil_value', param: nil
|
457
|
+
expect(last_response.status).to eq(200)
|
458
|
+
expect(last_response.body).to eq('NilClass')
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
context 'special types' do
|
464
|
+
Grape::Validations::Types::SPECIAL.each_key do |type|
|
465
|
+
it 'respects the nil value' do
|
466
|
+
subject.params do
|
467
|
+
requires :param, type: type
|
468
|
+
end
|
469
|
+
subject.get '/nil_value' do
|
470
|
+
params[:param].class
|
471
|
+
end
|
472
|
+
|
473
|
+
get '/nil_value', param: nil
|
474
|
+
expect(last_response.status).to eq(200)
|
475
|
+
expect(last_response.body).to eq('NilClass')
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
context 'variant-member-type collections' do
|
480
|
+
[
|
481
|
+
Array[Integer, String],
|
482
|
+
[Integer, String, Array[Integer, String]]
|
483
|
+
].each do |type|
|
484
|
+
it 'respects the nil value' do
|
485
|
+
subject.params do
|
486
|
+
requires :param, type: type
|
487
|
+
end
|
488
|
+
subject.get '/nil_value' do
|
489
|
+
params[:param].class
|
490
|
+
end
|
491
|
+
|
492
|
+
get '/nil_value', param: nil
|
493
|
+
expect(last_response.status).to eq(200)
|
494
|
+
expect(last_response.body).to eq('NilClass')
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
427
500
|
end
|
428
501
|
|
429
502
|
context 'using coerce_with' do
|
@@ -752,19 +825,6 @@ describe Grape::Validations::CoerceValidator do
|
|
752
825
|
expect(last_response.body).to eq('String')
|
753
826
|
end
|
754
827
|
|
755
|
-
it 'respects nil values' do
|
756
|
-
subject.params do
|
757
|
-
optional :a, types: [File, String]
|
758
|
-
end
|
759
|
-
subject.get '/' do
|
760
|
-
params[:a].class.to_s
|
761
|
-
end
|
762
|
-
|
763
|
-
get '/', a: nil
|
764
|
-
expect(last_response.status).to eq(200)
|
765
|
-
expect(last_response.body).to eq('NilClass')
|
766
|
-
end
|
767
|
-
|
768
828
|
it 'fails when no coercion is possible' do
|
769
829
|
subject.params do
|
770
830
|
requires :a, types: [Boolean, Integer]
|
@@ -298,4 +298,125 @@ describe Grape::Validations::DefaultValidator do
|
|
298
298
|
end
|
299
299
|
end
|
300
300
|
end
|
301
|
+
|
302
|
+
context 'optional with nil as value' do
|
303
|
+
subject do
|
304
|
+
Class.new(Grape::API) do
|
305
|
+
default_format :json
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def app
|
310
|
+
subject
|
311
|
+
end
|
312
|
+
|
313
|
+
context 'primitive types' do
|
314
|
+
[
|
315
|
+
[Integer, 0],
|
316
|
+
[Integer, 42],
|
317
|
+
[Float, 0.0],
|
318
|
+
[Float, 4.2],
|
319
|
+
[BigDecimal, 0.0],
|
320
|
+
[BigDecimal, 4.2],
|
321
|
+
[Numeric, 0],
|
322
|
+
[Numeric, 42],
|
323
|
+
[Date, Date.today],
|
324
|
+
[DateTime, DateTime.now],
|
325
|
+
[Time, Time.now],
|
326
|
+
[Time, Time.at(0)],
|
327
|
+
[Grape::API::Boolean, false],
|
328
|
+
[String, ''],
|
329
|
+
[String, 'non-empty-string'],
|
330
|
+
[Symbol, :symbol],
|
331
|
+
[TrueClass, true],
|
332
|
+
[FalseClass, false]
|
333
|
+
].each do |type, default|
|
334
|
+
it 'respects the default value' do
|
335
|
+
subject.params do
|
336
|
+
optional :param, type: type, default: default
|
337
|
+
end
|
338
|
+
subject.get '/default_value' do
|
339
|
+
params[:param]
|
340
|
+
end
|
341
|
+
|
342
|
+
get '/default_value', param: nil
|
343
|
+
expect(last_response.status).to eq(200)
|
344
|
+
expect(last_response.body).to eq(default.to_json)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
context 'structures types' do
|
350
|
+
[
|
351
|
+
[Hash, {}],
|
352
|
+
[Hash, { test: 'non-empty' }],
|
353
|
+
[Array, []],
|
354
|
+
[Array, ['non-empty']],
|
355
|
+
[Array[Integer], []],
|
356
|
+
[Set, []],
|
357
|
+
[Set, [1]]
|
358
|
+
].each do |type, default|
|
359
|
+
it 'respects the default value' do
|
360
|
+
subject.params do
|
361
|
+
optional :param, type: type, default: default
|
362
|
+
end
|
363
|
+
subject.get '/default_value' do
|
364
|
+
params[:param]
|
365
|
+
end
|
366
|
+
|
367
|
+
get '/default_value', param: nil
|
368
|
+
expect(last_response.status).to eq(200)
|
369
|
+
expect(last_response.body).to eq(default.to_json)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
context 'special types' do
|
375
|
+
[
|
376
|
+
[JSON, ''],
|
377
|
+
[JSON, { test: 'non-empty-string' }.to_json],
|
378
|
+
[Array[JSON], []],
|
379
|
+
[Array[JSON], [{ test: 'non-empty-string' }.to_json]],
|
380
|
+
[::File, ''],
|
381
|
+
[::File, { test: 'non-empty-string' }.to_json],
|
382
|
+
[Rack::Multipart::UploadedFile, ''],
|
383
|
+
[Rack::Multipart::UploadedFile, { test: 'non-empty-string' }.to_json]
|
384
|
+
].each do |type, default|
|
385
|
+
it 'respects the default value' do
|
386
|
+
subject.params do
|
387
|
+
optional :param, type: type, default: default
|
388
|
+
end
|
389
|
+
subject.get '/default_value' do
|
390
|
+
params[:param]
|
391
|
+
end
|
392
|
+
|
393
|
+
get '/default_value', param: nil
|
394
|
+
expect(last_response.status).to eq(200)
|
395
|
+
expect(last_response.body).to eq(default.to_json)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
context 'variant-member-type collections' do
|
401
|
+
[
|
402
|
+
[Array[Integer, String], [0, '']],
|
403
|
+
[Array[Integer, String], [42, 'non-empty-string']],
|
404
|
+
[[Integer, String, Array[Integer, String]], [0, '', [0, '']]],
|
405
|
+
[[Integer, String, Array[Integer, String]], [42, 'non-empty-string', [42, 'non-empty-string']]]
|
406
|
+
].each do |type, default|
|
407
|
+
it 'respects the default value' do
|
408
|
+
subject.params do
|
409
|
+
optional :param, type: type, default: default
|
410
|
+
end
|
411
|
+
subject.get '/default_value' do
|
412
|
+
params[:param]
|
413
|
+
end
|
414
|
+
|
415
|
+
get '/default_value', param: nil
|
416
|
+
expect(last_response.status).to eq(200)
|
417
|
+
expect(last_response.body).to eq(default.to_json)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
301
422
|
end
|
@@ -319,7 +319,7 @@ describe Grape::Validations::ValuesValidator do
|
|
319
319
|
expect(last_response.status).to eq 200
|
320
320
|
end
|
321
321
|
|
322
|
-
it '
|
322
|
+
it 'accepts for an optional param with a list of values' do
|
323
323
|
put('/optional_with_array_of_string_values', optional: nil)
|
324
324
|
expect(last_response.status).to eq 200
|
325
325
|
end
|
@@ -574,7 +574,7 @@ describe Grape::Validations do
|
|
574
574
|
# NOTE: with body parameters in json or XML or similar this
|
575
575
|
# should actually fail with: children[parents][name] is missing.
|
576
576
|
expect(last_response.status).to eq(400)
|
577
|
-
expect(last_response.body).to eq('children[1][parents] is missing')
|
577
|
+
expect(last_response.body).to eq('children[1][parents] is missing, children[0][parents][1][name] is missing, children[0][parents][1][name] is empty')
|
578
578
|
end
|
579
579
|
|
580
580
|
it 'errors when a parameter is not present in array within array' do
|
@@ -615,7 +615,7 @@ describe Grape::Validations do
|
|
615
615
|
|
616
616
|
get '/within_array', children: [name: 'Jay']
|
617
617
|
expect(last_response.status).to eq(400)
|
618
|
-
expect(last_response.body).to eq('children[0][parents] is missing')
|
618
|
+
expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing, children[0][parents][0][name] is empty')
|
619
619
|
end
|
620
620
|
|
621
621
|
it 'errors when param is not an Array' do
|
@@ -763,7 +763,7 @@ describe Grape::Validations do
|
|
763
763
|
expect(last_response.status).to eq(200)
|
764
764
|
put_with_json '/within_array', children: [name: 'Jay']
|
765
765
|
expect(last_response.status).to eq(400)
|
766
|
-
expect(last_response.body).to eq('children[0][parents] is missing')
|
766
|
+
expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing')
|
767
767
|
end
|
768
768
|
end
|
769
769
|
|
@@ -838,7 +838,7 @@ describe Grape::Validations do
|
|
838
838
|
it 'does internal validations if the outer group is present' do
|
839
839
|
get '/nested_optional_group', items: [{ key: 'foo' }]
|
840
840
|
expect(last_response.status).to eq(400)
|
841
|
-
expect(last_response.body).to eq('items[0][required_subitems] is missing')
|
841
|
+
expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
|
842
842
|
|
843
843
|
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
|
844
844
|
expect(last_response.status).to eq(200)
|
@@ -858,7 +858,7 @@ describe Grape::Validations do
|
|
858
858
|
it 'handles validation within arrays' do
|
859
859
|
get '/nested_optional_group', items: [{ key: 'foo' }]
|
860
860
|
expect(last_response.status).to eq(400)
|
861
|
-
expect(last_response.body).to eq('items[0][required_subitems] is missing')
|
861
|
+
expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
|
862
862
|
|
863
863
|
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
|
864
864
|
expect(last_response.status).to eq(200)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Bleigh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -332,7 +332,9 @@ files:
|
|
332
332
|
- spec/grape/validations/multiple_attributes_iterator_spec.rb
|
333
333
|
- spec/grape/validations/params_scope_spec.rb
|
334
334
|
- spec/grape/validations/single_attribute_iterator_spec.rb
|
335
|
+
- spec/grape/validations/types/array_coercer_spec.rb
|
335
336
|
- spec/grape/validations/types/primitive_coercer_spec.rb
|
337
|
+
- spec/grape/validations/types/set_coercer_spec.rb
|
336
338
|
- spec/grape/validations/types_spec.rb
|
337
339
|
- spec/grape/validations/validators/all_or_none_spec.rb
|
338
340
|
- spec/grape/validations/validators/allow_blank_spec.rb
|
@@ -364,9 +366,9 @@ licenses:
|
|
364
366
|
- MIT
|
365
367
|
metadata:
|
366
368
|
bug_tracker_uri: https://github.com/ruby-grape/grape/issues
|
367
|
-
changelog_uri: https://github.com/ruby-grape/grape/blob/v1.3.
|
368
|
-
documentation_uri: https://www.rubydoc.info/gems/grape/1.3.
|
369
|
-
source_code_uri: https://github.com/ruby-grape/grape/tree/v1.3.
|
369
|
+
changelog_uri: https://github.com/ruby-grape/grape/blob/v1.3.3/CHANGELOG.md
|
370
|
+
documentation_uri: https://www.rubydoc.info/gems/grape/1.3.3
|
371
|
+
source_code_uri: https://github.com/ruby-grape/grape/tree/v1.3.3
|
370
372
|
post_install_message:
|
371
373
|
rdoc_options: []
|
372
374
|
require_paths:
|
@@ -415,6 +417,8 @@ test_files:
|
|
415
417
|
- spec/grape/api_remount_spec.rb
|
416
418
|
- spec/grape/validations/types_spec.rb
|
417
419
|
- spec/grape/validations/attributes_iterator_spec.rb
|
420
|
+
- spec/grape/validations/types/array_coercer_spec.rb
|
421
|
+
- spec/grape/validations/types/set_coercer_spec.rb
|
418
422
|
- spec/grape/validations/types/primitive_coercer_spec.rb
|
419
423
|
- spec/grape/validations/validators/regexp_spec.rb
|
420
424
|
- spec/grape/validations/validators/default_spec.rb
|