grape 1.3.0 → 1.5.2
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 +119 -1
- data/LICENSE +1 -1
- data/README.md +123 -29
- data/UPGRADING.md +265 -39
- data/lib/grape/api/instance.rb +32 -31
- data/lib/grape/api.rb +5 -5
- data/lib/grape/content_types.rb +34 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/helpers.rb +2 -1
- data/lib/grape/dsl/inside_route.rb +77 -43
- data/lib/grape/dsl/parameters.rb +12 -8
- data/lib/grape/dsl/routing.rb +12 -11
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +8 -6
- data/lib/grape/exceptions/base.rb +0 -4
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +12 -13
- data/lib/grape/http/headers.rb +26 -0
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/base.rb +4 -5
- data/lib/grape/middleware/error.rb +11 -13
- data/lib/grape/middleware/formatter.rb +3 -3
- data/lib/grape/middleware/stack.rb +10 -2
- data/lib/grape/middleware/versioner/header.rb +4 -4
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +12 -2
- data/lib/grape/path.rb +13 -3
- data/lib/grape/request.rb +13 -8
- data/lib/grape/router/attribute_translator.rb +26 -5
- data/lib/grape/router/pattern.rb +17 -16
- data/lib/grape/router/route.rb +5 -24
- data/lib/grape/router.rb +26 -30
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
- data/lib/grape/util/base_inheritable.rb +15 -8
- data/lib/grape/util/cache.rb +20 -0
- data/lib/grape/util/lazy_object.rb +43 -0
- data/lib/grape/util/lazy_value.rb +1 -0
- data/lib/grape/util/reverse_stackable_values.rb +2 -0
- data/lib/grape/util/stackable_values.rb +7 -20
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +10 -8
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/array_coercer.rb +14 -5
- data/lib/grape/validations/types/build_coercer.rb +5 -8
- data/lib/grape/validations/types/custom_type_coercer.rb +16 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
- data/lib/grape/validations/types/file.rb +15 -12
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +40 -36
- data/lib/grape/validations/types/primitive_coercer.rb +15 -6
- 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/types.rb +7 -9
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/as.rb +1 -1
- data/lib/grape/validations/validators/base.rb +8 -8
- data/lib/grape/validations/validators/coerce.rb +11 -15
- data/lib/grape/validations/validators/default.rb +3 -5
- data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
- data/lib/grape/validations/validators/except_values.rb +1 -1
- data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/validations/validators/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +5 -5
- data/spec/grape/api/instance_spec.rb +50 -0
- data/spec/grape/api_remount_spec.rb +9 -4
- data/spec/grape/api_spec.rb +82 -6
- data/spec/grape/dsl/inside_route_spec.rb +182 -33
- data/spec/grape/endpoint/declared_spec.rb +601 -0
- data/spec/grape/endpoint_spec.rb +0 -521
- data/spec/grape/entity_spec.rb +7 -1
- data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
- data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -1
- data/spec/grape/middleware/formatter_spec.rb +3 -3
- data/spec/grape/middleware/stack_spec.rb +10 -0
- data/spec/grape/path_spec.rb +4 -4
- data/spec/grape/request_spec.rb +1 -1
- data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
- data/spec/grape/validations/params_scope_spec.rb +26 -0
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/types_spec.rb +1 -1
- data/spec/grape/validations/validators/coerce_spec.rb +366 -86
- data/spec/grape/validations/validators/default_spec.rb +170 -0
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
- data/spec/grape/validations/validators/except_values_spec.rb +1 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +298 -30
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/shared/versioning_examples.rb +20 -20
- data/spec/spec_helper.rb +3 -10
- data/spec/support/chunks.rb +14 -0
- data/spec/support/eager_load.rb +19 -0
- data/spec/support/versioned_helpers.rb +4 -6
- metadata +27 -10
- data/lib/grape/util/content_types.rb +0 -28
@@ -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
|
|
@@ -7,6 +7,7 @@ require_relative 'types/multiple_type_coercer'
|
|
7
7
|
require_relative 'types/variant_collection_coercer'
|
8
8
|
require_relative 'types/json'
|
9
9
|
require_relative 'types/file'
|
10
|
+
require_relative 'types/invalid_value'
|
10
11
|
|
11
12
|
module Grape
|
12
13
|
module Validations
|
@@ -21,10 +22,6 @@ module Grape
|
|
21
22
|
# and {Grape::Dsl::Parameters#optional}. The main
|
22
23
|
# entry point for this process is {Types.build_coercer}.
|
23
24
|
module Types
|
24
|
-
# Instances of this class may be used as tokens to denote that
|
25
|
-
# a parameter value could not be coerced.
|
26
|
-
class InvalidValue; end
|
27
|
-
|
28
25
|
# Types representing a single value, which are coerced.
|
29
26
|
PRIMITIVES = [
|
30
27
|
# Numerical
|
@@ -42,7 +39,6 @@ module Grape
|
|
42
39
|
Grape::API::Boolean,
|
43
40
|
String,
|
44
41
|
Symbol,
|
45
|
-
Rack::Multipart::UploadedFile,
|
46
42
|
TrueClass,
|
47
43
|
FalseClass
|
48
44
|
].freeze
|
@@ -54,8 +50,7 @@ module Grape
|
|
54
50
|
Set
|
55
51
|
].freeze
|
56
52
|
|
57
|
-
#
|
58
|
-
# and type-checking logic.
|
53
|
+
# Special custom types provided by Grape.
|
59
54
|
SPECIAL = {
|
60
55
|
JSON => Json,
|
61
56
|
Array[JSON] => JsonArray,
|
@@ -130,7 +125,6 @@ module Grape
|
|
130
125
|
!primitive?(type) &&
|
131
126
|
!structure?(type) &&
|
132
127
|
!multiple?(type) &&
|
133
|
-
!special?(type) &&
|
134
128
|
type.respond_to?(:parse) &&
|
135
129
|
type.method(:parse).arity == 1
|
136
130
|
end
|
@@ -143,7 +137,11 @@ module Grape
|
|
143
137
|
def self.collection_of_custom?(type)
|
144
138
|
(type.is_a?(Array) || type.is_a?(Set)) &&
|
145
139
|
type.length == 1 &&
|
146
|
-
custom?(type.first)
|
140
|
+
(custom?(type.first) || special?(type.first))
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.map_special(type)
|
144
|
+
SPECIAL.fetch(type, type)
|
147
145
|
end
|
148
146
|
end
|
149
147
|
end
|
@@ -12,14 +12,15 @@ module Grape
|
|
12
12
|
# @param options [Object] implementation-dependent Validator options
|
13
13
|
# @param required [Boolean] attribute(s) are required or optional
|
14
14
|
# @param scope [ParamsScope] parent scope for this Validator
|
15
|
-
# @param opts [
|
16
|
-
def initialize(attrs, options, required, scope, opts
|
15
|
+
# @param opts [Array] additional validation options
|
16
|
+
def initialize(attrs, options, required, scope, *opts)
|
17
17
|
@attrs = Array(attrs)
|
18
18
|
@option = options
|
19
19
|
@required = required
|
20
20
|
@scope = scope
|
21
|
-
|
22
|
-
@
|
21
|
+
opts = opts.any? ? opts.shift : {}
|
22
|
+
@fail_fast = opts.fetch(:fail_fast, false)
|
23
|
+
@allow_blank = opts.fetch(:allow_blank, false)
|
23
24
|
end
|
24
25
|
|
25
26
|
# Validates a given request.
|
@@ -43,13 +44,12 @@ module Grape
|
|
43
44
|
# there may be more than one error per field
|
44
45
|
array_errors = []
|
45
46
|
|
46
|
-
attributes.each do |val, attr_name, empty_val|
|
47
|
+
attributes.each do |val, attr_name, empty_val, skip_value|
|
48
|
+
next if skip_value
|
47
49
|
next if !@scope.required? && empty_val
|
48
50
|
next unless @scope.meets_dependency?(val, params)
|
49
51
|
begin
|
50
|
-
if @required || val.respond_to?(:key?) && val.key?(attr_name)
|
51
|
-
validate_param!(attr_name, val)
|
52
|
-
end
|
52
|
+
validate_param!(attr_name, val) if @required || val.respond_to?(:key?) && val.key?(attr_name)
|
53
53
|
rescue Grape::Exceptions::Validation => e
|
54
54
|
array_errors << e
|
55
55
|
end
|
@@ -17,7 +17,7 @@ module Grape
|
|
17
17
|
|
18
18
|
module Validations
|
19
19
|
class CoerceValidator < Base
|
20
|
-
def initialize(
|
20
|
+
def initialize(attrs, options, required, scope, **opts)
|
21
21
|
super
|
22
22
|
|
23
23
|
@converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer)
|
@@ -36,7 +36,7 @@ module Grape
|
|
36
36
|
|
37
37
|
new_value = coerce_value(params[attr_name])
|
38
38
|
|
39
|
-
raise validation_exception(attr_name) unless valid_type?(new_value)
|
39
|
+
raise validation_exception(attr_name, new_value.message) unless valid_type?(new_value)
|
40
40
|
|
41
41
|
# Don't assign a value if it is identical. It fixes a problem with Hashie::Mash
|
42
42
|
# which looses wrappers for hashes and arrays after reassigning values
|
@@ -47,7 +47,9 @@ module Grape
|
|
47
47
|
# h[:list] = list
|
48
48
|
# h
|
49
49
|
# => #<Hashie::Mash list=[1, 2, 3, 4]>
|
50
|
-
params[attr_name]
|
50
|
+
return if params[attr_name].class == new_value.class && params[attr_name] == new_value
|
51
|
+
|
52
|
+
params[attr_name] = new_value
|
51
53
|
end
|
52
54
|
|
53
55
|
private
|
@@ -65,18 +67,9 @@ module Grape
|
|
65
67
|
end
|
66
68
|
|
67
69
|
def coerce_value(val)
|
68
|
-
# define default values for structures, the dry-types lib which is used
|
69
|
-
# for coercion doesn't accept nil as a value, so it would fail
|
70
|
-
if val.nil?
|
71
|
-
return [] if type == Array || type.is_a?(Array)
|
72
|
-
return Set.new if type == Set
|
73
|
-
return {} if type == Hash
|
74
|
-
end
|
75
|
-
|
76
70
|
converter.call(val)
|
77
|
-
|
78
71
|
# Some custom types might fail, so it should be treated as an invalid value
|
79
|
-
rescue
|
72
|
+
rescue StandardError
|
80
73
|
Types::InvalidValue.new
|
81
74
|
end
|
82
75
|
|
@@ -87,8 +80,11 @@ module Grape
|
|
87
80
|
@option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type]
|
88
81
|
end
|
89
82
|
|
90
|
-
def validation_exception(attr_name)
|
91
|
-
Grape::Exceptions::Validation.new(
|
83
|
+
def validation_exception(attr_name, custom_msg = nil)
|
84
|
+
Grape::Exceptions::Validation.new(
|
85
|
+
params: [@scope.full_name(attr_name)],
|
86
|
+
message: custom_msg || message(:coerce)
|
87
|
+
)
|
92
88
|
end
|
93
89
|
end
|
94
90
|
end
|
@@ -3,13 +3,12 @@
|
|
3
3
|
module Grape
|
4
4
|
module Validations
|
5
5
|
class DefaultValidator < Base
|
6
|
-
def initialize(attrs, options, required, scope, opts
|
6
|
+
def initialize(attrs, options, required, scope, **opts)
|
7
7
|
@default = options
|
8
8
|
super
|
9
9
|
end
|
10
10
|
|
11
11
|
def validate_param!(attr_name, params)
|
12
|
-
return if params.key? attr_name
|
13
12
|
params[attr_name] = if @default.is_a? Proc
|
14
13
|
@default.call
|
15
14
|
elsif @default.frozen? || !duplicatable?(@default)
|
@@ -22,9 +21,8 @@ module Grape
|
|
22
21
|
def validate!(params)
|
23
22
|
attrs = SingleAttributeIterator.new(self, @scope, params)
|
24
23
|
attrs.each do |resource_params, attr_name|
|
25
|
-
|
26
|
-
|
27
|
-
end
|
24
|
+
next unless @scope.meets_dependency?(resource_params, params)
|
25
|
+
validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
@@ -6,8 +6,10 @@ module Grape
|
|
6
6
|
module Validations
|
7
7
|
class ExactlyOneOfValidator < MultipleParamsBase
|
8
8
|
def validate_params!(params)
|
9
|
-
|
10
|
-
|
9
|
+
keys = keys_in_common(params)
|
10
|
+
return if keys.length == 1
|
11
|
+
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero?
|
12
|
+
raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -7,7 +7,8 @@ module Grape
|
|
7
7
|
attributes = MultipleAttributesIterator.new(self, @scope, params)
|
8
8
|
array_errors = []
|
9
9
|
|
10
|
-
attributes.each do |resource_params|
|
10
|
+
attributes.each do |resource_params, skip_value|
|
11
|
+
next if skip_value
|
11
12
|
begin
|
12
13
|
validate_params!(resource_params)
|
13
14
|
rescue Grape::Exceptions::Validation => e
|
@@ -5,7 +5,7 @@ module Grape
|
|
5
5
|
class RegexpValidator < Base
|
6
6
|
def validate_param!(attr_name, params)
|
7
7
|
return unless params.respond_to?(:key?) && params.key?(attr_name)
|
8
|
-
return if Array.wrap(params[attr_name]).all? { |param| param.nil? ||
|
8
|
+
return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) }
|
9
9
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))
|
10
10
|
end
|
11
11
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Grape
|
4
4
|
module Validations
|
5
5
|
class ValuesValidator < Base
|
6
|
-
def initialize(attrs, options, required, scope, opts
|
6
|
+
def initialize(attrs, options, required, scope, **opts)
|
7
7
|
if options.is_a?(Hash)
|
8
8
|
@excepts = options[:except]
|
9
9
|
@values = options[:value]
|
data/lib/grape/version.rb
CHANGED
data/lib/grape.rb
CHANGED
@@ -12,6 +12,7 @@ require 'active_support/core_ext/hash/indifferent_access'
|
|
12
12
|
require 'active_support/core_ext/object/blank'
|
13
13
|
require 'active_support/core_ext/array/extract_options'
|
14
14
|
require 'active_support/core_ext/array/wrap'
|
15
|
+
require 'active_support/core_ext/array/conversions'
|
15
16
|
require 'active_support/core_ext/hash/deep_merge'
|
16
17
|
require 'active_support/core_ext/hash/reverse_merge'
|
17
18
|
require 'active_support/core_ext/hash/except'
|
@@ -20,7 +21,6 @@ require 'active_support/core_ext/hash/conversions'
|
|
20
21
|
require 'active_support/dependencies/autoload'
|
21
22
|
require 'active_support/notifications'
|
22
23
|
require 'i18n'
|
23
|
-
require 'thread'
|
24
24
|
|
25
25
|
I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
|
26
26
|
|
@@ -84,7 +84,6 @@ module Grape
|
|
84
84
|
eager_autoload do
|
85
85
|
autoload :DeepMergeableHash
|
86
86
|
autoload :DeepSymbolizeHash
|
87
|
-
autoload :DeepHashWithIndifferentAccess
|
88
87
|
autoload :Hash
|
89
88
|
end
|
90
89
|
module ActiveSupport
|
@@ -208,18 +207,19 @@ module Grape
|
|
208
207
|
end
|
209
208
|
end
|
210
209
|
|
211
|
-
module
|
210
|
+
module ServeStream
|
212
211
|
extend ::ActiveSupport::Autoload
|
213
212
|
eager_autoload do
|
214
|
-
autoload :FileResponse
|
215
213
|
autoload :FileBody
|
216
214
|
autoload :SendfileResponse
|
215
|
+
autoload :StreamResponse
|
217
216
|
end
|
218
217
|
end
|
219
218
|
end
|
220
219
|
|
221
220
|
require 'grape/config'
|
222
|
-
require 'grape/
|
221
|
+
require 'grape/content_types'
|
222
|
+
|
223
223
|
require 'grape/util/lazy_value'
|
224
224
|
require 'grape/util/lazy_block'
|
225
225
|
require 'grape/util/endpoint_configuration'
|
@@ -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
|
@@ -340,19 +340,24 @@ describe Grape::API do
|
|
340
340
|
context 'when the configuration is read within a namespace' do
|
341
341
|
before do
|
342
342
|
a_remounted_api.namespace 'api' do
|
343
|
+
params do
|
344
|
+
requires configuration[:required_param]
|
345
|
+
end
|
343
346
|
get "/#{configuration[:path]}" do
|
344
347
|
'10 votes'
|
345
348
|
end
|
346
349
|
end
|
347
|
-
root_api.mount a_remounted_api, with: { path: 'votes' }
|
348
|
-
root_api.mount a_remounted_api, with: { path: 'scores' }
|
350
|
+
root_api.mount a_remounted_api, with: { path: 'votes', required_param: 'param_key' }
|
351
|
+
root_api.mount a_remounted_api, with: { path: 'scores', required_param: 'param_key' }
|
349
352
|
end
|
350
353
|
|
351
354
|
it 'will use the dynamic configuration on all routes' do
|
352
|
-
get 'api/votes'
|
355
|
+
get 'api/votes', param_key: 'a'
|
353
356
|
expect(last_response.body).to eql '10 votes'
|
354
|
-
get 'api/scores'
|
357
|
+
get 'api/scores', param_key: 'a'
|
355
358
|
expect(last_response.body).to eql '10 votes'
|
359
|
+
get 'api/votes'
|
360
|
+
expect(last_response.status).to eq 400
|
356
361
|
end
|
357
362
|
end
|
358
363
|
|
data/spec/grape/api_spec.rb
CHANGED
@@ -816,6 +816,71 @@ XML
|
|
816
816
|
end
|
817
817
|
end
|
818
818
|
|
819
|
+
describe 'when hook behaviour is controlled by attributes on the route ' do
|
820
|
+
before do
|
821
|
+
subject.before do
|
822
|
+
error!('Access Denied', 401) unless route.options[:secret] == params[:secret]
|
823
|
+
end
|
824
|
+
|
825
|
+
subject.namespace 'example' do
|
826
|
+
before do
|
827
|
+
error!('Access Denied', 401) unless route.options[:namespace_secret] == params[:namespace_secret]
|
828
|
+
end
|
829
|
+
|
830
|
+
desc 'it gets with secret', secret: 'password'
|
831
|
+
get { status(params[:id] == '504' ? 200 : 404) }
|
832
|
+
|
833
|
+
desc 'it post with secret', secret: 'password', namespace_secret: 'namespace_password'
|
834
|
+
post {}
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
context 'when HTTP method is not defined' do
|
839
|
+
let(:response) { delete('/example') }
|
840
|
+
|
841
|
+
it 'responds with a 405 status' do
|
842
|
+
expect(response.status).to eql 405
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
context 'when HTTP method is defined with attribute' do
|
847
|
+
let(:response) { post('/example?secret=incorrect_password') }
|
848
|
+
it 'responds with the defined error in the before hook' do
|
849
|
+
expect(response.status).to eql 401
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
context 'when HTTP method is defined and the underlying before hook expectation is not met' do
|
854
|
+
let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }
|
855
|
+
it 'ends up in the endpoint' do
|
856
|
+
expect(response.status).to eql 401
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
context 'when HTTP method is defined and everything is like the before hooks expect' do
|
861
|
+
let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }
|
862
|
+
it 'ends up in the endpoint' do
|
863
|
+
expect(response.status).to eql 201
|
864
|
+
end
|
865
|
+
end
|
866
|
+
|
867
|
+
context 'when HEAD is called for the defined GET' do
|
868
|
+
let(:response) { head('/example?id=504') }
|
869
|
+
|
870
|
+
it 'responds with 401 because before expectations in before hooks are not met' do
|
871
|
+
expect(response.status).to eql 401
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
context 'when HEAD is called for the defined GET' do
|
876
|
+
let(:response) { head('/example?id=504&secret=password') }
|
877
|
+
|
878
|
+
it 'responds with 200 because before hooks are not called' do
|
879
|
+
expect(response.status).to eql 200
|
880
|
+
end
|
881
|
+
end
|
882
|
+
end
|
883
|
+
|
819
884
|
context 'allows HEAD on a GET request that' do
|
820
885
|
before do
|
821
886
|
subject.get 'example' do
|
@@ -935,7 +1000,7 @@ XML
|
|
935
1000
|
|
936
1001
|
it 'adds a before filter to current and child namespaces only' do
|
937
1002
|
subject.get '/' do
|
938
|
-
"root - #{@foo}"
|
1003
|
+
"root - #{instance_variable_defined?(:@foo) ? @foo : nil}"
|
939
1004
|
end
|
940
1005
|
subject.namespace :blah do
|
941
1006
|
before { @foo = 'foo' }
|
@@ -1084,6 +1149,11 @@ XML
|
|
1084
1149
|
expect(last_response.headers['Content-Type']).to eq('text/plain')
|
1085
1150
|
end
|
1086
1151
|
|
1152
|
+
it 'does not set Cache-Control' do
|
1153
|
+
get '/foo'
|
1154
|
+
expect(last_response.headers['Cache-Control']).to eq(nil)
|
1155
|
+
end
|
1156
|
+
|
1087
1157
|
it 'sets content type for xml' do
|
1088
1158
|
get '/foo.xml'
|
1089
1159
|
expect(last_response.headers['Content-Type']).to eq('application/xml')
|
@@ -1530,6 +1600,11 @@ XML
|
|
1530
1600
|
expect(subject.io).to receive(:write).with(message)
|
1531
1601
|
subject.logger.info 'this will be logged'
|
1532
1602
|
end
|
1603
|
+
|
1604
|
+
it 'does not unnecessarily retain duplicate setup blocks' do
|
1605
|
+
subject.logger
|
1606
|
+
expect { subject.logger }.to_not change(subject.instance_variable_get(:@setup), :size)
|
1607
|
+
end
|
1533
1608
|
end
|
1534
1609
|
|
1535
1610
|
describe '.helpers' do
|
@@ -3677,12 +3752,13 @@ XML
|
|
3677
3752
|
end
|
3678
3753
|
end
|
3679
3754
|
context ':serializable_hash' do
|
3680
|
-
|
3681
|
-
|
3682
|
-
def
|
3683
|
-
{ abc: 'def' }
|
3684
|
-
end
|
3755
|
+
class SerializableHashExample
|
3756
|
+
def serializable_hash
|
3757
|
+
{ abc: 'def' }
|
3685
3758
|
end
|
3759
|
+
end
|
3760
|
+
|
3761
|
+
before(:each) do
|
3686
3762
|
subject.format :serializable_hash
|
3687
3763
|
end
|
3688
3764
|
it 'instance' do
|