grape 1.3.2 → 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|