grape 0.1.1 → 2.4.0
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 +7 -0
- data/CHANGELOG.md +1167 -0
- data/CONTRIBUTING.md +156 -0
- data/LICENSE +1 -1
- data/README.md +4192 -0
- data/UPGRADING.md +1697 -0
- data/grape.gemspec +28 -105
- data/grape.png +0 -0
- data/lib/grape/api/helpers.rb +9 -0
- data/lib/grape/api/instance.rb +247 -0
- data/lib/grape/api.rb +158 -194
- data/lib/grape/content_types.rb +31 -0
- data/lib/grape/cookies.rb +50 -0
- data/lib/grape/dry_types.rb +10 -0
- data/lib/grape/dsl/api.rb +17 -0
- data/lib/grape/dsl/callbacks.rb +69 -0
- data/lib/grape/dsl/configuration.rb +15 -0
- data/lib/grape/dsl/desc.rb +124 -0
- data/lib/grape/dsl/headers.rb +21 -0
- data/lib/grape/dsl/helpers.rb +108 -0
- data/lib/grape/dsl/inside_route.rb +454 -0
- data/lib/grape/dsl/logger.rb +22 -0
- data/lib/grape/dsl/middleware.rb +54 -0
- data/lib/grape/dsl/parameters.rb +266 -0
- data/lib/grape/dsl/request_response.rb +171 -0
- data/lib/grape/dsl/routing.rb +248 -0
- data/lib/grape/dsl/settings.rb +179 -0
- data/lib/grape/dsl/validations.rb +57 -0
- data/lib/grape/endpoint.rb +398 -68
- data/lib/grape/env.rb +20 -0
- data/lib/grape/error_formatter/base.rb +68 -0
- data/lib/grape/error_formatter/json.rb +28 -0
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +21 -0
- data/lib/grape/error_formatter/xml.rb +11 -0
- data/lib/grape/error_formatter.rb +15 -0
- data/lib/grape/exceptions/base.rb +76 -0
- data/lib/grape/exceptions/conflicting_types.rb +11 -0
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +11 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +11 -0
- data/lib/grape/exceptions/invalid_formatter.rb +11 -0
- data/lib/grape/exceptions/invalid_message_body.rb +11 -0
- data/lib/grape/exceptions/invalid_parameters.rb +11 -0
- data/lib/grape/exceptions/invalid_response.rb +11 -0
- data/lib/grape/exceptions/invalid_version_header.rb +11 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +11 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +11 -0
- data/lib/grape/exceptions/method_not_allowed.rb +11 -0
- data/lib/grape/exceptions/missing_group_type.rb +13 -0
- data/lib/grape/exceptions/missing_mime_type.rb +11 -0
- data/lib/grape/exceptions/missing_option.rb +11 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +11 -0
- data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
- data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_options.rb +11 -0
- data/lib/grape/exceptions/unknown_parameter.rb +11 -0
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/unknown_validator.rb +11 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +13 -0
- data/lib/grape/exceptions/validation.rb +25 -0
- data/lib/grape/exceptions/validation_array_errors.rb +14 -0
- data/lib/grape/exceptions/validation_errors.rb +53 -0
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +24 -0
- data/lib/grape/extensions/hash.rb +27 -0
- data/lib/grape/extensions/hashie/mash.rb +24 -0
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +13 -0
- data/lib/grape/formatter/serializable_hash.rb +39 -0
- data/lib/grape/formatter/txt.rb +11 -0
- data/lib/grape/formatter/xml.rb +13 -0
- data/lib/grape/formatter.rb +17 -0
- data/lib/grape/json.rb +10 -0
- data/lib/grape/locale/en.yml +59 -0
- data/lib/grape/middleware/auth/base.rb +23 -0
- data/lib/grape/middleware/auth/dsl.rb +39 -0
- data/lib/grape/middleware/auth/strategies.rb +25 -0
- data/lib/grape/middleware/auth/strategy_info.rb +15 -0
- data/lib/grape/middleware/base.rb +89 -18
- data/lib/grape/middleware/error.rb +129 -10
- data/lib/grape/middleware/filter.rb +19 -0
- data/lib/grape/middleware/formatter.rb +124 -82
- data/lib/grape/middleware/globals.rb +14 -0
- data/lib/grape/middleware/stack.rb +109 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +38 -0
- data/lib/grape/middleware/versioner/base.rb +74 -0
- data/lib/grape/middleware/versioner/header.rb +127 -0
- data/lib/grape/middleware/versioner/param.rb +32 -0
- data/lib/grape/middleware/versioner/path.rb +40 -0
- data/lib/grape/middleware/versioner.rb +21 -21
- data/lib/grape/namespace.rb +46 -0
- data/lib/grape/params_builder/base.rb +18 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +32 -0
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +14 -0
- data/lib/grape/parser/xml.rb +14 -0
- data/lib/grape/parser.rb +15 -0
- data/lib/grape/path.rb +76 -0
- data/lib/grape/presenters/presenter.rb +11 -0
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +190 -0
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +81 -0
- data/lib/grape/router/route.rb +62 -0
- data/lib/grape/router.rb +190 -0
- data/lib/grape/serve_stream/file_body.rb +36 -0
- data/lib/grape/serve_stream/sendfile_response.rb +21 -0
- data/lib/grape/serve_stream/stream_response.rb +23 -0
- data/lib/grape/types/invalid_value.rb +8 -0
- data/lib/grape/util/base_inheritable.rb +43 -0
- data/lib/grape/util/cache.rb +17 -0
- data/lib/grape/util/endpoint_configuration.rb +8 -0
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_setting.rb +100 -0
- data/lib/grape/util/inheritable_values.rb +29 -0
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -0
- data/lib/grape/util/registry.rb +27 -0
- data/lib/grape/util/reverse_stackable_values.rb +15 -0
- data/lib/grape/util/stackable_values.rb +36 -0
- data/lib/grape/util/strict_hash_configuration.rb +108 -0
- data/lib/grape/validations/attributes_doc.rb +60 -0
- data/lib/grape/validations/attributes_iterator.rb +62 -0
- data/lib/grape/validations/contract_scope.rb +34 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
- data/lib/grape/validations/params_scope.rb +538 -0
- data/lib/grape/validations/single_attribute_iterator.rb +26 -0
- data/lib/grape/validations/types/array_coercer.rb +61 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +164 -0
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +56 -0
- data/lib/grape/validations/types/dry_type_coercer.rb +66 -0
- data/lib/grape/validations/types/file.rb +31 -0
- data/lib/grape/validations/types/invalid_value.rb +17 -0
- data/lib/grape/validations/types/json.rb +71 -0
- data/lib/grape/validations/types/multiple_type_coercer.rb +57 -0
- data/lib/grape/validations/types/primitive_coercer.rb +73 -0
- data/lib/grape/validations/types/set_coercer.rb +35 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +51 -0
- data/lib/grape/validations/types.rb +213 -0
- data/lib/grape/validations/validator_factory.rb +15 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
- data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
- data/lib/grape/validations/validators/as_validator.rb +14 -0
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
- data/lib/grape/validations/validators/base.rb +88 -0
- data/lib/grape/validations/validators/coerce_validator.rb +75 -0
- data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
- data/lib/grape/validations/validators/default_validator.rb +37 -0
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
- data/lib/grape/validations/validators/except_values_validator.rb +24 -0
- data/lib/grape/validations/validators/length_validator.rb +49 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +34 -0
- data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
- data/lib/grape/validations/validators/presence_validator.rb +15 -0
- data/lib/grape/validations/validators/regexp_validator.rb +16 -0
- data/lib/grape/validations/validators/same_as_validator.rb +29 -0
- data/lib/grape/validations/validators/values_validator.rb +60 -0
- data/lib/grape/validations.rb +21 -0
- data/lib/grape/version.rb +6 -0
- data/lib/grape/xml.rb +10 -0
- data/lib/grape.rb +81 -17
- metadata +250 -192
- data/.document +0 -5
- data/.gitignore +0 -23
- data/.rspec +0 -2
- data/.rvmrc +0 -1
- data/Gemfile +0 -21
- data/Gemfile.lock +0 -58
- data/README.markdown +0 -75
- data/Rakefile +0 -70
- data/VERSION +0 -1
- data/autotest/discover.rb +0 -1
- data/lib/grape/middleware/auth/basic.rb +0 -30
- data/lib/grape/middleware/auth/oauth2.rb +0 -55
- data/lib/grape/middleware/prefixer.rb +0 -21
- data/lib/grape/middleware_stack.rb +0 -35
- data/spec/grape/api_spec.rb +0 -343
- data/spec/grape/endpoint_spec.rb +0 -104
- data/spec/grape/middleware/auth/basic_spec.rb +0 -31
- data/spec/grape/middleware/auth/oauth2_spec.rb +0 -88
- data/spec/grape/middleware/base_spec.rb +0 -63
- data/spec/grape/middleware/error_spec.rb +0 -49
- data/spec/grape/middleware/formatter_spec.rb +0 -87
- data/spec/grape/middleware/prefixer_spec.rb +0 -30
- data/spec/grape/middleware/versioner_spec.rb +0 -40
- data/spec/grape/middleware_stack_spec.rb +0 -47
- data/spec/grape_spec.rb +0 -1
- data/spec/spec_helper.rb +0 -20
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module DSL
|
|
5
|
+
module Desc
|
|
6
|
+
include Grape::DSL::Settings
|
|
7
|
+
|
|
8
|
+
ROUTE_ATTRIBUTES = %i[
|
|
9
|
+
body_name
|
|
10
|
+
consumes
|
|
11
|
+
default
|
|
12
|
+
deprecated
|
|
13
|
+
description
|
|
14
|
+
detail
|
|
15
|
+
entity
|
|
16
|
+
headers
|
|
17
|
+
hidden
|
|
18
|
+
http_codes
|
|
19
|
+
is_array
|
|
20
|
+
named
|
|
21
|
+
nickname
|
|
22
|
+
params
|
|
23
|
+
produces
|
|
24
|
+
security
|
|
25
|
+
summary
|
|
26
|
+
tags
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
# Add a description to the next namespace or function.
|
|
30
|
+
# @param description [String] descriptive string for this endpoint
|
|
31
|
+
# or namespace
|
|
32
|
+
# @param options [Hash] other properties you can set to describe the
|
|
33
|
+
# endpoint or namespace. Optional.
|
|
34
|
+
# @option options :detail [String] additional detail about this endpoint
|
|
35
|
+
# @option options :summary [String] summary for this endpoint
|
|
36
|
+
# @option options :params [Hash] param types and info. normally, you set
|
|
37
|
+
# these via the `params` dsl method.
|
|
38
|
+
# @option options :entity [Grape::Entity] the entity returned upon a
|
|
39
|
+
# successful call to this action
|
|
40
|
+
# @option options :http_codes [Array[Array]] possible HTTP codes this
|
|
41
|
+
# endpoint may return, with their meanings, in a 2d array
|
|
42
|
+
# @option options :named [String] a specific name to help find this route
|
|
43
|
+
# @option options :body_name [String] override the autogenerated body name param
|
|
44
|
+
# @option options :headers [Hash] HTTP headers this method can accept
|
|
45
|
+
# @option options :hidden [Boolean] hide the endpoint or not
|
|
46
|
+
# @option options :deprecated [Boolean] deprecate the endpoint or not
|
|
47
|
+
# @option options :is_array [Boolean] response entity is array or not
|
|
48
|
+
# @option options :nickname [String] nickname of the endpoint
|
|
49
|
+
# @option options :produces [Array[String]] a list of MIME types the endpoint produce
|
|
50
|
+
# @option options :consumes [Array[String]] a list of MIME types the endpoint consume
|
|
51
|
+
# @option options :security [Array[Hash]] a list of security schemes
|
|
52
|
+
# @option options :tags [Array[String]] a list of tags
|
|
53
|
+
# @yield a block yielding an instance context with methods mapping to
|
|
54
|
+
# each of the above, except that :entity is also aliased as #success
|
|
55
|
+
# and :http_codes is aliased as #failure.
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
#
|
|
59
|
+
# desc 'create a user'
|
|
60
|
+
# post '/users' do
|
|
61
|
+
# # ...
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
# desc 'find a user' do
|
|
65
|
+
# detail 'locates the user from the given user ID'
|
|
66
|
+
# failure [ [404, 'Couldn\'t find the given user' ] ]
|
|
67
|
+
# success User::Entity
|
|
68
|
+
# end
|
|
69
|
+
# get '/user/:id' do
|
|
70
|
+
# # ...
|
|
71
|
+
# end
|
|
72
|
+
#
|
|
73
|
+
def desc(description, options = nil, &config_block)
|
|
74
|
+
opts =
|
|
75
|
+
if config_block
|
|
76
|
+
desc_container(endpoint_configuration).then do |config_class|
|
|
77
|
+
config_class.configure do
|
|
78
|
+
description(description)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
config_class.configure(&config_block)
|
|
82
|
+
config_class.settings
|
|
83
|
+
end
|
|
84
|
+
else
|
|
85
|
+
options&.merge(description: description) || { description: description }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
namespace_setting :description, opts
|
|
89
|
+
route_setting :description, opts
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns an object which configures itself via an instance-context DSL.
|
|
93
|
+
def desc_container(endpoint_configuration)
|
|
94
|
+
Module.new do
|
|
95
|
+
include Grape::Util::StrictHashConfiguration.module(*ROUTE_ATTRIBUTES)
|
|
96
|
+
config_context.define_singleton_method(:configuration) do
|
|
97
|
+
endpoint_configuration
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def config_context.success(*args)
|
|
101
|
+
entity(*args)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def config_context.failure(*args)
|
|
105
|
+
http_codes(*args)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def endpoint_configuration
|
|
113
|
+
return {} unless defined?(configuration)
|
|
114
|
+
|
|
115
|
+
if configuration.respond_to?(:evaluate)
|
|
116
|
+
configuration.evaluate
|
|
117
|
+
# Within `given` or `mounted blocks` the configuration is already evaluated
|
|
118
|
+
elsif configuration.is_a?(Hash)
|
|
119
|
+
configuration
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module DSL
|
|
5
|
+
module Headers
|
|
6
|
+
# This method has four responsibilities:
|
|
7
|
+
# 1. Set a specifc header value by key
|
|
8
|
+
# 2. Retrieve a specifc header value by key
|
|
9
|
+
# 3. Retrieve all headers that have been set
|
|
10
|
+
# 4. Delete a specifc header key-value pair
|
|
11
|
+
def header(key = nil, val = nil)
|
|
12
|
+
if key
|
|
13
|
+
val ? header[key] = val : header.delete(key)
|
|
14
|
+
else
|
|
15
|
+
@header ||= Grape::Util::Header.new
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
alias headers header
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module DSL
|
|
5
|
+
module Helpers
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
include Grape::DSL::Configuration
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# Add helper methods that will be accessible from any
|
|
11
|
+
# endpoint within this namespace (and child namespaces).
|
|
12
|
+
#
|
|
13
|
+
# When called without a block, all known helpers within this scope
|
|
14
|
+
# are included.
|
|
15
|
+
#
|
|
16
|
+
# @param [Array] new_modules optional array of modules to include
|
|
17
|
+
# @param [Block] block optional block of methods to include
|
|
18
|
+
#
|
|
19
|
+
# @example Define some helpers.
|
|
20
|
+
#
|
|
21
|
+
# class ExampleAPI < Grape::API
|
|
22
|
+
# helpers do
|
|
23
|
+
# def current_user
|
|
24
|
+
# User.find_by_id(params[:token])
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @example Include many modules
|
|
30
|
+
#
|
|
31
|
+
# class ExampleAPI < Grape::API
|
|
32
|
+
# helpers Authentication, Mailer, OtherModule
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
def helpers(*new_modules, &block)
|
|
36
|
+
include_new_modules(new_modules)
|
|
37
|
+
include_block(block)
|
|
38
|
+
include_all_in_scope if !block && new_modules.empty?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
def include_new_modules(modules)
|
|
44
|
+
return if modules.empty?
|
|
45
|
+
|
|
46
|
+
modules.each { |mod| make_inclusion(mod) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def include_block(block)
|
|
50
|
+
return unless block
|
|
51
|
+
|
|
52
|
+
Module.new.tap do |mod|
|
|
53
|
+
make_inclusion(mod) { mod.class_eval(&block) }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def make_inclusion(mod, &block)
|
|
58
|
+
define_boolean_in_mod(mod)
|
|
59
|
+
inject_api_helpers_to_mod(mod, &block)
|
|
60
|
+
namespace_stackable(:helpers, mod)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def include_all_in_scope
|
|
64
|
+
Module.new.tap do |mod|
|
|
65
|
+
namespace_stackable(:helpers).each { |mod_to_include| mod.include mod_to_include }
|
|
66
|
+
change!
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def define_boolean_in_mod(mod)
|
|
71
|
+
return if defined? mod::Boolean
|
|
72
|
+
|
|
73
|
+
mod.const_set(:Boolean, Grape::API::Boolean)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def inject_api_helpers_to_mod(mod, &block)
|
|
77
|
+
mod.extend(BaseHelper) unless mod.is_a?(BaseHelper)
|
|
78
|
+
yield if block
|
|
79
|
+
mod.api_changed(self)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# This module extends user defined helpers
|
|
84
|
+
# to provide some API-specific functionality.
|
|
85
|
+
module BaseHelper
|
|
86
|
+
attr_accessor :api
|
|
87
|
+
|
|
88
|
+
def params(name, &block)
|
|
89
|
+
@named_params ||= {}
|
|
90
|
+
@named_params[name] = block
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def api_changed(new_api)
|
|
94
|
+
@api = new_api
|
|
95
|
+
process_named_params
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
protected
|
|
99
|
+
|
|
100
|
+
def process_named_params
|
|
101
|
+
return if @named_params.blank?
|
|
102
|
+
|
|
103
|
+
api.namespace_stackable(:named_params, @named_params)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module DSL
|
|
5
|
+
module InsideRoute
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
include Grape::DSL::Settings
|
|
8
|
+
include Grape::DSL::Headers
|
|
9
|
+
|
|
10
|
+
# Denotes a situation where a DSL method has been invoked in a
|
|
11
|
+
# filter which it should not yet be available in
|
|
12
|
+
class MethodNotYetAvailable < StandardError; end
|
|
13
|
+
|
|
14
|
+
# @param type [Symbol] The type of filter for which evaluation has been
|
|
15
|
+
# completed
|
|
16
|
+
# @return [Module] A module containing method overrides suitable for the
|
|
17
|
+
# position in the filter evaluation sequence denoted by +type+. This
|
|
18
|
+
# defaults to an empty module if no overrides are defined for the given
|
|
19
|
+
# filter +type+.
|
|
20
|
+
def self.post_filter_methods(type)
|
|
21
|
+
@post_filter_modules ||= { before: PostBeforeFilter }
|
|
22
|
+
@post_filter_modules[type]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Methods which should not be available in filters until the before filter
|
|
26
|
+
# has completed
|
|
27
|
+
module PostBeforeFilter
|
|
28
|
+
def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
|
|
29
|
+
options.reverse_merge!(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
|
|
30
|
+
declared_params ||= optioned_declared_params(options[:include_parent_namespaces])
|
|
31
|
+
|
|
32
|
+
res = if passed_params.is_a?(Array)
|
|
33
|
+
declared_array(passed_params, options, declared_params, params_nested_path)
|
|
34
|
+
else
|
|
35
|
+
declared_hash(passed_params, options, declared_params, params_nested_path)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if (key_maps = namespace_stackable(:contract_key_map))
|
|
39
|
+
key_maps.each { |key_map| key_map.write(passed_params, res) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
res
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def declared_array(passed_params, options, declared_params, params_nested_path)
|
|
48
|
+
passed_params.map do |passed_param|
|
|
49
|
+
declared(passed_param || {}, options, declared_params, params_nested_path)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def declared_hash(passed_params, options, declared_params, params_nested_path)
|
|
54
|
+
declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
|
|
55
|
+
next if options[:evaluate_given] && !declared_param_attr.scope.attr_meets_dependency?(passed_params)
|
|
56
|
+
|
|
57
|
+
declared_hash_attr(passed_params, options, declared_param_attr.key, params_nested_path, memo)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def declared_hash_attr(passed_params, options, declared_param, params_nested_path, memo)
|
|
62
|
+
renamed_params = route_setting(:renamed_params) || {}
|
|
63
|
+
if declared_param.is_a?(Hash)
|
|
64
|
+
declared_param.each_pair do |declared_parent_param, declared_children_params|
|
|
65
|
+
params_nested_path_dup = params_nested_path.dup
|
|
66
|
+
params_nested_path_dup << declared_parent_param.to_s
|
|
67
|
+
next unless options[:include_missing] || passed_params.key?(declared_parent_param)
|
|
68
|
+
|
|
69
|
+
rename_path = params_nested_path + [declared_parent_param.to_s]
|
|
70
|
+
renamed_param_name = renamed_params[rename_path]
|
|
71
|
+
|
|
72
|
+
memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
|
|
73
|
+
passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
|
|
74
|
+
|
|
75
|
+
memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
|
|
76
|
+
declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
# If it is not a Hash then it does not have children.
|
|
81
|
+
# Find its value or set it to nil.
|
|
82
|
+
return unless options[:include_missing] || passed_params.try(:key?, declared_param)
|
|
83
|
+
|
|
84
|
+
rename_path = params_nested_path + [declared_param.to_s]
|
|
85
|
+
renamed_param_name = renamed_params[rename_path]
|
|
86
|
+
|
|
87
|
+
memo_key = optioned_param_key(renamed_param_name || declared_param, options)
|
|
88
|
+
passed_param = passed_params[declared_param]
|
|
89
|
+
|
|
90
|
+
params_nested_path_dup = params_nested_path.dup
|
|
91
|
+
params_nested_path_dup << declared_param.to_s
|
|
92
|
+
memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
|
|
93
|
+
passed_param
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def handle_passed_param(params_nested_path, has_passed_children = false, &_block)
|
|
99
|
+
return yield if has_passed_children
|
|
100
|
+
|
|
101
|
+
key = params_nested_path[0]
|
|
102
|
+
key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1
|
|
103
|
+
|
|
104
|
+
route_options_params = options[:route_options][:params] || {}
|
|
105
|
+
type = route_options_params.dig(key, :type)
|
|
106
|
+
has_children = route_options_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }
|
|
107
|
+
|
|
108
|
+
if type == 'Hash' && !has_children
|
|
109
|
+
{}
|
|
110
|
+
elsif type == 'Array' || (type&.start_with?('[') && type.exclude?(','))
|
|
111
|
+
[]
|
|
112
|
+
elsif type == 'Set' || type&.start_with?('#<Set')
|
|
113
|
+
Set.new
|
|
114
|
+
else
|
|
115
|
+
yield
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def optioned_param_key(declared_param, options)
|
|
120
|
+
options[:stringify] ? declared_param.to_s : declared_param.to_sym
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def optioned_declared_params(include_parent_namespaces)
|
|
124
|
+
declared_params = if include_parent_namespaces
|
|
125
|
+
# Declared params including parent namespaces
|
|
126
|
+
route_setting(:declared_params)
|
|
127
|
+
else
|
|
128
|
+
# Declared params at current namespace
|
|
129
|
+
namespace_stackable(:declared_params).last || []
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
|
|
133
|
+
|
|
134
|
+
declared_params
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# A filtering method that will return a hash
|
|
139
|
+
# consisting only of keys that have been declared by a
|
|
140
|
+
# `params` statement against the current/target endpoint or parent
|
|
141
|
+
# namespaces.
|
|
142
|
+
#
|
|
143
|
+
# @see +PostBeforeFilter#declared+
|
|
144
|
+
#
|
|
145
|
+
# @param params [Hash] The initial hash to filter. Usually this will just be `params`
|
|
146
|
+
# @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
|
|
147
|
+
# options. `:include_parent_namespaces` defaults to true, hence must be set to false if
|
|
148
|
+
# you want only to return params declared against the current/target endpoint.
|
|
149
|
+
def declared(*)
|
|
150
|
+
raise MethodNotYetAvailable, '#declared is not available prior to parameter validation.'
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# The API version as specified in the URL.
|
|
154
|
+
def version
|
|
155
|
+
env[Grape::Env::API_VERSION]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def configuration
|
|
159
|
+
options[:for].configuration.evaluate
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# End the request and display an error to the
|
|
163
|
+
# end user with the specified message.
|
|
164
|
+
#
|
|
165
|
+
# @param message [String] The message to display.
|
|
166
|
+
# @param status [Integer] The HTTP Status Code. Defaults to default_error_status, 500 if not set.
|
|
167
|
+
# @param additional_headers [Hash] Addtional headers for the response.
|
|
168
|
+
# @param backtrace [Array<String>] The backtrace of the exception that caused the error.
|
|
169
|
+
# @param original_exception [Exception] The original exception that caused the error.
|
|
170
|
+
def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
|
|
171
|
+
status = self.status(status || namespace_inheritable(:default_error_status))
|
|
172
|
+
headers = additional_headers.present? ? header.merge(additional_headers) : header
|
|
173
|
+
throw :error,
|
|
174
|
+
message: message,
|
|
175
|
+
status: status,
|
|
176
|
+
headers: headers,
|
|
177
|
+
backtrace: backtrace,
|
|
178
|
+
original_exception: original_exception
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Creates a Rack response based on the provided message, status, and headers.
|
|
182
|
+
# The content type in the headers is set to the default content type unless provided.
|
|
183
|
+
# The message is HTML-escaped if the content type is 'text/html'.
|
|
184
|
+
#
|
|
185
|
+
# @param message [String] The content of the response.
|
|
186
|
+
# @param status [Integer] The HTTP status code.
|
|
187
|
+
# @params headers [Hash] (optional) Headers for the response
|
|
188
|
+
# (default: {Rack::CONTENT_TYPE => content_type}).
|
|
189
|
+
#
|
|
190
|
+
# Returns:
|
|
191
|
+
# A Rack::Response object containing the specified message, status, and headers.
|
|
192
|
+
#
|
|
193
|
+
def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => content_type })
|
|
194
|
+
Grape.deprecator.warn('The rack_response method has been deprecated, use error! instead.')
|
|
195
|
+
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
|
|
196
|
+
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), headers)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Redirect to a new url.
|
|
200
|
+
#
|
|
201
|
+
# @param url [String] The url to be redirect.
|
|
202
|
+
# @param permanent [Boolean] default false.
|
|
203
|
+
# @param body default a short message including the URL.
|
|
204
|
+
def redirect(url, permanent: false, body: nil)
|
|
205
|
+
body_message = body
|
|
206
|
+
if permanent
|
|
207
|
+
status 301
|
|
208
|
+
body_message ||= "This resource has been moved permanently to #{url}."
|
|
209
|
+
elsif http_version == 'HTTP/1.1' && !request.get?
|
|
210
|
+
status 303
|
|
211
|
+
body_message ||= "An alternate resource is located at #{url}."
|
|
212
|
+
else
|
|
213
|
+
status 302
|
|
214
|
+
body_message ||= "This resource has been moved temporarily to #{url}."
|
|
215
|
+
end
|
|
216
|
+
header 'Location', url
|
|
217
|
+
content_type 'text/plain'
|
|
218
|
+
body body_message
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Set or retrieve the HTTP status code.
|
|
222
|
+
#
|
|
223
|
+
# @param status [Integer] The HTTP Status Code to return for this request.
|
|
224
|
+
def status(status = nil)
|
|
225
|
+
case status
|
|
226
|
+
when Symbol
|
|
227
|
+
raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
|
|
228
|
+
|
|
229
|
+
@status = Rack::Utils.status_code(status)
|
|
230
|
+
when Integer
|
|
231
|
+
@status = status
|
|
232
|
+
when nil
|
|
233
|
+
return @status if instance_variable_defined?(:@status) && @status
|
|
234
|
+
|
|
235
|
+
if request.post?
|
|
236
|
+
201
|
|
237
|
+
elsif request.delete?
|
|
238
|
+
if instance_variable_defined?(:@body) && @body.present?
|
|
239
|
+
200
|
|
240
|
+
else
|
|
241
|
+
204
|
|
242
|
+
end
|
|
243
|
+
else
|
|
244
|
+
200
|
|
245
|
+
end
|
|
246
|
+
else
|
|
247
|
+
raise ArgumentError, 'Status code must be Integer or Symbol.'
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Set response content-type
|
|
252
|
+
def content_type(val = nil)
|
|
253
|
+
if val
|
|
254
|
+
header(Rack::CONTENT_TYPE, val)
|
|
255
|
+
else
|
|
256
|
+
header[Rack::CONTENT_TYPE]
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Allows you to define the response body as something other than the
|
|
261
|
+
# return value.
|
|
262
|
+
#
|
|
263
|
+
# @example
|
|
264
|
+
# get '/body' do
|
|
265
|
+
# body "Body"
|
|
266
|
+
# "Not the Body"
|
|
267
|
+
# end
|
|
268
|
+
#
|
|
269
|
+
# GET /body # => "Body"
|
|
270
|
+
def body(value = nil)
|
|
271
|
+
if value
|
|
272
|
+
@body = value
|
|
273
|
+
elsif value == false
|
|
274
|
+
@body = ''
|
|
275
|
+
status 204
|
|
276
|
+
else
|
|
277
|
+
instance_variable_defined?(:@body) ? @body : nil
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Allows you to explicitly return no content.
|
|
282
|
+
#
|
|
283
|
+
# @example
|
|
284
|
+
# delete :id do
|
|
285
|
+
# return_no_content
|
|
286
|
+
# "not returned"
|
|
287
|
+
# end
|
|
288
|
+
#
|
|
289
|
+
# DELETE /12 # => 204 No Content, ""
|
|
290
|
+
def return_no_content
|
|
291
|
+
status 204
|
|
292
|
+
body false
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Allows you to send a file to the client via sendfile.
|
|
296
|
+
#
|
|
297
|
+
# @example
|
|
298
|
+
# get '/file' do
|
|
299
|
+
# sendfile FileStreamer.new(...)
|
|
300
|
+
# end
|
|
301
|
+
#
|
|
302
|
+
# GET /file # => "contents of file"
|
|
303
|
+
def sendfile(value = nil)
|
|
304
|
+
if value.is_a?(String)
|
|
305
|
+
file_body = Grape::ServeStream::FileBody.new(value)
|
|
306
|
+
@stream = Grape::ServeStream::StreamResponse.new(file_body)
|
|
307
|
+
elsif !value.is_a?(NilClass)
|
|
308
|
+
raise ArgumentError, 'Argument must be a file path'
|
|
309
|
+
else
|
|
310
|
+
stream
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Allows you to define the response as a streamable object.
|
|
315
|
+
#
|
|
316
|
+
# If Content-Length and Transfer-Encoding are blank (among other conditions),
|
|
317
|
+
# Rack assumes this response can be streamed in chunks.
|
|
318
|
+
#
|
|
319
|
+
# @example
|
|
320
|
+
# get '/stream' do
|
|
321
|
+
# stream FileStreamer.new(...)
|
|
322
|
+
# end
|
|
323
|
+
#
|
|
324
|
+
# GET /stream # => "chunked contents of file"
|
|
325
|
+
#
|
|
326
|
+
# See:
|
|
327
|
+
# * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb
|
|
328
|
+
# * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb
|
|
329
|
+
def stream(value = nil)
|
|
330
|
+
return if value.nil? && @stream.nil?
|
|
331
|
+
|
|
332
|
+
header Rack::CONTENT_LENGTH, nil
|
|
333
|
+
header 'Transfer-Encoding', nil
|
|
334
|
+
header Rack::CACHE_CONTROL, 'no-cache' # Skips ETag generation (reading the response up front)
|
|
335
|
+
if value.is_a?(String)
|
|
336
|
+
file_body = Grape::ServeStream::FileBody.new(value)
|
|
337
|
+
@stream = Grape::ServeStream::StreamResponse.new(file_body)
|
|
338
|
+
elsif value.respond_to?(:each)
|
|
339
|
+
@stream = Grape::ServeStream::StreamResponse.new(value)
|
|
340
|
+
elsif !value.is_a?(NilClass)
|
|
341
|
+
raise ArgumentError, 'Stream object must respond to :each.'
|
|
342
|
+
else
|
|
343
|
+
@stream
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Allows you to make use of Grape Entities by setting
|
|
348
|
+
# the response body to the serializable hash of the
|
|
349
|
+
# entity provided in the `:with` option. This has the
|
|
350
|
+
# added benefit of automatically passing along environment
|
|
351
|
+
# and version information to the serialization, making it
|
|
352
|
+
# very easy to do conditional exposures. See Entity docs
|
|
353
|
+
# for more info.
|
|
354
|
+
#
|
|
355
|
+
# @example
|
|
356
|
+
#
|
|
357
|
+
# get '/users/:id' do
|
|
358
|
+
# present User.find(params[:id]),
|
|
359
|
+
# with: API::Entities::User,
|
|
360
|
+
# admin: current_user.admin?
|
|
361
|
+
# end
|
|
362
|
+
def present(*args)
|
|
363
|
+
options = args.count > 1 ? args.extract_options! : {}
|
|
364
|
+
key, object = if args.count == 2 && args.first.is_a?(Symbol)
|
|
365
|
+
args
|
|
366
|
+
else
|
|
367
|
+
[nil, args.first]
|
|
368
|
+
end
|
|
369
|
+
entity_class = entity_class_for_obj(object, options)
|
|
370
|
+
|
|
371
|
+
root = options.delete(:root)
|
|
372
|
+
|
|
373
|
+
representation = if entity_class
|
|
374
|
+
entity_representation_for(entity_class, object, options)
|
|
375
|
+
else
|
|
376
|
+
object
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
representation = { root => representation } if root
|
|
380
|
+
|
|
381
|
+
if key
|
|
382
|
+
representation = (body || {}).merge(key => representation)
|
|
383
|
+
elsif entity_class.present? && body
|
|
384
|
+
raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
|
|
385
|
+
|
|
386
|
+
representation = body.merge(representation)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
body representation
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Returns route information for the current request.
|
|
393
|
+
#
|
|
394
|
+
# @example
|
|
395
|
+
#
|
|
396
|
+
# desc "Returns the route description."
|
|
397
|
+
# get '/' do
|
|
398
|
+
# route.description
|
|
399
|
+
# end
|
|
400
|
+
def route
|
|
401
|
+
env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Attempt to locate the Entity class for a given object, if not given
|
|
405
|
+
# explicitly. This is done by looking for the presence of Klass::Entity,
|
|
406
|
+
# where Klass is the class of the `object` parameter, or one of its
|
|
407
|
+
# ancestors.
|
|
408
|
+
# @param object [Object] the object to locate the Entity class for
|
|
409
|
+
# @param options [Hash]
|
|
410
|
+
# @option options :with [Class] the explicit entity class to use
|
|
411
|
+
# @return [Class] the located Entity class, or nil if none is found
|
|
412
|
+
def entity_class_for_obj(object, options)
|
|
413
|
+
entity_class = options.delete(:with)
|
|
414
|
+
|
|
415
|
+
if entity_class.nil?
|
|
416
|
+
# entity class not explicitly defined, auto-detect from relation#klass or first object in the collection
|
|
417
|
+
object_class = if object.respond_to?(:klass)
|
|
418
|
+
object.klass
|
|
419
|
+
else
|
|
420
|
+
object.respond_to?(:first) ? object.first.class : object.class
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
object_class.ancestors.each do |potential|
|
|
424
|
+
entity_class ||= (namespace_stackable_with_hash(:representations) || {})[potential]
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
entity_class ||= object_class.const_get(:Entity) if object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
entity_class
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# @return the representation of the given object as done through
|
|
434
|
+
# the given entity_class.
|
|
435
|
+
def entity_representation_for(entity_class, object, options)
|
|
436
|
+
embeds = { env: env }
|
|
437
|
+
embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
|
|
438
|
+
entity_class.represent(object, **embeds, **options)
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def http_version
|
|
442
|
+
env.fetch('HTTP_VERSION') { env[Rack::SERVER_PROTOCOL] }
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def api_format(format)
|
|
446
|
+
env[Grape::Env::API_FORMAT] = format
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def context
|
|
450
|
+
self
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|