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
data/lib/grape/endpoint.rb
CHANGED
|
@@ -1,87 +1,417 @@
|
|
|
1
|
-
|
|
2
|
-
require 'grape'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module Grape
|
|
5
4
|
# An Endpoint is the proxy scope in which all routing
|
|
6
5
|
# blocks are executed. In other words, any methods
|
|
7
6
|
# on the instance level of this class may be called
|
|
8
|
-
# from inside a `get`, `post`, etc.
|
|
7
|
+
# from inside a `get`, `post`, etc.
|
|
9
8
|
class Endpoint
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
extend Forwardable
|
|
10
|
+
include Grape::DSL::Settings
|
|
11
|
+
include Grape::DSL::InsideRoute
|
|
12
|
+
|
|
13
|
+
attr_accessor :block, :source, :options
|
|
14
|
+
attr_reader :env, :request
|
|
15
|
+
|
|
16
|
+
def_delegators :request, :params, :headers, :cookies
|
|
17
|
+
def_delegator :cookies, :response_cookies
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def new(...)
|
|
21
|
+
self == Endpoint ? Class.new(Endpoint).new(...) : super
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def before_each(new_setup = false, &block)
|
|
25
|
+
@before_each ||= []
|
|
26
|
+
if new_setup == false
|
|
27
|
+
return @before_each unless block
|
|
28
|
+
|
|
29
|
+
@before_each << block
|
|
30
|
+
else
|
|
31
|
+
@before_each = [new_setup]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run_before_each(endpoint)
|
|
36
|
+
superclass.run_before_each(endpoint) unless self == Endpoint
|
|
37
|
+
before_each.each { |blk| blk.try(:call, endpoint) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @api private
|
|
41
|
+
#
|
|
42
|
+
# Create an UnboundMethod that is appropriate for executing an endpoint
|
|
43
|
+
# route.
|
|
44
|
+
#
|
|
45
|
+
# The unbound method allows explicit calls to +return+ without raising a
|
|
46
|
+
# +LocalJumpError+. The method will be removed, but a +Proc+ reference to
|
|
47
|
+
# it will be returned. The returned +Proc+ expects a single argument: the
|
|
48
|
+
# instance of +Endpoint+ to bind to the method during the call.
|
|
49
|
+
#
|
|
50
|
+
# @param [String, Symbol] method_name
|
|
51
|
+
# @return [Proc]
|
|
52
|
+
# @raise [NameError] an instance method with the same name already exists
|
|
53
|
+
def generate_api_method(method_name, &block)
|
|
54
|
+
raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") if method_defined?(method_name)
|
|
55
|
+
|
|
56
|
+
define_method(method_name, &block)
|
|
57
|
+
method = instance_method(method_name)
|
|
58
|
+
remove_method(method_name)
|
|
59
|
+
|
|
60
|
+
proc do |endpoint_instance|
|
|
61
|
+
ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
|
|
62
|
+
method.bind_call(endpoint_instance)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
14
65
|
end
|
|
15
|
-
c
|
|
16
66
|
end
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
67
|
+
|
|
68
|
+
# Create a new endpoint.
|
|
69
|
+
# @param new_settings [InheritableSetting] settings to determine the params,
|
|
70
|
+
# validations, and other properties from.
|
|
71
|
+
# @param options [Hash] attributes of this endpoint
|
|
72
|
+
# @option options path [String or Array] the path to this endpoint, within
|
|
73
|
+
# the current scope.
|
|
74
|
+
# @option options method [String or Array] which HTTP method(s) can be used
|
|
75
|
+
# to reach this endpoint.
|
|
76
|
+
# @option options route_options [Hash]
|
|
77
|
+
# @note This happens at the time of API definition, so in this context the
|
|
78
|
+
# endpoint does not know if it will be mounted under a different endpoint.
|
|
79
|
+
# @yield a block defining what your API should do when this endpoint is hit
|
|
80
|
+
def initialize(new_settings, options = {}, &block)
|
|
81
|
+
require_option(options, :path)
|
|
82
|
+
require_option(options, :method)
|
|
83
|
+
|
|
84
|
+
self.inheritable_setting = new_settings.point_in_time_copy
|
|
85
|
+
|
|
86
|
+
# now +namespace_stackable(:declared_params)+ contains all params defined for
|
|
87
|
+
# this endpoint and its parents, but later it will be cleaned up,
|
|
88
|
+
# see +reset_validations!+ in lib/grape/dsl/validations.rb
|
|
89
|
+
route_setting(:declared_params, namespace_stackable(:declared_params).flatten)
|
|
90
|
+
route_setting(:saved_validations, namespace_stackable(:validations))
|
|
91
|
+
|
|
92
|
+
namespace_stackable(:representations, []) unless namespace_stackable(:representations)
|
|
93
|
+
namespace_inheritable(:default_error_status, 500) unless namespace_inheritable(:default_error_status)
|
|
94
|
+
|
|
95
|
+
@options = options
|
|
96
|
+
|
|
97
|
+
@options[:path] = Array(options[:path])
|
|
98
|
+
@options[:path] << '/' if options[:path].empty?
|
|
99
|
+
|
|
100
|
+
@options[:method] = Array(options[:method])
|
|
101
|
+
@options[:route_options] ||= {}
|
|
102
|
+
|
|
103
|
+
@lazy_initialize_lock = Mutex.new
|
|
104
|
+
@lazy_initialized = nil
|
|
105
|
+
@block = nil
|
|
106
|
+
|
|
107
|
+
@status = nil
|
|
108
|
+
@stream = nil
|
|
109
|
+
@body = nil
|
|
110
|
+
@proc = nil
|
|
111
|
+
|
|
112
|
+
return unless block
|
|
113
|
+
|
|
114
|
+
@source = block
|
|
115
|
+
@block = self.class.generate_api_method(method_name, &block)
|
|
20
116
|
end
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
117
|
+
|
|
118
|
+
# Update our settings from a given set of stackable parameters. Used when
|
|
119
|
+
# the endpoint's API is mounted under another one.
|
|
120
|
+
def inherit_settings(namespace_stackable)
|
|
121
|
+
parent_validations = namespace_stackable[:validations]
|
|
122
|
+
inheritable_setting.route[:saved_validations].concat(parent_validations) if parent_validations.any?
|
|
123
|
+
parent_declared_params = namespace_stackable[:declared_params]
|
|
124
|
+
inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params.any?
|
|
125
|
+
|
|
126
|
+
endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
|
|
24
127
|
end
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# The parameters passed into the request as
|
|
29
|
-
# well as parsed from URL segments.
|
|
30
|
-
def params
|
|
31
|
-
@params ||= request.params.merge(env['rack.routing_args'] || {}).inject({}) do |h,(k,v)|
|
|
32
|
-
h[k.to_s] = v
|
|
33
|
-
h[k.to_sym] = v
|
|
34
|
-
h
|
|
35
|
-
end
|
|
128
|
+
|
|
129
|
+
def require_option(options, key)
|
|
130
|
+
raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
|
|
36
131
|
end
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
132
|
+
|
|
133
|
+
def method_name
|
|
134
|
+
[options[:method],
|
|
135
|
+
Namespace.joined_space(namespace_stackable(:namespace)),
|
|
136
|
+
(namespace_stackable(:mount_path) || []).join('/'),
|
|
137
|
+
options[:path].join('/')]
|
|
138
|
+
.join(' ')
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def routes
|
|
142
|
+
@routes ||= endpoints&.collect(&:routes)&.flatten || to_routes
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def reset_routes!
|
|
146
|
+
endpoints&.each(&:reset_routes!)
|
|
147
|
+
@namespace = nil
|
|
148
|
+
@routes = nil
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def mount_in(router)
|
|
152
|
+
return endpoints.each { |e| e.mount_in(router) } if endpoints
|
|
153
|
+
|
|
154
|
+
reset_routes!
|
|
155
|
+
routes.each do |route|
|
|
156
|
+
router.append(route.apply(self))
|
|
157
|
+
next unless !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
|
|
158
|
+
|
|
159
|
+
route.dup.then do |head_route|
|
|
160
|
+
head_route.convert_to_head_request!
|
|
161
|
+
router.append(head_route.apply(self))
|
|
63
162
|
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Set an individual header or retrieve
|
|
68
|
-
# all headers that have been set.
|
|
69
|
-
def header(key = nil, val = nil)
|
|
70
|
-
if key
|
|
71
|
-
val ? @header[key.to_s] = val : @header.delete(key.to_s)
|
|
72
|
-
else
|
|
73
|
-
@header
|
|
74
163
|
end
|
|
75
164
|
end
|
|
76
|
-
|
|
165
|
+
|
|
166
|
+
def to_routes
|
|
167
|
+
default_route_options = prepare_default_route_attributes
|
|
168
|
+
|
|
169
|
+
map_routes do |method, raw_path|
|
|
170
|
+
prepared_path = Path.new(raw_path, namespace, prepare_default_path_settings)
|
|
171
|
+
params = options[:route_options].present? ? options[:route_options].merge(default_route_options) : default_route_options
|
|
172
|
+
route = Grape::Router::Route.new(method, prepared_path.origin, prepared_path.suffix, params)
|
|
173
|
+
route.apply(self)
|
|
174
|
+
end.flatten
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def prepare_routes_requirements
|
|
178
|
+
{}.merge!(*namespace_stackable(:namespace).map(&:requirements)).tap do |requirements|
|
|
179
|
+
endpoint_requirements = options.dig(:route_options, :requirements)
|
|
180
|
+
requirements.merge!(endpoint_requirements) if endpoint_requirements
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def prepare_default_route_attributes
|
|
185
|
+
{
|
|
186
|
+
namespace: namespace,
|
|
187
|
+
version: prepare_version,
|
|
188
|
+
requirements: prepare_routes_requirements,
|
|
189
|
+
prefix: namespace_inheritable(:root_prefix),
|
|
190
|
+
anchor: options[:route_options].fetch(:anchor, true),
|
|
191
|
+
settings: inheritable_setting.route.except(:declared_params, :saved_validations),
|
|
192
|
+
forward_match: options[:forward_match]
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def prepare_version
|
|
197
|
+
version = namespace_inheritable(:version)
|
|
198
|
+
return if version.blank?
|
|
199
|
+
|
|
200
|
+
version.length == 1 ? version.first : version
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def map_routes
|
|
204
|
+
options[:method].map { |method| options[:path].map { |path| yield method, path } }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def prepare_default_path_settings
|
|
208
|
+
namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
|
|
209
|
+
namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
|
|
210
|
+
namespace_stackable_hash.merge!(namespace_inheritable_hash)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def namespace
|
|
214
|
+
@namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace))
|
|
215
|
+
end
|
|
216
|
+
|
|
77
217
|
def call(env)
|
|
218
|
+
lazy_initialize!
|
|
219
|
+
dup.call!(env)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def call!(env)
|
|
223
|
+
env[Grape::Env::API_ENDPOINT] = self
|
|
78
224
|
@env = env
|
|
79
|
-
@
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
225
|
+
@app.call(env)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Return the collection of endpoints within this endpoint.
|
|
229
|
+
# This is the case when an Grape::API mounts another Grape::API.
|
|
230
|
+
def endpoints
|
|
231
|
+
@endpoints ||= options[:app].try(:endpoints)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def equals?(endpoint)
|
|
235
|
+
(options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# The purpose of this override is solely for stripping internals when an error occurs while calling
|
|
239
|
+
# an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
|
|
240
|
+
# Otherwise, it calls super.
|
|
241
|
+
def inspect
|
|
242
|
+
return super unless env
|
|
243
|
+
|
|
244
|
+
"#{self.class} in '#{route.origin}' endpoint"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
protected
|
|
248
|
+
|
|
249
|
+
def run
|
|
250
|
+
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
|
|
251
|
+
@request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
|
|
252
|
+
begin
|
|
253
|
+
self.class.run_before_each(self)
|
|
254
|
+
run_filters befores, :before
|
|
255
|
+
|
|
256
|
+
if env.key?(Grape::Env::GRAPE_ALLOWED_METHODS)
|
|
257
|
+
header['Allow'] = env[Grape::Env::GRAPE_ALLOWED_METHODS].join(', ')
|
|
258
|
+
raise Grape::Exceptions::MethodNotAllowed.new(header) unless options?
|
|
259
|
+
|
|
260
|
+
header 'Allow', header['Allow']
|
|
261
|
+
response_object = ''
|
|
262
|
+
status 204
|
|
263
|
+
else
|
|
264
|
+
run_filters before_validations, :before_validation
|
|
265
|
+
run_validators validations, request
|
|
266
|
+
run_filters after_validations, :after_validation
|
|
267
|
+
response_object = execute
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
run_filters afters, :after
|
|
271
|
+
build_response_cookies
|
|
272
|
+
|
|
273
|
+
# status verifies body presence when DELETE
|
|
274
|
+
@body ||= response_object
|
|
275
|
+
|
|
276
|
+
# The body commonly is an Array of Strings, the application instance itself, or a Stream-like object
|
|
277
|
+
response_object = stream || [body]
|
|
278
|
+
|
|
279
|
+
[status, header, response_object]
|
|
280
|
+
ensure
|
|
281
|
+
run_filters finallies, :finally
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def execute
|
|
287
|
+
@block&.call(self)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def helpers
|
|
291
|
+
lazy_initialize! && @helpers
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def lazy_initialize!
|
|
295
|
+
return true if @lazy_initialized
|
|
296
|
+
|
|
297
|
+
@lazy_initialize_lock.synchronize do
|
|
298
|
+
return true if @lazy_initialized
|
|
299
|
+
|
|
300
|
+
@helpers = build_helpers&.tap { |mod| self.class.include mod }
|
|
301
|
+
@app = options[:app] || build_stack(@helpers)
|
|
302
|
+
|
|
303
|
+
@lazy_initialized = true
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def run_validators(validators, request)
|
|
308
|
+
validation_errors = []
|
|
309
|
+
|
|
310
|
+
ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
|
|
311
|
+
validators.each do |validator|
|
|
312
|
+
validator.validate(request)
|
|
313
|
+
rescue Grape::Exceptions::Validation => e
|
|
314
|
+
validation_errors << e
|
|
315
|
+
break if validator.fail_fast?
|
|
316
|
+
rescue Grape::Exceptions::ValidationArrayErrors => e
|
|
317
|
+
validation_errors.concat e.errors
|
|
318
|
+
break if validator.fail_fast?
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
validation_errors.any? && raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header))
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def run_filters(filters, type = :other)
|
|
326
|
+
ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
|
|
327
|
+
filters&.each { |filter| instance_eval(&filter) }
|
|
328
|
+
end
|
|
329
|
+
post_extension = DSL::InsideRoute.post_filter_methods(type)
|
|
330
|
+
extend post_extension if post_extension
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
%i[befores before_validations after_validations afters finallies].each do |method|
|
|
334
|
+
define_method method do
|
|
335
|
+
namespace_stackable(method)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def validations
|
|
340
|
+
return enum_for(:validations) unless block_given?
|
|
341
|
+
|
|
342
|
+
route_setting(:saved_validations)&.each do |saved_validation|
|
|
343
|
+
yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def options?
|
|
348
|
+
options[:options_route_enabled] &&
|
|
349
|
+
env[Rack::REQUEST_METHOD] == Rack::OPTIONS
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
private
|
|
353
|
+
|
|
354
|
+
def build_stack(helpers)
|
|
355
|
+
stack = Grape::Middleware::Stack.new
|
|
356
|
+
|
|
357
|
+
content_types = namespace_stackable_with_hash(:content_types)
|
|
358
|
+
format = namespace_inheritable(:format)
|
|
359
|
+
|
|
360
|
+
stack.use Rack::Head
|
|
361
|
+
stack.use Rack::Lint if lint?
|
|
362
|
+
stack.use Class.new(Grape::Middleware::Error),
|
|
363
|
+
helpers: helpers,
|
|
364
|
+
format: format,
|
|
365
|
+
content_types: content_types,
|
|
366
|
+
default_status: namespace_inheritable(:default_error_status),
|
|
367
|
+
rescue_all: namespace_inheritable(:rescue_all),
|
|
368
|
+
rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
|
|
369
|
+
default_error_formatter: namespace_inheritable(:default_error_formatter),
|
|
370
|
+
error_formatters: namespace_stackable_with_hash(:error_formatters),
|
|
371
|
+
rescue_options: namespace_stackable_with_hash(:rescue_options),
|
|
372
|
+
rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
|
|
373
|
+
base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
|
|
374
|
+
all_rescue_handler: namespace_inheritable(:all_rescue_handler),
|
|
375
|
+
grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
|
|
376
|
+
|
|
377
|
+
stack.concat namespace_stackable(:middleware)
|
|
378
|
+
|
|
379
|
+
if namespace_inheritable(:version).present?
|
|
380
|
+
stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
|
|
381
|
+
versions: namespace_inheritable(:version).flatten,
|
|
382
|
+
version_options: namespace_inheritable(:version_options),
|
|
383
|
+
prefix: namespace_inheritable(:root_prefix),
|
|
384
|
+
mount_path: namespace_stackable(:mount_path).first
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
stack.use Grape::Middleware::Formatter,
|
|
388
|
+
format: format,
|
|
389
|
+
default_format: namespace_inheritable(:default_format) || :txt,
|
|
390
|
+
content_types: content_types,
|
|
391
|
+
formatters: namespace_stackable_with_hash(:formatters),
|
|
392
|
+
parsers: namespace_stackable_with_hash(:parsers)
|
|
393
|
+
|
|
394
|
+
builder = stack.build
|
|
395
|
+
builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
|
|
396
|
+
builder.to_app
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def build_helpers
|
|
400
|
+
helpers = namespace_stackable(:helpers)
|
|
401
|
+
return if helpers.empty?
|
|
402
|
+
|
|
403
|
+
Module.new { helpers.each { |mod_to_include| include mod_to_include } }
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def build_response_cookies
|
|
407
|
+
response_cookies do |name, value|
|
|
408
|
+
cookie_value = value.is_a?(Hash) ? value : { value: value }
|
|
409
|
+
Rack::Utils.set_cookie_header! header, name, cookie_value
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def lint?
|
|
414
|
+
namespace_inheritable(:lint) || Grape.config.lint
|
|
85
415
|
end
|
|
86
416
|
end
|
|
87
417
|
end
|
data/lib/grape/env.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Env
|
|
5
|
+
API_VERSION = 'api.version'
|
|
6
|
+
API_ENDPOINT = 'api.endpoint'
|
|
7
|
+
API_REQUEST_INPUT = 'api.request.input'
|
|
8
|
+
API_REQUEST_BODY = 'api.request.body'
|
|
9
|
+
API_TYPE = 'api.type'
|
|
10
|
+
API_SUBTYPE = 'api.subtype'
|
|
11
|
+
API_VENDOR = 'api.vendor'
|
|
12
|
+
API_FORMAT = 'api.format'
|
|
13
|
+
|
|
14
|
+
GRAPE_REQUEST = 'grape.request'
|
|
15
|
+
GRAPE_REQUEST_HEADERS = 'grape.request.headers'
|
|
16
|
+
GRAPE_REQUEST_PARAMS = 'grape.request.params'
|
|
17
|
+
GRAPE_ROUTING_ARGS = 'grape.routing_args'
|
|
18
|
+
GRAPE_ALLOWED_METHODS = 'grape.allowed_methods'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ErrorFormatter
|
|
5
|
+
class Base
|
|
6
|
+
class << self
|
|
7
|
+
def call(message, backtrace, options = {}, env = nil, original_exception = nil)
|
|
8
|
+
merge_backtrace = backtrace.present? && options.dig(:rescue_options, :backtrace)
|
|
9
|
+
merge_original_exception = original_exception && options.dig(:rescue_options, :original_exception)
|
|
10
|
+
|
|
11
|
+
wrapped_message = wrap_message(present(message, env))
|
|
12
|
+
if wrapped_message.is_a?(Hash)
|
|
13
|
+
wrapped_message[:backtrace] = backtrace if merge_backtrace
|
|
14
|
+
wrapped_message[:original_exception] = original_exception.inspect if merge_original_exception
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
format_structured_message(wrapped_message)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def present(message, env)
|
|
21
|
+
present_options = {}
|
|
22
|
+
presented_message = message
|
|
23
|
+
if presented_message.is_a?(Hash)
|
|
24
|
+
presented_message = presented_message.dup
|
|
25
|
+
present_options[:with] = presented_message.delete(:with)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
presenter = env[Grape::Env::API_ENDPOINT].entity_class_for_obj(presented_message, present_options)
|
|
29
|
+
|
|
30
|
+
unless presenter || env[Grape::Env::GRAPE_ROUTING_ARGS].nil?
|
|
31
|
+
# env['api.endpoint'].route does not work when the error occurs within a middleware
|
|
32
|
+
# the Endpoint does not have a valid env at this moment
|
|
33
|
+
http_codes = env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || []
|
|
34
|
+
|
|
35
|
+
found_code = http_codes.find do |http_code|
|
|
36
|
+
(http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent)
|
|
37
|
+
end if env[Grape::Env::API_ENDPOINT].request
|
|
38
|
+
|
|
39
|
+
presenter = found_code[2] if found_code
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if presenter
|
|
43
|
+
embeds = { env: env }
|
|
44
|
+
embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
|
|
45
|
+
presented_message = presenter.represent(presented_message, embeds).serializable_hash
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
presented_message
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def wrap_message(message)
|
|
52
|
+
return message if message.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
{ message: message }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def format_structured_message(_structured_message)
|
|
58
|
+
raise NotImplementedError
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def inherited(klass)
|
|
62
|
+
super
|
|
63
|
+
ErrorFormatter.register(klass)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ErrorFormatter
|
|
5
|
+
class Json < Base
|
|
6
|
+
class << self
|
|
7
|
+
def format_structured_message(structured_message)
|
|
8
|
+
::Grape::Json.dump(structured_message)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def wrap_message(message)
|
|
14
|
+
return message if message.is_a?(Hash)
|
|
15
|
+
return message.as_json if message.is_a?(Exceptions::ValidationErrors)
|
|
16
|
+
|
|
17
|
+
{ error: ensure_utf8(message) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ensure_utf8(message)
|
|
21
|
+
return message unless message.respond_to? :encode
|
|
22
|
+
|
|
23
|
+
message.encode('UTF-8', invalid: :replace, undef: :replace)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ErrorFormatter
|
|
5
|
+
class Txt < Base
|
|
6
|
+
def self.format_structured_message(structured_message)
|
|
7
|
+
message = structured_message[:message] || Grape::Json.dump(structured_message)
|
|
8
|
+
Array.wrap(message).tap do |final_message|
|
|
9
|
+
if structured_message.key?(:backtrace)
|
|
10
|
+
final_message << 'backtrace:'
|
|
11
|
+
final_message.concat(structured_message[:backtrace])
|
|
12
|
+
end
|
|
13
|
+
if structured_message.key?(:original_exception)
|
|
14
|
+
final_message << 'original exception:'
|
|
15
|
+
final_message << structured_message[:original_exception]
|
|
16
|
+
end
|
|
17
|
+
end.join("\r\n ")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ErrorFormatter
|
|
5
|
+
class Xml < Base
|
|
6
|
+
def self.format_structured_message(structured_message)
|
|
7
|
+
structured_message.respond_to?(:to_xml) ? structured_message.to_xml(root: :error) : structured_message.to_s
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ErrorFormatter
|
|
5
|
+
extend Grape::Util::Registry
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def formatter_for(format, error_formatters = nil, default_error_formatter = nil)
|
|
10
|
+
return error_formatters[format] if error_formatters&.key?(format)
|
|
11
|
+
|
|
12
|
+
registry[format] || default_error_formatter || Grape::ErrorFormatter::Txt
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|