grape 1.1.0 → 1.5.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 +278 -44
- data/LICENSE +1 -1
- data/README.md +514 -69
- data/UPGRADING.md +424 -17
- data/grape.gemspec +13 -2
- data/lib/grape.rb +104 -71
- data/lib/grape/api.rb +138 -175
- data/lib/grape/api/helpers.rb +2 -0
- data/lib/grape/api/instance.rb +283 -0
- data/lib/grape/config.rb +34 -0
- data/lib/grape/content_types.rb +34 -0
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/api.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +22 -0
- data/lib/grape/dsl/configuration.rb +2 -0
- data/lib/grape/dsl/desc.rb +41 -7
- data/lib/grape/dsl/headers.rb +2 -0
- data/lib/grape/dsl/helpers.rb +5 -2
- data/lib/grape/dsl/inside_route.rb +92 -49
- data/lib/grape/dsl/logger.rb +2 -0
- data/lib/grape/dsl/middleware.rb +9 -0
- data/lib/grape/dsl/parameters.rb +25 -14
- data/lib/grape/dsl/request_response.rb +4 -2
- data/lib/grape/dsl/routing.rb +17 -10
- data/lib/grape/dsl/settings.rb +7 -1
- data/lib/grape/dsl/validations.rb +24 -4
- data/lib/grape/eager_load.rb +20 -0
- data/lib/grape/endpoint.rb +59 -35
- data/lib/grape/error_formatter.rb +4 -2
- data/lib/grape/error_formatter/base.rb +2 -0
- data/lib/grape/error_formatter/json.rb +2 -0
- data/lib/grape/error_formatter/txt.rb +2 -0
- data/lib/grape/error_formatter/xml.rb +2 -0
- data/lib/grape/exceptions/base.rb +20 -14
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +2 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +2 -0
- data/lib/grape/exceptions/invalid_formatter.rb +2 -0
- data/lib/grape/exceptions/invalid_message_body.rb +2 -0
- data/lib/grape/exceptions/invalid_response.rb +11 -0
- data/lib/grape/exceptions/invalid_version_header.rb +2 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +2 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -0
- data/lib/grape/exceptions/method_not_allowed.rb +2 -0
- data/lib/grape/exceptions/missing_group_type.rb +2 -0
- data/lib/grape/exceptions/missing_mime_type.rb +2 -0
- data/lib/grape/exceptions/missing_option.rb +2 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +2 -0
- data/lib/grape/exceptions/unknown_options.rb +2 -0
- data/lib/grape/exceptions/unknown_parameter.rb +2 -0
- data/lib/grape/exceptions/unknown_validator.rb +2 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +2 -0
- data/lib/grape/exceptions/validation.rb +4 -2
- data/lib/grape/exceptions/validation_array_errors.rb +2 -0
- data/lib/grape/exceptions/validation_errors.rb +16 -13
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
- data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
- data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
- data/lib/grape/extensions/hash.rb +2 -0
- data/lib/grape/extensions/hashie/mash.rb +2 -0
- data/lib/grape/formatter.rb +5 -3
- data/lib/grape/formatter/json.rb +2 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -0
- data/lib/grape/formatter/txt.rb +2 -0
- data/lib/grape/formatter/xml.rb +2 -0
- data/lib/grape/http/headers.rb +50 -18
- data/lib/grape/locale/en.yml +3 -1
- data/lib/grape/middleware/auth/base.rb +7 -7
- data/lib/grape/middleware/auth/dsl.rb +2 -0
- data/lib/grape/middleware/auth/strategies.rb +2 -0
- data/lib/grape/middleware/auth/strategy_info.rb +2 -0
- data/lib/grape/middleware/base.rb +10 -7
- data/lib/grape/middleware/error.rb +21 -16
- data/lib/grape/middleware/filter.rb +2 -0
- data/lib/grape/middleware/formatter.rb +8 -6
- data/lib/grape/middleware/globals.rb +2 -0
- data/lib/grape/middleware/helpers.rb +12 -0
- data/lib/grape/middleware/stack.rb +13 -3
- data/lib/grape/middleware/versioner.rb +2 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -0
- data/lib/grape/middleware/versioner/header.rb +10 -8
- data/lib/grape/middleware/versioner/param.rb +3 -1
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +4 -1
- data/lib/grape/middleware/versioner/path.rb +3 -1
- data/lib/grape/namespace.rb +14 -2
- data/lib/grape/parser.rb +4 -2
- data/lib/grape/parser/json.rb +3 -1
- data/lib/grape/parser/xml.rb +3 -1
- data/lib/grape/path.rb +15 -3
- data/lib/grape/presenters/presenter.rb +2 -0
- data/lib/grape/request.rb +19 -10
- data/lib/grape/router.rb +30 -29
- data/lib/grape/router/attribute_translator.rb +41 -8
- data/lib/grape/router/pattern.rb +20 -16
- data/lib/grape/router/route.rb +14 -28
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +3 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +3 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +10 -8
- data/lib/grape/util/base_inheritable.rb +43 -0
- data/lib/grape/util/cache.rb +20 -0
- data/lib/grape/util/endpoint_configuration.rb +8 -0
- data/lib/grape/util/env.rb +19 -17
- data/lib/grape/util/inheritable_setting.rb +2 -0
- data/lib/grape/util/inheritable_values.rb +7 -25
- data/lib/grape/util/json.rb +2 -0
- data/lib/grape/util/lazy_block.rb +27 -0
- data/lib/grape/util/lazy_object.rb +43 -0
- data/lib/grape/util/lazy_value.rb +98 -0
- data/lib/grape/util/registrable.rb +2 -0
- data/lib/grape/util/reverse_stackable_values.rb +10 -35
- data/lib/grape/util/stackable_values.rb +21 -34
- data/lib/grape/util/strict_hash_configuration.rb +2 -0
- data/lib/grape/util/xml.rb +2 -0
- data/lib/grape/validations.rb +2 -0
- data/lib/grape/validations/attributes_iterator.rb +16 -6
- data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
- data/lib/grape/validations/params_scope.rb +51 -30
- data/lib/grape/validations/single_attribute_iterator.rb +24 -0
- data/lib/grape/validations/types.rb +13 -38
- data/lib/grape/validations/types/array_coercer.rb +65 -0
- data/lib/grape/validations/types/build_coercer.rb +47 -49
- data/lib/grape/validations/types/custom_type_coercer.rb +29 -51
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
- data/lib/grape/validations/types/dry_type_coercer.rb +76 -0
- data/lib/grape/validations/types/file.rb +22 -18
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +46 -39
- data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
- data/lib/grape/validations/types/primitive_coercer.rb +67 -0
- data/lib/grape/validations/types/set_coercer.rb +40 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +5 -13
- data/lib/grape/validations/validator_factory.rb +8 -11
- data/lib/grape/validations/validators/all_or_none.rb +8 -13
- data/lib/grape/validations/validators/allow_blank.rb +3 -1
- data/lib/grape/validations/validators/as.rb +5 -4
- data/lib/grape/validations/validators/at_least_one_of.rb +7 -13
- data/lib/grape/validations/validators/base.rb +20 -16
- data/lib/grape/validations/validators/coerce.rb +46 -29
- data/lib/grape/validations/validators/default.rb +6 -6
- data/lib/grape/validations/validators/exactly_one_of.rb +10 -23
- data/lib/grape/validations/validators/except_values.rb +4 -2
- data/lib/grape/validations/validators/multiple_params_base.rb +17 -10
- data/lib/grape/validations/validators/mutual_exclusion.rb +8 -18
- data/lib/grape/validations/validators/presence.rb +3 -1
- data/lib/grape/validations/validators/regexp.rb +4 -2
- data/lib/grape/validations/validators/same_as.rb +26 -0
- data/lib/grape/validations/validators/values.rb +18 -6
- data/lib/grape/version.rb +3 -1
- data/spec/grape/api/custom_validations_spec.rb +5 -3
- data/spec/grape/api/deeply_included_options_spec.rb +2 -0
- data/spec/grape/api/defines_boolean_in_params_spec.rb +39 -0
- data/spec/grape/api/inherited_helpers_spec.rb +2 -0
- data/spec/grape/api/instance_spec.rb +104 -0
- data/spec/grape/api/invalid_format_spec.rb +2 -0
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +2 -0
- data/spec/grape/api/nested_helpers_spec.rb +2 -0
- data/spec/grape/api/optional_parameters_in_route_spec.rb +2 -0
- data/spec/grape/api/parameters_modification_spec.rb +3 -1
- data/spec/grape/api/patch_method_helpers_spec.rb +2 -0
- data/spec/grape/api/recognize_path_spec.rb +2 -0
- data/spec/grape/api/required_parameters_in_route_spec.rb +2 -0
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +2 -0
- data/spec/grape/api/routes_with_requirements_spec.rb +61 -0
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +2 -0
- data/spec/grape/api/shared_helpers_spec.rb +2 -0
- data/spec/grape/api_remount_spec.rb +473 -0
- data/spec/grape/api_spec.rb +565 -12
- data/spec/grape/config_spec.rb +19 -0
- data/spec/grape/dsl/callbacks_spec.rb +2 -0
- data/spec/grape/dsl/configuration_spec.rb +2 -0
- data/spec/grape/dsl/desc_spec.rb +42 -16
- data/spec/grape/dsl/headers_spec.rb +2 -0
- data/spec/grape/dsl/helpers_spec.rb +4 -2
- data/spec/grape/dsl/inside_route_spec.rb +184 -33
- data/spec/grape/dsl/logger_spec.rb +2 -0
- data/spec/grape/dsl/middleware_spec.rb +10 -0
- data/spec/grape/dsl/parameters_spec.rb +2 -0
- data/spec/grape/dsl/request_response_spec.rb +2 -0
- data/spec/grape/dsl/routing_spec.rb +12 -0
- data/spec/grape/dsl/settings_spec.rb +2 -0
- data/spec/grape/dsl/validations_spec.rb +2 -0
- data/spec/grape/endpoint/declared_spec.rb +601 -0
- data/spec/grape/endpoint_spec.rb +53 -523
- data/spec/grape/entity_spec.rb +9 -1
- data/spec/grape/exceptions/base_spec.rb +67 -0
- data/spec/grape/exceptions/body_parse_errors_spec.rb +2 -0
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +2 -0
- data/spec/grape/exceptions/invalid_formatter_spec.rb +2 -0
- data/spec/grape/exceptions/invalid_response_spec.rb +13 -0
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +2 -0
- data/spec/grape/exceptions/missing_option_spec.rb +2 -0
- data/spec/grape/exceptions/unknown_options_spec.rb +2 -0
- data/spec/grape/exceptions/unknown_validator_spec.rb +2 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +8 -4
- data/spec/grape/exceptions/validation_spec.rb +3 -1
- data/spec/grape/extensions/param_builders/hash_spec.rb +2 -0
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +2 -0
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +2 -0
- data/spec/grape/integration/global_namespace_function_spec.rb +2 -0
- data/spec/grape/integration/rack_sendfile_spec.rb +14 -8
- data/spec/grape/integration/rack_spec.rb +25 -7
- data/spec/grape/loading_spec.rb +2 -0
- data/spec/grape/middleware/auth/base_spec.rb +2 -0
- data/spec/grape/middleware/auth/dsl_spec.rb +5 -3
- data/spec/grape/middleware/auth/strategies_spec.rb +3 -1
- data/spec/grape/middleware/base_spec.rb +10 -0
- data/spec/grape/middleware/error_spec.rb +3 -1
- data/spec/grape/middleware/exception_spec.rb +4 -2
- data/spec/grape/middleware/formatter_spec.rb +33 -16
- data/spec/grape/middleware/globals_spec.rb +2 -0
- data/spec/grape/middleware/stack_spec.rb +12 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -1
- data/spec/grape/middleware/versioner/header_spec.rb +9 -1
- data/spec/grape/middleware/versioner/param_spec.rb +3 -1
- data/spec/grape/middleware/versioner/path_spec.rb +3 -1
- data/spec/grape/middleware/versioner_spec.rb +2 -0
- data/spec/grape/named_api_spec.rb +21 -0
- data/spec/grape/parser_spec.rb +7 -5
- data/spec/grape/path_spec.rb +6 -4
- data/spec/grape/presenters/presenter_spec.rb +2 -0
- data/spec/grape/request_spec.rb +26 -0
- data/spec/grape/util/inheritable_setting_spec.rb +2 -0
- data/spec/grape/util/inheritable_values_spec.rb +2 -0
- data/spec/grape/util/reverse_stackable_values_spec.rb +2 -0
- data/spec/grape/util/stackable_values_spec.rb +3 -1
- data/spec/grape/util/strict_hash_configuration_spec.rb +2 -0
- data/spec/grape/validations/attributes_iterator_spec.rb +2 -0
- data/spec/grape/validations/instance_behaivour_spec.rb +5 -3
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +41 -0
- data/spec/grape/validations/params_scope_spec.rb +213 -9
- data/spec/grape/validations/single_attribute_iterator_spec.rb +58 -0
- 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 +9 -36
- data/spec/grape/validations/validators/all_or_none_spec.rb +140 -30
- data/spec/grape/validations/validators/allow_blank_spec.rb +2 -0
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +175 -29
- data/spec/grape/validations/validators/coerce_spec.rb +476 -135
- data/spec/grape/validations/validators/default_spec.rb +172 -0
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +204 -38
- data/spec/grape/validations/validators/except_values_spec.rb +4 -1
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +186 -27
- data/spec/grape/validations/validators/presence_spec.rb +30 -0
- data/spec/grape/validations/validators/regexp_spec.rb +2 -0
- data/spec/grape/validations/validators/same_as_spec.rb +65 -0
- data/spec/grape/validations/validators/values_spec.rb +30 -5
- data/spec/grape/validations_spec.rb +388 -50
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/integration/multi_json/json_spec.rb +2 -0
- data/spec/integration/multi_xml/xml_spec.rb +2 -0
- data/spec/shared/versioning_examples.rb +22 -20
- data/spec/spec_helper.rb +12 -1
- data/spec/support/basic_auth_encode_helpers.rb +2 -0
- data/spec/support/chunks.rb +14 -0
- data/spec/support/content_type_helpers.rb +2 -0
- data/spec/support/eager_load.rb +19 -0
- data/spec/support/endpoint_faker.rb +2 -0
- data/spec/support/file_streamer.rb +2 -0
- data/spec/support/integer_helpers.rb +2 -0
- data/spec/support/versioned_helpers.rb +8 -8
- metadata +86 -48
- data/Appraisals +0 -32
- data/Dangerfile +0 -2
- data/Gemfile +0 -33
- data/Gemfile.lock +0 -231
- data/Guardfile +0 -10
- data/RELEASING.md +0 -111
- data/Rakefile +0 -25
- data/benchmark/simple.rb +0 -27
- data/benchmark/simple_with_type_coercer.rb +0 -22
- data/gemfiles/multi_json.gemfile +0 -35
- data/gemfiles/multi_xml.gemfile +0 -35
- data/gemfiles/rack_1.5.2.gemfile +0 -35
- data/gemfiles/rack_edge.gemfile +0 -35
- data/gemfiles/rails_3.gemfile +0 -36
- data/gemfiles/rails_4.gemfile +0 -35
- data/gemfiles/rails_5.gemfile +0 -35
- data/gemfiles/rails_edge.gemfile +0 -35
- data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
- data/lib/grape/util/content_types.rb +0 -26
- data/lib/grape/validations/types/virtus_collection_patch.rb +0 -16
- data/pkg/grape-0.17.0.gem +0 -0
- data/pkg/grape-0.19.0.gem +0 -0
data/spec/grape/api_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'shared/versioning_examples'
|
3
5
|
|
@@ -220,6 +222,15 @@ describe Grape::API do
|
|
220
222
|
end
|
221
223
|
end
|
222
224
|
|
225
|
+
describe '.call' do
|
226
|
+
context 'it does not add to the app setup' do
|
227
|
+
it 'calls the app' do
|
228
|
+
expect(subject).not_to receive(:add_setup)
|
229
|
+
subject.call({})
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
223
234
|
describe '.route_param' do
|
224
235
|
it 'adds a parameterized route segment namespace' do
|
225
236
|
subject.namespace :users do
|
@@ -805,6 +816,71 @@ XML
|
|
805
816
|
end
|
806
817
|
end
|
807
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
|
+
|
808
884
|
context 'allows HEAD on a GET request that' do
|
809
885
|
before do
|
810
886
|
subject.get 'example' do
|
@@ -876,6 +952,40 @@ XML
|
|
876
952
|
end
|
877
953
|
end
|
878
954
|
|
955
|
+
describe '.compile!' do
|
956
|
+
it 'requires the grape/eager_load file' do
|
957
|
+
expect(app).to receive(:require).with('grape/eager_load') { nil }
|
958
|
+
app.compile!
|
959
|
+
end
|
960
|
+
|
961
|
+
it 'compiles the instance for rack!' do
|
962
|
+
stubbed_object = double(:instance_for_rack)
|
963
|
+
allow(app).to receive(:instance_for_rack) { stubbed_object }
|
964
|
+
end
|
965
|
+
end
|
966
|
+
|
967
|
+
# NOTE: this method is required to preserve the ability of pre-mounting
|
968
|
+
# the root API into a namespace, it may be deprecated in the future.
|
969
|
+
describe 'instance_for_rack' do
|
970
|
+
context 'when the app was not mounted' do
|
971
|
+
it 'returns the base_instance' do
|
972
|
+
expect(app.send(:instance_for_rack)).to eq app.base_instance
|
973
|
+
end
|
974
|
+
end
|
975
|
+
|
976
|
+
context 'when the app was mounted' do
|
977
|
+
it 'returns the first mounted instance' do
|
978
|
+
mounted_app = app
|
979
|
+
Class.new(Grape::API) do
|
980
|
+
namespace 'new_namespace' do
|
981
|
+
mount mounted_app
|
982
|
+
end
|
983
|
+
end
|
984
|
+
expect(app.send(:instance_for_rack)).to eq app.send(:mounted_instances).first
|
985
|
+
end
|
986
|
+
end
|
987
|
+
end
|
988
|
+
|
879
989
|
describe 'filters' do
|
880
990
|
it 'adds a before filter' do
|
881
991
|
subject.before { @foo = 'first' }
|
@@ -890,7 +1000,7 @@ XML
|
|
890
1000
|
|
891
1001
|
it 'adds a before filter to current and child namespaces only' do
|
892
1002
|
subject.get '/' do
|
893
|
-
"root - #{@foo}"
|
1003
|
+
"root - #{instance_variable_defined?(:@foo) ? @foo : nil}"
|
894
1004
|
end
|
895
1005
|
subject.namespace :blah do
|
896
1006
|
before { @foo = 'foo' }
|
@@ -1039,6 +1149,11 @@ XML
|
|
1039
1149
|
expect(last_response.headers['Content-Type']).to eq('text/plain')
|
1040
1150
|
end
|
1041
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
|
+
|
1042
1157
|
it 'sets content type for xml' do
|
1043
1158
|
get '/foo.xml'
|
1044
1159
|
expect(last_response.headers['Content-Type']).to eq('application/xml')
|
@@ -1090,7 +1205,7 @@ XML
|
|
1090
1205
|
|
1091
1206
|
subject.use Rack::Chunked
|
1092
1207
|
subject.get('/stream') { stream test_stream }
|
1093
|
-
get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1'
|
1208
|
+
get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1'
|
1094
1209
|
|
1095
1210
|
expect(last_response.headers['Content-Type']).to eq('text/plain')
|
1096
1211
|
expect(last_response.headers['Content-Length']).to eq(nil)
|
@@ -1348,6 +1463,28 @@ XML
|
|
1348
1463
|
end
|
1349
1464
|
end
|
1350
1465
|
|
1466
|
+
describe '.insert' do
|
1467
|
+
it 'inserts middleware in a specific location in the stack' do
|
1468
|
+
m = Class.new(Grape::Middleware::Base) do
|
1469
|
+
def call(env)
|
1470
|
+
env['phony.args'] ||= []
|
1471
|
+
env['phony.args'] << @options[:message]
|
1472
|
+
@app.call(env)
|
1473
|
+
end
|
1474
|
+
end
|
1475
|
+
|
1476
|
+
subject.use ApiSpec::PhonyMiddleware, 'bye'
|
1477
|
+
subject.insert 0, m, message: 'good'
|
1478
|
+
subject.insert 0, m, message: 'hello'
|
1479
|
+
subject.get '/' do
|
1480
|
+
env['phony.args'].join(' ')
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
get '/'
|
1484
|
+
expect(last_response.body).to eql 'hello good bye'
|
1485
|
+
end
|
1486
|
+
end
|
1487
|
+
|
1351
1488
|
describe '.http_basic' do
|
1352
1489
|
it 'protects any resources on the same scope' do
|
1353
1490
|
subject.http_basic do |u, _p|
|
@@ -1463,6 +1600,11 @@ XML
|
|
1463
1600
|
expect(subject.io).to receive(:write).with(message)
|
1464
1601
|
subject.logger.info 'this will be logged'
|
1465
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
|
1466
1608
|
end
|
1467
1609
|
|
1468
1610
|
describe '.helpers' do
|
@@ -1597,6 +1739,199 @@ XML
|
|
1597
1739
|
end
|
1598
1740
|
end
|
1599
1741
|
|
1742
|
+
describe 'lifecycle' do
|
1743
|
+
let!(:lifecycle) { [] }
|
1744
|
+
let!(:standard_cycle) do
|
1745
|
+
%i[before before_validation after_validation api_call after finally]
|
1746
|
+
end
|
1747
|
+
|
1748
|
+
let!(:validation_error) do
|
1749
|
+
%i[before before_validation finally]
|
1750
|
+
end
|
1751
|
+
|
1752
|
+
let!(:errored_cycle) do
|
1753
|
+
%i[before before_validation after_validation api_call finally]
|
1754
|
+
end
|
1755
|
+
|
1756
|
+
before do
|
1757
|
+
current_cycle = lifecycle
|
1758
|
+
|
1759
|
+
subject.before do
|
1760
|
+
current_cycle << :before
|
1761
|
+
end
|
1762
|
+
|
1763
|
+
subject.before_validation do
|
1764
|
+
current_cycle << :before_validation
|
1765
|
+
end
|
1766
|
+
|
1767
|
+
subject.after_validation do
|
1768
|
+
current_cycle << :after_validation
|
1769
|
+
end
|
1770
|
+
|
1771
|
+
subject.after do
|
1772
|
+
current_cycle << :after
|
1773
|
+
end
|
1774
|
+
|
1775
|
+
subject.finally do
|
1776
|
+
current_cycle << :finally
|
1777
|
+
end
|
1778
|
+
end
|
1779
|
+
|
1780
|
+
context 'when the api_call succeeds' do
|
1781
|
+
before do
|
1782
|
+
current_cycle = lifecycle
|
1783
|
+
|
1784
|
+
subject.get 'api_call' do
|
1785
|
+
current_cycle << :api_call
|
1786
|
+
end
|
1787
|
+
end
|
1788
|
+
|
1789
|
+
it 'follows the standard life_cycle' do
|
1790
|
+
get '/api_call'
|
1791
|
+
expect(lifecycle).to eq standard_cycle
|
1792
|
+
end
|
1793
|
+
end
|
1794
|
+
|
1795
|
+
context 'when the api_call has a controlled error' do
|
1796
|
+
before do
|
1797
|
+
current_cycle = lifecycle
|
1798
|
+
|
1799
|
+
subject.get 'api_call' do
|
1800
|
+
current_cycle << :api_call
|
1801
|
+
error!(:some_error)
|
1802
|
+
end
|
1803
|
+
end
|
1804
|
+
|
1805
|
+
it 'follows the errored life_cycle (skips after)' do
|
1806
|
+
get '/api_call'
|
1807
|
+
expect(lifecycle).to eq errored_cycle
|
1808
|
+
end
|
1809
|
+
end
|
1810
|
+
|
1811
|
+
context 'when the api_call has an exception' do
|
1812
|
+
before do
|
1813
|
+
current_cycle = lifecycle
|
1814
|
+
|
1815
|
+
subject.get 'api_call' do
|
1816
|
+
current_cycle << :api_call
|
1817
|
+
raise StandardError
|
1818
|
+
end
|
1819
|
+
end
|
1820
|
+
|
1821
|
+
it 'follows the errored life_cycle (skips after)' do
|
1822
|
+
expect { get '/api_call' }.to raise_error(StandardError)
|
1823
|
+
expect(lifecycle).to eq errored_cycle
|
1824
|
+
end
|
1825
|
+
end
|
1826
|
+
|
1827
|
+
context 'when the api_call fails validation' do
|
1828
|
+
before do
|
1829
|
+
current_cycle = lifecycle
|
1830
|
+
|
1831
|
+
subject.params do
|
1832
|
+
requires :some_param, type: String
|
1833
|
+
end
|
1834
|
+
|
1835
|
+
subject.get 'api_call' do
|
1836
|
+
current_cycle << :api_call
|
1837
|
+
end
|
1838
|
+
end
|
1839
|
+
|
1840
|
+
it 'follows the failed_validation cycle (skips after_validation, api_call & after)' do
|
1841
|
+
get '/api_call'
|
1842
|
+
expect(lifecycle).to eq validation_error
|
1843
|
+
end
|
1844
|
+
end
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
describe '.finally' do
|
1848
|
+
let!(:code) { { has_executed: false } }
|
1849
|
+
let(:block_to_run) do
|
1850
|
+
code_to_execute = code
|
1851
|
+
proc do
|
1852
|
+
code_to_execute[:has_executed] = true
|
1853
|
+
end
|
1854
|
+
end
|
1855
|
+
|
1856
|
+
context 'when the ensure block has no exceptions' do
|
1857
|
+
before { subject.finally(&block_to_run) }
|
1858
|
+
|
1859
|
+
context 'when no API call is made' do
|
1860
|
+
it 'has not executed the ensure code' do
|
1861
|
+
expect(code[:has_executed]).to be false
|
1862
|
+
end
|
1863
|
+
end
|
1864
|
+
|
1865
|
+
context 'when no errors occurs' do
|
1866
|
+
before do
|
1867
|
+
subject.get '/no_exceptions' do
|
1868
|
+
'success'
|
1869
|
+
end
|
1870
|
+
end
|
1871
|
+
|
1872
|
+
it 'executes the ensure code' do
|
1873
|
+
get '/no_exceptions'
|
1874
|
+
expect(last_response.body).to eq 'success'
|
1875
|
+
expect(code[:has_executed]).to be true
|
1876
|
+
end
|
1877
|
+
|
1878
|
+
context 'with a helper' do
|
1879
|
+
let(:block_to_run) do
|
1880
|
+
code_to_execute = code
|
1881
|
+
proc do
|
1882
|
+
code_to_execute[:value] = some_helper
|
1883
|
+
end
|
1884
|
+
end
|
1885
|
+
|
1886
|
+
before do
|
1887
|
+
subject.helpers do
|
1888
|
+
def some_helper
|
1889
|
+
'some_value'
|
1890
|
+
end
|
1891
|
+
end
|
1892
|
+
|
1893
|
+
subject.get '/with_helpers' do
|
1894
|
+
'success'
|
1895
|
+
end
|
1896
|
+
end
|
1897
|
+
|
1898
|
+
it 'has access to the helper' do
|
1899
|
+
get '/with_helpers'
|
1900
|
+
expect(code[:value]).to eq 'some_value'
|
1901
|
+
end
|
1902
|
+
end
|
1903
|
+
end
|
1904
|
+
|
1905
|
+
context 'when an unhandled occurs inside the API call' do
|
1906
|
+
before do
|
1907
|
+
subject.get '/unhandled_exception' do
|
1908
|
+
raise StandardError
|
1909
|
+
end
|
1910
|
+
end
|
1911
|
+
|
1912
|
+
it 'executes the ensure code' do
|
1913
|
+
expect { get '/unhandled_exception' }.to raise_error StandardError
|
1914
|
+
expect(code[:has_executed]).to be true
|
1915
|
+
end
|
1916
|
+
end
|
1917
|
+
|
1918
|
+
context 'when a handled error occurs inside the API call' do
|
1919
|
+
before do
|
1920
|
+
subject.rescue_from(StandardError) { error! 'handled' }
|
1921
|
+
subject.get '/handled_exception' do
|
1922
|
+
raise StandardError
|
1923
|
+
end
|
1924
|
+
end
|
1925
|
+
|
1926
|
+
it 'executes the ensure code' do
|
1927
|
+
get '/handled_exception'
|
1928
|
+
expect(code[:has_executed]).to be true
|
1929
|
+
expect(last_response.body).to eq 'handled'
|
1930
|
+
end
|
1931
|
+
end
|
1932
|
+
end
|
1933
|
+
end
|
1934
|
+
|
1600
1935
|
describe '.rescue_from' do
|
1601
1936
|
it 'does not rescue errors when rescue_from is not set' do
|
1602
1937
|
subject.get '/exception' do
|
@@ -1643,9 +1978,9 @@ XML
|
|
1643
1978
|
it 'avoids polluting global namespace' do
|
1644
1979
|
env = Rack::MockRequest.env_for('/')
|
1645
1980
|
|
1646
|
-
expect(a.call(env)[2]
|
1647
|
-
expect(b.call(env)[2]
|
1648
|
-
expect(a.call(env)[2]
|
1981
|
+
expect(read_chunks(a.call(env)[2])).to eq(['foo'])
|
1982
|
+
expect(read_chunks(b.call(env)[2])).to eq(['bar'])
|
1983
|
+
expect(read_chunks(a.call(env)[2])).to eq(['foo'])
|
1649
1984
|
end
|
1650
1985
|
end
|
1651
1986
|
|
@@ -1682,6 +2017,26 @@ XML
|
|
1682
2017
|
expect { get '/unrescued' }.to raise_error(RuntimeError, 'beefcake')
|
1683
2018
|
end
|
1684
2019
|
|
2020
|
+
it 'mimics default ruby "rescue" handler' do
|
2021
|
+
# The exception is matched to the rescue starting at the top, and matches only once
|
2022
|
+
|
2023
|
+
subject.rescue_from ArgumentError do |e|
|
2024
|
+
error!(e, 402)
|
2025
|
+
end
|
2026
|
+
subject.rescue_from StandardError do |e|
|
2027
|
+
error!(e, 401)
|
2028
|
+
end
|
2029
|
+
|
2030
|
+
subject.get('/child_of_standard_error') { raise ArgumentError }
|
2031
|
+
subject.get('/standard_error') { raise StandardError }
|
2032
|
+
|
2033
|
+
get '/child_of_standard_error'
|
2034
|
+
expect(last_response.status).to eql 402
|
2035
|
+
|
2036
|
+
get '/standard_error'
|
2037
|
+
expect(last_response.status).to eql 401
|
2038
|
+
end
|
2039
|
+
|
1685
2040
|
context 'CustomError subclass of Grape::Exceptions::Base' do
|
1686
2041
|
before do
|
1687
2042
|
module ApiSpec
|
@@ -1700,7 +2055,7 @@ XML
|
|
1700
2055
|
rack_response('New Error', e.status)
|
1701
2056
|
end
|
1702
2057
|
subject.get '/custom_error' do
|
1703
|
-
raise ApiSpec::CustomError
|
2058
|
+
raise ApiSpec::CustomError.new(status: 400, message: 'Custom Error')
|
1704
2059
|
end
|
1705
2060
|
|
1706
2061
|
get '/custom_error'
|
@@ -1723,6 +2078,16 @@ XML
|
|
1723
2078
|
expect(last_response.status).to eql 500
|
1724
2079
|
expect(last_response.body).to eq('Formatter Error')
|
1725
2080
|
end
|
2081
|
+
|
2082
|
+
it 'uses default_rescue_handler to handle invalid response from rescue_from' do
|
2083
|
+
subject.rescue_from(:all) { 'error' }
|
2084
|
+
subject.get('/') { raise }
|
2085
|
+
|
2086
|
+
expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
|
2087
|
+
get '/'
|
2088
|
+
expect(last_response.status).to eql 500
|
2089
|
+
expect(last_response.body).to eql 'Invalid response'
|
2090
|
+
end
|
1726
2091
|
end
|
1727
2092
|
|
1728
2093
|
describe '.rescue_from klass, block' do
|
@@ -3177,6 +3542,43 @@ XML
|
|
3177
3542
|
expect { a.mount b }.to_not raise_error
|
3178
3543
|
end
|
3179
3544
|
end
|
3545
|
+
|
3546
|
+
context 'when including a module' do
|
3547
|
+
let(:included_module) do
|
3548
|
+
Module.new do
|
3549
|
+
def self.included(base)
|
3550
|
+
base.extend(ClassMethods)
|
3551
|
+
end
|
3552
|
+
module ClassMethods
|
3553
|
+
def my_method
|
3554
|
+
@test = true
|
3555
|
+
end
|
3556
|
+
end
|
3557
|
+
end
|
3558
|
+
end
|
3559
|
+
|
3560
|
+
it 'should correctly include module in nested mount' do
|
3561
|
+
module_to_include = included_module
|
3562
|
+
v1 = Class.new(Grape::API) do
|
3563
|
+
version :v1, using: :path
|
3564
|
+
include module_to_include
|
3565
|
+
my_method
|
3566
|
+
end
|
3567
|
+
v2 = Class.new(Grape::API) do
|
3568
|
+
version :v2, using: :path
|
3569
|
+
end
|
3570
|
+
segment_base = Class.new(Grape::API) do
|
3571
|
+
mount v1
|
3572
|
+
mount v2
|
3573
|
+
end
|
3574
|
+
|
3575
|
+
Class.new(Grape::API) do
|
3576
|
+
mount segment_base
|
3577
|
+
end
|
3578
|
+
|
3579
|
+
expect(v1.my_method).to be_truthy
|
3580
|
+
end
|
3581
|
+
end
|
3180
3582
|
end
|
3181
3583
|
end
|
3182
3584
|
|
@@ -3192,7 +3594,7 @@ XML
|
|
3192
3594
|
it 'sets the instance' do
|
3193
3595
|
expect(subject.instance).to be_nil
|
3194
3596
|
subject.compile
|
3195
|
-
expect(subject.instance).to be_kind_of(subject)
|
3597
|
+
expect(subject.instance).to be_kind_of(subject.base_instance)
|
3196
3598
|
end
|
3197
3599
|
end
|
3198
3600
|
|
@@ -3350,12 +3752,13 @@ XML
|
|
3350
3752
|
end
|
3351
3753
|
end
|
3352
3754
|
context ':serializable_hash' do
|
3353
|
-
|
3354
|
-
|
3355
|
-
def
|
3356
|
-
{ abc: 'def' }
|
3357
|
-
end
|
3755
|
+
class SerializableHashExample
|
3756
|
+
def serializable_hash
|
3757
|
+
{ abc: 'def' }
|
3358
3758
|
end
|
3759
|
+
end
|
3760
|
+
|
3761
|
+
before(:each) do
|
3359
3762
|
subject.format :serializable_hash
|
3360
3763
|
end
|
3361
3764
|
it 'instance' do
|
@@ -3449,6 +3852,44 @@ XML
|
|
3449
3852
|
end
|
3450
3853
|
end
|
3451
3854
|
|
3855
|
+
describe '.configure' do
|
3856
|
+
context 'when given a block' do
|
3857
|
+
it 'returns self' do
|
3858
|
+
expect(subject.configure {}).to be subject
|
3859
|
+
end
|
3860
|
+
|
3861
|
+
it 'calls the block passing the config' do
|
3862
|
+
call = [false, nil]
|
3863
|
+
subject.configure do |config|
|
3864
|
+
call = [true, config]
|
3865
|
+
end
|
3866
|
+
|
3867
|
+
expect(call[0]).to be true
|
3868
|
+
expect(call[1]).not_to be_nil
|
3869
|
+
end
|
3870
|
+
end
|
3871
|
+
|
3872
|
+
context 'when not given a block' do
|
3873
|
+
it 'returns a configuration object' do
|
3874
|
+
expect(subject.configure).to respond_to(:[], :[]=)
|
3875
|
+
end
|
3876
|
+
end
|
3877
|
+
|
3878
|
+
it 'allows configuring the api' do
|
3879
|
+
subject.configure do |config|
|
3880
|
+
config[:hello] = 'hello'
|
3881
|
+
config[:bread] = 'bread'
|
3882
|
+
end
|
3883
|
+
|
3884
|
+
subject.get '/hello-bread' do
|
3885
|
+
"#{configuration[:hello]} #{configuration[:bread]}"
|
3886
|
+
end
|
3887
|
+
|
3888
|
+
get '/hello-bread'
|
3889
|
+
expect(last_response.body).to eq 'hello bread'
|
3890
|
+
end
|
3891
|
+
end
|
3892
|
+
|
3452
3893
|
context 'catch-all' do
|
3453
3894
|
before do
|
3454
3895
|
api1 = Class.new(Grape::API)
|
@@ -3580,4 +4021,116 @@ XML
|
|
3580
4021
|
end
|
3581
4022
|
end
|
3582
4023
|
end
|
4024
|
+
|
4025
|
+
describe 'normal class methods' do
|
4026
|
+
subject(:grape_api) { Class.new(Grape::API) }
|
4027
|
+
|
4028
|
+
before do
|
4029
|
+
stub_const('MyAPI', grape_api)
|
4030
|
+
end
|
4031
|
+
|
4032
|
+
it 'can find the appropiate name' do
|
4033
|
+
expect(grape_api.name).to eq 'MyAPI'
|
4034
|
+
end
|
4035
|
+
|
4036
|
+
it 'is equal to itself' do
|
4037
|
+
expect(grape_api.itself).to eq grape_api
|
4038
|
+
expect(grape_api).to eq MyAPI
|
4039
|
+
expect(grape_api.eql?(MyAPI))
|
4040
|
+
end
|
4041
|
+
end
|
4042
|
+
|
4043
|
+
describe 'const_missing' do
|
4044
|
+
subject(:grape_api) { Class.new(Grape::API) }
|
4045
|
+
let(:mounted) do
|
4046
|
+
Class.new(Grape::API) do
|
4047
|
+
get '/missing' do
|
4048
|
+
SomeRandomConstant
|
4049
|
+
end
|
4050
|
+
end
|
4051
|
+
end
|
4052
|
+
|
4053
|
+
before { subject.mount mounted => '/const' }
|
4054
|
+
|
4055
|
+
it 'raises an error' do
|
4056
|
+
expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)
|
4057
|
+
end
|
4058
|
+
end
|
4059
|
+
|
4060
|
+
describe 'custom route helpers on nested APIs' do
|
4061
|
+
let(:shared_api_module) do
|
4062
|
+
Module.new do
|
4063
|
+
# rubocop:disable Style/ExplicitBlockArgument because this causes
|
4064
|
+
# the underlying issue in this form
|
4065
|
+
def uniqe_id_route
|
4066
|
+
params do
|
4067
|
+
use :unique_id
|
4068
|
+
end
|
4069
|
+
route_param(:id) do
|
4070
|
+
yield
|
4071
|
+
end
|
4072
|
+
end
|
4073
|
+
# rubocop:enable Style/ExplicitBlockArgument
|
4074
|
+
end
|
4075
|
+
end
|
4076
|
+
let(:shared_api_definitions) do
|
4077
|
+
Module.new do
|
4078
|
+
extend ActiveSupport::Concern
|
4079
|
+
|
4080
|
+
included do
|
4081
|
+
helpers do
|
4082
|
+
params :unique_id do
|
4083
|
+
requires :id, type: String,
|
4084
|
+
allow_blank: false,
|
4085
|
+
regexp: /\d+-\d+/
|
4086
|
+
end
|
4087
|
+
end
|
4088
|
+
end
|
4089
|
+
end
|
4090
|
+
end
|
4091
|
+
let(:orders_root) do
|
4092
|
+
shared = shared_api_definitions
|
4093
|
+
find = orders_find_endpoint
|
4094
|
+
Class.new(Grape::API) do
|
4095
|
+
include shared
|
4096
|
+
|
4097
|
+
namespace(:orders) do
|
4098
|
+
mount find
|
4099
|
+
end
|
4100
|
+
end
|
4101
|
+
end
|
4102
|
+
let(:orders_find_endpoint) do
|
4103
|
+
shared = shared_api_definitions
|
4104
|
+
Class.new(Grape::API) do
|
4105
|
+
include shared
|
4106
|
+
|
4107
|
+
uniqe_id_route do
|
4108
|
+
desc 'Fetch a single order' do
|
4109
|
+
detail 'While specifying the order id on the route'
|
4110
|
+
end
|
4111
|
+
get { params[:id] }
|
4112
|
+
end
|
4113
|
+
end
|
4114
|
+
end
|
4115
|
+
subject(:grape_api) do
|
4116
|
+
Class.new(Grape::API) do
|
4117
|
+
version 'v1', using: :path
|
4118
|
+
end
|
4119
|
+
end
|
4120
|
+
|
4121
|
+
before do
|
4122
|
+
Grape::API::Instance.extend(shared_api_module)
|
4123
|
+
subject.mount orders_root
|
4124
|
+
end
|
4125
|
+
|
4126
|
+
it 'returns an error when the id is bad' do
|
4127
|
+
get '/v1/orders/abc'
|
4128
|
+
expect(last_response.body).to be_eql('id is invalid')
|
4129
|
+
end
|
4130
|
+
|
4131
|
+
it 'returns the given id when it is valid' do
|
4132
|
+
get '/v1/orders/1-2'
|
4133
|
+
expect(last_response.body).to be_eql('1-2')
|
4134
|
+
end
|
4135
|
+
end
|
3583
4136
|
end
|