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,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
class Router
|
|
5
|
+
class Route < BaseRoute
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }
|
|
9
|
+
NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }
|
|
10
|
+
|
|
11
|
+
attr_reader :app, :request_method
|
|
12
|
+
|
|
13
|
+
def_delegators :pattern, :path, :origin
|
|
14
|
+
|
|
15
|
+
def initialize(method, origin, path, options)
|
|
16
|
+
@request_method = upcase_method(method)
|
|
17
|
+
@pattern = Grape::Router::Pattern.new(origin, path, options)
|
|
18
|
+
@match_function = options[:forward_match] ? FORWARD_MATCH_METHOD : NON_FORWARD_MATCH_METHOD
|
|
19
|
+
super(options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def convert_to_head_request!
|
|
23
|
+
@request_method = Rack::HEAD
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def exec(env)
|
|
27
|
+
@app.call(env)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def apply(app)
|
|
31
|
+
@app = app
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def match?(input)
|
|
36
|
+
return false if input.blank?
|
|
37
|
+
|
|
38
|
+
@match_function.call(input, pattern)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def params(input = nil)
|
|
42
|
+
return params_without_input if input.blank?
|
|
43
|
+
|
|
44
|
+
parsed = pattern.params(input)
|
|
45
|
+
return {} unless parsed
|
|
46
|
+
|
|
47
|
+
parsed.compact.symbolize_keys
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def params_without_input
|
|
53
|
+
@params_without_input ||= pattern.captures_default.merge(attributes.params)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def upcase_method(method)
|
|
57
|
+
method_s = method.to_s
|
|
58
|
+
Grape::HTTP_SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/grape/router.rb
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
class Router
|
|
5
|
+
attr_reader :map, :compiled
|
|
6
|
+
|
|
7
|
+
# Taken from Rails
|
|
8
|
+
# normalize_path("/foo") # => "/foo"
|
|
9
|
+
# normalize_path("/foo/") # => "/foo"
|
|
10
|
+
# normalize_path("foo") # => "/foo"
|
|
11
|
+
# normalize_path("") # => "/"
|
|
12
|
+
# normalize_path("/%ab") # => "/%AB"
|
|
13
|
+
# https://github.com/rails/rails/blob/00cc4ff0259c0185fe08baadaa40e63ea2534f6e/actionpack/lib/action_dispatch/journey/router/utils.rb#L19
|
|
14
|
+
def self.normalize_path(path)
|
|
15
|
+
return +'/' unless path
|
|
16
|
+
|
|
17
|
+
# Fast path for the overwhelming majority of paths that don't need to be normalized
|
|
18
|
+
return path.dup if path == '/' || (path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//})))
|
|
19
|
+
|
|
20
|
+
# Slow path
|
|
21
|
+
encoding = path.encoding
|
|
22
|
+
path = +"/#{path}"
|
|
23
|
+
path.squeeze!('/')
|
|
24
|
+
|
|
25
|
+
unless path == '/'
|
|
26
|
+
path.delete_suffix!('/')
|
|
27
|
+
path.gsub!(/(%[a-f0-9]{2})/) { ::Regexp.last_match(1).upcase }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
path.force_encoding(encoding)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize
|
|
34
|
+
@neutral_map = []
|
|
35
|
+
@neutral_regexes = []
|
|
36
|
+
@map = Hash.new { |hash, key| hash[key] = [] }
|
|
37
|
+
@optimized_map = Hash.new { |hash, key| hash[key] = // }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def compile!
|
|
41
|
+
return if compiled
|
|
42
|
+
|
|
43
|
+
@union = Regexp.union(@neutral_regexes)
|
|
44
|
+
@neutral_regexes = nil
|
|
45
|
+
(Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
|
|
46
|
+
next unless map.key?(method)
|
|
47
|
+
|
|
48
|
+
routes = map[method]
|
|
49
|
+
optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
|
|
50
|
+
@optimized_map[method] = Regexp.union(optimized_map)
|
|
51
|
+
end
|
|
52
|
+
@compiled = true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def append(route)
|
|
56
|
+
map[route.request_method] << route
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def associate_routes(pattern, options)
|
|
60
|
+
Grape::Router::GreedyRoute.new(pattern, options).then do |greedy_route|
|
|
61
|
+
@neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
|
|
62
|
+
@neutral_map << greedy_route
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def call(env)
|
|
67
|
+
with_optimization do
|
|
68
|
+
response, route = identity(env)
|
|
69
|
+
response || rotation(env, route)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def recognize_path(input)
|
|
74
|
+
any = with_optimization { greedy_match?(input) }
|
|
75
|
+
return if any == default_response
|
|
76
|
+
|
|
77
|
+
any.endpoint
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def identity(env)
|
|
83
|
+
route = nil
|
|
84
|
+
response = transaction(env) do |input, method|
|
|
85
|
+
route = match?(input, method)
|
|
86
|
+
process_route(route, env) if route
|
|
87
|
+
end
|
|
88
|
+
[response, route]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def rotation(env, exact_route = nil)
|
|
92
|
+
response = nil
|
|
93
|
+
input, method = *extract_input_and_method(env)
|
|
94
|
+
map[method].each do |route|
|
|
95
|
+
next if exact_route == route
|
|
96
|
+
next unless route.match?(input)
|
|
97
|
+
|
|
98
|
+
response = process_route(route, env)
|
|
99
|
+
break unless cascade?(response)
|
|
100
|
+
end
|
|
101
|
+
response
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def transaction(env)
|
|
105
|
+
input, method = *extract_input_and_method(env)
|
|
106
|
+
|
|
107
|
+
# using a Proc is important since `return` will exit the enclosing function
|
|
108
|
+
cascade_or_return_response = proc do |response|
|
|
109
|
+
if response
|
|
110
|
+
cascade?(response).tap do |cascade|
|
|
111
|
+
return response unless cascade
|
|
112
|
+
|
|
113
|
+
# we need to close the body if possible before dismissing
|
|
114
|
+
response[2].try(:close)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
last_response_cascade = cascade_or_return_response.call(yield(input, method))
|
|
120
|
+
last_neighbor_route = greedy_match?(input)
|
|
121
|
+
|
|
122
|
+
# If last_neighbor_route exists and request method is OPTIONS,
|
|
123
|
+
# return response by using #call_with_allow_headers.
|
|
124
|
+
return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Rack::OPTIONS && !last_response_cascade
|
|
125
|
+
|
|
126
|
+
route = match?(input, '*')
|
|
127
|
+
|
|
128
|
+
return last_neighbor_route.options[:endpoint].call(env) if last_neighbor_route && last_response_cascade && route
|
|
129
|
+
|
|
130
|
+
last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
|
|
131
|
+
|
|
132
|
+
return call_with_allow_headers(env, last_neighbor_route) if !last_response_cascade && last_neighbor_route
|
|
133
|
+
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def process_route(route, env)
|
|
138
|
+
prepare_env_from_route(env, route)
|
|
139
|
+
route.exec(env)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def make_routing_args(default_args, route, input)
|
|
143
|
+
args = default_args || { route_info: route }
|
|
144
|
+
args.merge(route.params(input))
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def extract_input_and_method(env)
|
|
148
|
+
input = string_for(env[Rack::PATH_INFO])
|
|
149
|
+
method = env[Rack::REQUEST_METHOD]
|
|
150
|
+
[input, method]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def with_optimization
|
|
154
|
+
compile! unless compiled
|
|
155
|
+
yield || default_response
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def default_response
|
|
159
|
+
headers = Grape::Util::Header.new.merge('X-Cascade' => 'pass')
|
|
160
|
+
[404, headers, ['404 Not Found']]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def match?(input, method)
|
|
164
|
+
@optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def greedy_match?(input)
|
|
168
|
+
@union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def call_with_allow_headers(env, route)
|
|
172
|
+
prepare_env_from_route(env, route)
|
|
173
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.options[:allow_header]
|
|
174
|
+
route.options[:endpoint].call(env)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def prepare_env_from_route(env, route)
|
|
178
|
+
input, = *extract_input_and_method(env)
|
|
179
|
+
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def cascade?(response)
|
|
183
|
+
response && response[1]['X-Cascade'] == 'pass'
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def string_for(input)
|
|
187
|
+
self.class.normalize_path(input)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ServeStream
|
|
5
|
+
CHUNK_SIZE = 16_384
|
|
6
|
+
|
|
7
|
+
# Class helps send file through API
|
|
8
|
+
class FileBody
|
|
9
|
+
attr_reader :path
|
|
10
|
+
|
|
11
|
+
# @param path [String]
|
|
12
|
+
def initialize(path)
|
|
13
|
+
@path = path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Need for Rack::Sendfile middleware
|
|
17
|
+
#
|
|
18
|
+
# @return [String]
|
|
19
|
+
def to_path
|
|
20
|
+
path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def each
|
|
24
|
+
File.open(path, 'rb') do |file|
|
|
25
|
+
while (chunk = file.read(CHUNK_SIZE))
|
|
26
|
+
yield chunk
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ==(other)
|
|
32
|
+
path == other.path
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ServeStream
|
|
5
|
+
# Response should respond to to_path method
|
|
6
|
+
# for using Rack::SendFile middleware
|
|
7
|
+
class SendfileResponse < Rack::Response
|
|
8
|
+
def respond_to?(method_name, include_all = false)
|
|
9
|
+
if method_name == :to_path
|
|
10
|
+
@body.respond_to?(:to_path, include_all)
|
|
11
|
+
else
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_path
|
|
17
|
+
@body.to_path
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ServeStream
|
|
5
|
+
# A simple class used to identify responses which represent streams (or files) and do not
|
|
6
|
+
# need to be formatted or pre-read by Rack::Response
|
|
7
|
+
class StreamResponse
|
|
8
|
+
attr_reader :stream
|
|
9
|
+
|
|
10
|
+
# @param stream [Object]
|
|
11
|
+
def initialize(stream)
|
|
12
|
+
@stream = stream
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Equality provided mostly for tests.
|
|
16
|
+
#
|
|
17
|
+
# @return [Boolean]
|
|
18
|
+
def ==(other)
|
|
19
|
+
stream == other.stream
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
# Base for classes which need to operate with own values kept
|
|
6
|
+
# in the hash and inherited values kept in a Hash-like object.
|
|
7
|
+
class BaseInheritable
|
|
8
|
+
attr_accessor :inherited_values, :new_values
|
|
9
|
+
|
|
10
|
+
# @param inherited_values [Object] An object implementing an interface
|
|
11
|
+
# of the Hash class.
|
|
12
|
+
def initialize(inherited_values = nil)
|
|
13
|
+
@inherited_values = inherited_values || {}
|
|
14
|
+
@new_values = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def delete(key)
|
|
18
|
+
new_values.delete key
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize_copy(other)
|
|
22
|
+
super
|
|
23
|
+
self.inherited_values = other.inherited_values
|
|
24
|
+
self.new_values = other.new_values.dup
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def keys
|
|
28
|
+
if new_values.any?
|
|
29
|
+
inherited_values.keys.tap do |combined|
|
|
30
|
+
combined.concat(new_values.keys)
|
|
31
|
+
combined.uniq!
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
inherited_values.keys
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def key?(name)
|
|
39
|
+
inherited_values.key?(name) || new_values.key?(name)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
class Cache
|
|
6
|
+
include Singleton
|
|
7
|
+
|
|
8
|
+
attr_reader :cache
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
extend Forwardable
|
|
12
|
+
def_delegators :cache, :[]
|
|
13
|
+
def_delegators :instance, :cache
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
if Gem::Version.new(Rack.release) >= Gem::Version.new('3')
|
|
6
|
+
require 'rack/headers'
|
|
7
|
+
Header = Rack::Headers
|
|
8
|
+
else
|
|
9
|
+
require 'rack/utils'
|
|
10
|
+
Header = Rack::Utils::HeaderHash
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
# A branchable, inheritable settings object which can store both stackable
|
|
6
|
+
# and inheritable values (see InheritableValues and StackableValues).
|
|
7
|
+
class InheritableSetting
|
|
8
|
+
attr_accessor :route, :api_class, :namespace, :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable, :parent, :point_in_time_copies
|
|
9
|
+
|
|
10
|
+
# Retrieve global settings.
|
|
11
|
+
def self.global
|
|
12
|
+
@global ||= {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Clear all global settings.
|
|
16
|
+
# @api private
|
|
17
|
+
# @note only for testing
|
|
18
|
+
def self.reset_global!
|
|
19
|
+
@global = {}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Instantiate a new settings instance, with blank values. The fresh
|
|
23
|
+
# instance can then be set to inherit from an existing instance (see
|
|
24
|
+
# #inherit_from).
|
|
25
|
+
def initialize
|
|
26
|
+
self.route = {}
|
|
27
|
+
self.api_class = {}
|
|
28
|
+
self.namespace = InheritableValues.new # only inheritable from a parent when
|
|
29
|
+
# used with a mount, or should every API::Class be a separate namespace by default?
|
|
30
|
+
self.namespace_inheritable = InheritableValues.new
|
|
31
|
+
self.namespace_stackable = StackableValues.new
|
|
32
|
+
self.namespace_reverse_stackable = ReverseStackableValues.new
|
|
33
|
+
|
|
34
|
+
self.point_in_time_copies = []
|
|
35
|
+
|
|
36
|
+
self.parent = nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Return the class-level global properties.
|
|
40
|
+
def global
|
|
41
|
+
self.class.global
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Set our inherited values to the given parent's current values. Also,
|
|
45
|
+
# update the inherited values on any settings instances which were forked
|
|
46
|
+
# from us.
|
|
47
|
+
# @param parent [InheritableSetting]
|
|
48
|
+
def inherit_from(parent)
|
|
49
|
+
return if parent.nil?
|
|
50
|
+
|
|
51
|
+
self.parent = parent
|
|
52
|
+
|
|
53
|
+
namespace_inheritable.inherited_values = parent.namespace_inheritable
|
|
54
|
+
namespace_stackable.inherited_values = parent.namespace_stackable
|
|
55
|
+
namespace_reverse_stackable.inherited_values = parent.namespace_reverse_stackable
|
|
56
|
+
self.route = parent.route.merge(route)
|
|
57
|
+
|
|
58
|
+
point_in_time_copies.map { |cloned_one| cloned_one.inherit_from parent }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Create a point-in-time copy of this settings instance, with clones of
|
|
62
|
+
# all our values. Note that, should this instance's parent be set or
|
|
63
|
+
# changed via #inherit_from, it will copy that inheritence to any copies
|
|
64
|
+
# which were made.
|
|
65
|
+
def point_in_time_copy
|
|
66
|
+
self.class.new.tap do |new_setting|
|
|
67
|
+
point_in_time_copies << new_setting
|
|
68
|
+
new_setting.point_in_time_copies = []
|
|
69
|
+
|
|
70
|
+
new_setting.namespace = namespace.clone
|
|
71
|
+
new_setting.namespace_inheritable = namespace_inheritable.clone
|
|
72
|
+
new_setting.namespace_stackable = namespace_stackable.clone
|
|
73
|
+
new_setting.namespace_reverse_stackable = namespace_reverse_stackable.clone
|
|
74
|
+
new_setting.route = route.clone
|
|
75
|
+
new_setting.api_class = api_class
|
|
76
|
+
|
|
77
|
+
new_setting.inherit_from(parent)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Resets the instance store of per-route settings.
|
|
82
|
+
# @api private
|
|
83
|
+
def route_end
|
|
84
|
+
@route = {}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Return a serializable hash of our values.
|
|
88
|
+
def to_hash
|
|
89
|
+
{
|
|
90
|
+
global: global.clone,
|
|
91
|
+
route: route.clone,
|
|
92
|
+
namespace: namespace.to_hash,
|
|
93
|
+
namespace_inheritable: namespace_inheritable.to_hash,
|
|
94
|
+
namespace_stackable: namespace_stackable.to_hash,
|
|
95
|
+
namespace_reverse_stackable: namespace_reverse_stackable.to_hash
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
class InheritableValues < BaseInheritable
|
|
6
|
+
def [](name)
|
|
7
|
+
values[name]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def []=(name, value)
|
|
11
|
+
new_values[name] = value
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def merge(new_hash)
|
|
15
|
+
values.merge!(new_hash)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_hash
|
|
19
|
+
values
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
protected
|
|
23
|
+
|
|
24
|
+
def values
|
|
25
|
+
@inherited_values.merge(@new_values)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
class Block
|
|
7
|
+
def initialize(&new_block)
|
|
8
|
+
@block = new_block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def evaluate_from(configuration)
|
|
12
|
+
@block.call(configuration)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def evaluate
|
|
16
|
+
@block.call({})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def lazy?
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
evaluate.to_s
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
class Value
|
|
7
|
+
attr_reader :access_keys
|
|
8
|
+
|
|
9
|
+
def initialize(value, access_keys = [])
|
|
10
|
+
@value = value
|
|
11
|
+
@access_keys = access_keys
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def evaluate_from(configuration)
|
|
15
|
+
matching_lazy_value = configuration.fetch(@access_keys)
|
|
16
|
+
matching_lazy_value.evaluate
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def evaluate
|
|
20
|
+
@value
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def lazy?
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reached_by(parent_access_keys, access_key)
|
|
28
|
+
@access_keys = parent_access_keys + [access_key]
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_s
|
|
33
|
+
evaluate.to_s
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
class ValueArray < ValueEnumerable
|
|
7
|
+
def initialize(array)
|
|
8
|
+
super
|
|
9
|
+
@value_hash = []
|
|
10
|
+
array.each_with_index do |value, index|
|
|
11
|
+
self[index] = value
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def evaluate
|
|
16
|
+
@value_hash.map(&:evaluate)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
class ValueEnumerable < Value
|
|
7
|
+
def [](key)
|
|
8
|
+
if @value_hash[key].nil?
|
|
9
|
+
Value.new(nil).reached_by(access_keys, key)
|
|
10
|
+
else
|
|
11
|
+
@value_hash[key].reached_by(access_keys, key)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def fetch(access_keys)
|
|
16
|
+
fetched_keys = access_keys.dup
|
|
17
|
+
value = self[fetched_keys.shift]
|
|
18
|
+
fetched_keys.any? ? value.fetch(fetched_keys) : value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def []=(key, value)
|
|
22
|
+
@value_hash[key] = case value
|
|
23
|
+
when Hash
|
|
24
|
+
ValueHash.new(value)
|
|
25
|
+
when Array
|
|
26
|
+
ValueArray.new(value)
|
|
27
|
+
else
|
|
28
|
+
Value.new(value)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|