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