jsapi 1.4.1 → 2.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 +4 -4
- data/lib/jsapi/controller/actions/class_methods.rb +61 -0
- data/lib/jsapi/controller/actions.rb +13 -0
- data/lib/jsapi/controller/authentication/class_methods.rb +65 -0
- data/lib/jsapi/controller/authentication/credentials/api_key.rb +24 -0
- data/lib/jsapi/controller/authentication/credentials/http/base.rb +25 -0
- data/lib/jsapi/controller/authentication/credentials/http/basic.rb +34 -0
- data/lib/jsapi/controller/authentication/credentials/http/bearer.rb +30 -0
- data/lib/jsapi/controller/authentication/credentials/http.rb +5 -0
- data/lib/jsapi/controller/authentication/credentials.rb +38 -0
- data/lib/jsapi/controller/authentication.rb +70 -0
- data/lib/jsapi/controller/base.rb +5 -4
- data/lib/jsapi/controller/methods/callbacks/callback.rb +80 -0
- data/lib/jsapi/controller/methods/callbacks/class_methods.rb +62 -0
- data/lib/jsapi/controller/methods/callbacks.rb +54 -0
- data/lib/jsapi/controller/methods.rb +188 -71
- data/lib/jsapi/controller/parameters.rb +24 -20
- data/lib/jsapi/controller/response.rb +71 -39
- data/lib/jsapi/controller.rb +2 -1
- data/lib/jsapi/dsl/base.rb +38 -5
- data/lib/jsapi/dsl/class_methods.rb +2 -2
- data/lib/jsapi/dsl/definitions.rb +41 -27
- data/lib/jsapi/dsl/operation.rb +10 -109
- data/lib/jsapi/dsl/parameter.rb +1 -1
- data/lib/jsapi/dsl/path.rb +41 -18
- data/lib/jsapi/dsl/request_body.rb +1 -1
- data/lib/jsapi/dsl/response.rb +9 -6
- data/lib/jsapi/dsl/schema.rb +11 -5
- data/lib/jsapi/dsl/shared_operation_methods.rb +140 -0
- data/lib/jsapi/dsl.rb +1 -2
- data/lib/jsapi/json/array.rb +2 -2
- data/lib/jsapi/json/object.rb +6 -6
- data/lib/jsapi/json.rb +4 -6
- data/lib/jsapi/media/range.rb +39 -6
- data/lib/jsapi/media/type.rb +8 -4
- data/lib/jsapi/media.rb +5 -0
- data/lib/jsapi/messages.rb +19 -0
- data/lib/jsapi/meta/callback/base.rb +63 -8
- data/lib/jsapi/meta/content.rb +59 -0
- data/lib/jsapi/meta/definitions.rb +291 -141
- data/lib/jsapi/meta/example/base.rb +41 -8
- data/lib/jsapi/meta/existence.rb +1 -1
- data/lib/jsapi/meta/header/base.rb +4 -2
- data/lib/jsapi/meta/info.rb +3 -1
- data/lib/jsapi/meta/license.rb +11 -5
- data/lib/jsapi/meta/model/attributes/class_methods.rb +150 -0
- data/lib/jsapi/meta/model/attributes/frozen_error.rb +16 -0
- data/lib/jsapi/meta/model/attributes/type_caster.rb +56 -0
- data/lib/jsapi/meta/model/attributes.rb +24 -118
- data/lib/jsapi/meta/model/base.rb +2 -5
- data/lib/jsapi/meta/model/reference.rb +46 -10
- data/lib/jsapi/meta/model/wrappable.rb +23 -0
- data/lib/jsapi/meta/model/wrapper.rb +26 -0
- data/lib/jsapi/meta/model.rb +2 -1
- data/lib/jsapi/meta/oauth_flow.rb +1 -1
- data/lib/jsapi/meta/openapi/extensions.rb +5 -6
- data/lib/jsapi/meta/openapi/version.rb +2 -2
- data/lib/jsapi/meta/operation.rb +177 -71
- data/lib/jsapi/meta/parameter/base.rb +9 -5
- data/lib/jsapi/meta/parameter/wrapper.rb +13 -0
- data/lib/jsapi/meta/parameter.rb +3 -0
- data/lib/jsapi/meta/path.rb +59 -13
- data/lib/jsapi/meta/pathname.rb +3 -0
- data/lib/jsapi/meta/property.rb +10 -0
- data/lib/jsapi/meta/request_body/base.rb +69 -32
- data/lib/jsapi/meta/request_body/wrapper.rb +13 -0
- data/lib/jsapi/meta/request_body.rb +3 -0
- data/lib/jsapi/meta/rescue_handler.rb +18 -17
- data/lib/jsapi/meta/response/base.rb +86 -49
- data/lib/jsapi/meta/response/reference.rb +11 -1
- data/lib/jsapi/meta/response/wrapper.rb +26 -0
- data/lib/jsapi/meta/response.rb +3 -0
- data/lib/jsapi/meta/schema/additional_properties.rb +8 -0
- data/lib/jsapi/meta/schema/array.rb +20 -8
- data/lib/jsapi/meta/schema/base.rb +10 -9
- data/lib/jsapi/meta/schema/numeric.rb +26 -20
- data/lib/jsapi/meta/schema/object.rb +60 -44
- data/lib/jsapi/meta/schema/reference.rb +1 -8
- data/lib/jsapi/meta/schema/string.rb +12 -6
- data/lib/jsapi/meta/schema/wrapper.rb +31 -0
- data/lib/jsapi/meta/schema.rb +22 -9
- data/lib/jsapi/meta/security_requirement.rb +2 -2
- data/lib/jsapi/meta/security_scheme/api_key.rb +5 -2
- data/lib/jsapi/meta/security_scheme/base.rb +7 -5
- data/lib/jsapi/meta/security_scheme/http/basic.rb +5 -7
- data/lib/jsapi/meta/security_scheme/http/bearer.rb +5 -5
- data/lib/jsapi/meta/security_scheme/http/other.rb +1 -3
- data/lib/jsapi/meta/security_scheme/mutual_tls.rb +1 -3
- data/lib/jsapi/meta/security_scheme/oauth2.rb +18 -13
- data/lib/jsapi/meta/security_scheme/open_id_connect.rb +4 -4
- data/lib/jsapi/meta/security_scheme.rb +4 -4
- data/lib/jsapi/meta/server.rb +4 -2
- data/lib/jsapi/meta/tag.rb +9 -3
- data/lib/jsapi/meta.rb +2 -1
- data/lib/jsapi/model/base.rb +1 -1
- data/lib/jsapi/status/base.rb +35 -0
- data/lib/jsapi/status/code.rb +113 -0
- data/lib/jsapi/status/default.rb +16 -0
- data/lib/jsapi/status/range.rb +35 -0
- data/lib/jsapi/status.rb +37 -0
- data/lib/jsapi/version.rb +1 -1
- data/lib/jsapi.rb +2 -3
- metadata +32 -10
- data/lib/jsapi/controller/parameters_invalid.rb +0 -27
- data/lib/jsapi/dsl/callback.rb +0 -21
- data/lib/jsapi/dsl/error.rb +0 -36
- data/lib/jsapi/invalid_argument_error.rb +0 -12
- data/lib/jsapi/invalid_value_error.rb +0 -12
- data/lib/jsapi/invalid_value_helper.rb +0 -17
- data/lib/jsapi/meta/model/type_caster.rb +0 -50
- data/lib/jsapi/meta/schema/delegator.rb +0 -26
|
@@ -1,8 +1,63 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'methods/callbacks'
|
|
4
|
+
|
|
3
5
|
module Jsapi
|
|
4
6
|
module Controller
|
|
7
|
+
# Raised when no operation with the specified name could be found.
|
|
8
|
+
class OperationNotFound < StandardError
|
|
9
|
+
def initialize(operation_name)
|
|
10
|
+
super("operation not found: #{operation_name}")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Raised when no suitable response could be found.
|
|
15
|
+
class ResponseNotFound < StandardError
|
|
16
|
+
def initialize(operation, status)
|
|
17
|
+
super(
|
|
18
|
+
if operation.responses.none?
|
|
19
|
+
"#{operation.name.inspect} has no responses"
|
|
20
|
+
else
|
|
21
|
+
"#{operation.name.inspect} has no response for status #{status}"
|
|
22
|
+
end
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Raised when the current request could not be authenticated.
|
|
28
|
+
class Unauthorized < StandardError
|
|
29
|
+
def initialize # :nodoc:
|
|
30
|
+
super('request could not be authenticated')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Raised when the request parameters are invalid.
|
|
35
|
+
class ParametersInvalid < StandardError
|
|
36
|
+
|
|
37
|
+
# The parameters.
|
|
38
|
+
attr_reader :params
|
|
39
|
+
|
|
40
|
+
def initialize(params)
|
|
41
|
+
@params = params
|
|
42
|
+
super('')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns the errors encountered.
|
|
46
|
+
def errors
|
|
47
|
+
@params.errors.errors
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Overrides <code>Exception#message</code> to lazily generate the error message.
|
|
51
|
+
def message
|
|
52
|
+
"#{@params.errors.full_messages.map { |m| m.delete_suffix('.') }.join('. ')}."
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
5
56
|
module Methods
|
|
57
|
+
def self.included(base) # :nodoc:
|
|
58
|
+
base.include(Callbacks)
|
|
59
|
+
end
|
|
60
|
+
|
|
6
61
|
# Returns the Meta::Definitions instance associated with the controller class. In
|
|
7
62
|
# particular, this method can be used to create an OpenAPI document, for example:
|
|
8
63
|
#
|
|
@@ -16,18 +71,32 @@ module Jsapi
|
|
|
16
71
|
# :method: api_operation
|
|
17
72
|
# :args: operation_name = nil, omit: nil, status: nil, strong: false, &block
|
|
18
73
|
#
|
|
19
|
-
# Performs an API operation by calling the given block.
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
74
|
+
# Performs an API operation by calling the given block.
|
|
75
|
+
#
|
|
76
|
+
# The request parameters are passed as an instance of the operation's model class to the
|
|
77
|
+
# block. Parameter names are converted to snake case.
|
|
78
|
+
#
|
|
79
|
+
# The object returned by the block is implicitly rendered or streamed according to the
|
|
80
|
+
# most appropriate +response+ specification if the media type of that response is one of:
|
|
81
|
+
#
|
|
82
|
+
# - <code>"application/json"</code>, <code>"text/json"</code>, <code>"\*/\*+json"</code> -
|
|
83
|
+
# The \JSON representation of the object is rendered.
|
|
84
|
+
# - <code>"application/json-seq"</code> - The object is streamed in \JSON sequence
|
|
85
|
+
# text format.
|
|
86
|
+
# - <code>"text/plain"</code> - The +to_s+ representation of the object is rendered.
|
|
87
|
+
#
|
|
88
|
+
# Example:
|
|
25
89
|
#
|
|
26
90
|
# api_operation('foo') do |api_params|
|
|
27
91
|
# # ...
|
|
28
92
|
# end
|
|
29
93
|
#
|
|
30
|
-
# +operation_name+ can be
|
|
94
|
+
# +operation_name+ can be omitted if the controller handles one operation only.
|
|
95
|
+
#
|
|
96
|
+
# If no operation could be found for +operation_name+, an OperationNotFound exception
|
|
97
|
+
# is raised.
|
|
98
|
+
#
|
|
99
|
+
# +:status+ specifies the HTTP status code of the response to be produced.
|
|
31
100
|
#
|
|
32
101
|
# If +:strong+ is +true+, parameters that can be mapped are accepted only. That means
|
|
33
102
|
# that the model passed to the block is invalid if there are any request parameters
|
|
@@ -39,7 +108,10 @@ module Jsapi
|
|
|
39
108
|
# - +:empty+ - All of the properties whose value is empty are omitted.
|
|
40
109
|
# - +:nil+ - All of the properties whose value is +nil+ are omitted.
|
|
41
110
|
#
|
|
42
|
-
# Raises an
|
|
111
|
+
# Raises an Unauthorized exception if the Authentication module is included and the
|
|
112
|
+
# current request could not be authenticated.
|
|
113
|
+
#
|
|
114
|
+
# See Callbacks::ClassMethods for possible callbacks.
|
|
43
115
|
|
|
44
116
|
##
|
|
45
117
|
# :method: api_operation!
|
|
@@ -55,41 +127,77 @@ module Jsapi
|
|
|
55
127
|
[true, false].each do |bang|
|
|
56
128
|
define_method(bang ? :api_operation! : :api_operation) \
|
|
57
129
|
do |operation_name = nil, omit: nil, status: nil, strong: false, &block|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
130
|
+
operation = _api_operation(operation_name)
|
|
131
|
+
response_model = nil
|
|
132
|
+
|
|
133
|
+
result = begin
|
|
134
|
+
# Authenticate request first if Authentication is included
|
|
135
|
+
if respond_to?(:_api_authenticated?, true)
|
|
136
|
+
raise Unauthorized unless _api_authenticated?(operation)
|
|
137
|
+
|
|
138
|
+
_api_callback(:after_authentication, operation_name)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
status = Status::Code.from(status)
|
|
142
|
+
response_model = _api_response_model(operation, status)
|
|
143
|
+
|
|
144
|
+
api_params = _api_params(operation, strong: strong)
|
|
145
|
+
|
|
146
|
+
if bang
|
|
147
|
+
raise ParametersInvalid.new(api_params) if api_params.invalid?
|
|
148
|
+
|
|
149
|
+
_api_callback(:after_validation, operation_name, api_params)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
block&.call(api_params)
|
|
153
|
+
rescue StandardError => e
|
|
154
|
+
definitions = api_definitions
|
|
155
|
+
|
|
156
|
+
# Lookup a rescue handler
|
|
157
|
+
rescue_handler = definitions.rescue_handler_for(e)
|
|
158
|
+
raise e if rescue_handler.nil?
|
|
159
|
+
|
|
160
|
+
# Replace the status code and response model
|
|
161
|
+
status = rescue_handler.status_code
|
|
162
|
+
response_model = operation.find_response(status)
|
|
163
|
+
raise e if response_model.nil?
|
|
164
|
+
|
|
165
|
+
# Call on_rescue callbacks
|
|
166
|
+
definitions.on_rescue_callbacks.each do |callback|
|
|
167
|
+
callback.respond_to?(:call) ? callback.call(e) : send(callback, e)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
Error.new(e, status: status)
|
|
171
|
+
end
|
|
172
|
+
# Return if response body has already been rendered
|
|
173
|
+
return if response_body
|
|
174
|
+
|
|
175
|
+
# Produce response
|
|
176
|
+
media_type, content_model = _api_media_type_and_content_model(response_model)
|
|
177
|
+
status = status&.to_i
|
|
178
|
+
return head(status) unless content_model
|
|
179
|
+
|
|
180
|
+
result = _api_before_rendering(operation_name, result, api_params)
|
|
62
181
|
|
|
63
|
-
# Perform operation
|
|
64
|
-
api_params = _api_params(operation_model, definitions, strong: strong)
|
|
65
182
|
api_response = Response.new(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
rescue StandardError => e
|
|
71
|
-
# Lookup a rescue handler
|
|
72
|
-
rescue_handler = definitions.rescue_handler_for(e)
|
|
73
|
-
raise e if rescue_handler.nil?
|
|
74
|
-
|
|
75
|
-
# Change the HTTP status code and response model
|
|
76
|
-
status = rescue_handler.status
|
|
77
|
-
response_model = operation_model.response(status)&.resolve(definitions)
|
|
78
|
-
raise e if response_model.nil?
|
|
79
|
-
|
|
80
|
-
# Call on_rescue callbacks
|
|
81
|
-
definitions.on_rescue_callbacks.each do |callback|
|
|
82
|
-
callback.respond_to?(:call) ? callback.call(e) : send(callback, e)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
Error.new(e, status: status)
|
|
86
|
-
end,
|
|
87
|
-
response_model, definitions, omit: omit
|
|
183
|
+
result,
|
|
184
|
+
content_model,
|
|
185
|
+
omit: omit,
|
|
186
|
+
locale: response_model.locale
|
|
88
187
|
)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
188
|
+
if media_type.json?
|
|
189
|
+
render(
|
|
190
|
+
json: api_response,
|
|
191
|
+
status: status,
|
|
192
|
+
content_type: media_type.to_s
|
|
193
|
+
)
|
|
194
|
+
elsif media_type == Media::Type::TEXT_PLAIN
|
|
195
|
+
render(
|
|
196
|
+
plain: result,
|
|
197
|
+
status: status,
|
|
198
|
+
content_type: media_type.to_s
|
|
199
|
+
)
|
|
200
|
+
elsif media_type == Media::Type::APPLICATION_JSON_SEQ
|
|
93
201
|
self.content_type = media_type.to_s
|
|
94
202
|
response.status = status
|
|
95
203
|
|
|
@@ -98,8 +206,6 @@ module Jsapi
|
|
|
98
206
|
ensure
|
|
99
207
|
stream.close
|
|
100
208
|
end
|
|
101
|
-
elsif media_type.json?
|
|
102
|
-
render(json: api_response, status: status, content_type: media_type.to_s)
|
|
103
209
|
end
|
|
104
210
|
end
|
|
105
211
|
end
|
|
@@ -109,7 +215,10 @@ module Jsapi
|
|
|
109
215
|
#
|
|
110
216
|
# params = api_params('foo')
|
|
111
217
|
#
|
|
112
|
-
# +operation_name+ can be
|
|
218
|
+
# +operation_name+ can be omitted if the controller handles one operation only.
|
|
219
|
+
#
|
|
220
|
+
# If no operation could be found for +operation_name+, an OperationNotFound exception
|
|
221
|
+
# is raised.
|
|
113
222
|
#
|
|
114
223
|
# If +strong+ is +true+, parameters that can be mapped are accepted only. That means
|
|
115
224
|
# that the model returned is invalid if there are any request parameters that can't be
|
|
@@ -117,12 +226,7 @@ module Jsapi
|
|
|
117
226
|
#
|
|
118
227
|
# Note that each call of +api_params+ returns a newly created instance.
|
|
119
228
|
def api_params(operation_name = nil, strong: false)
|
|
120
|
-
|
|
121
|
-
_api_params(
|
|
122
|
-
_find_api_operation_model(operation_name, definitions),
|
|
123
|
-
definitions,
|
|
124
|
-
strong: strong
|
|
125
|
-
)
|
|
229
|
+
_api_params(_api_operation(operation_name), strong: strong)
|
|
126
230
|
end
|
|
127
231
|
|
|
128
232
|
# Returns a Response to serialize the JSON representation of +result+ according to the
|
|
@@ -130,49 +234,62 @@ module Jsapi
|
|
|
130
234
|
#
|
|
131
235
|
# render(json: api_response(bar, 'foo', status: 200))
|
|
132
236
|
#
|
|
133
|
-
# +operation_name+ can be
|
|
237
|
+
# +operation_name+ can be omitted if the controller handles one operation only.
|
|
238
|
+
#
|
|
239
|
+
# If no operation could be found for +operation_name+, an OperationNotFound exception
|
|
240
|
+
# is raised.
|
|
241
|
+
#
|
|
242
|
+
# +:status+ specifies the HTTP status code of the response to be produced.
|
|
134
243
|
#
|
|
135
244
|
# The +:omit+ option specifies on which conditions properties are omitted.
|
|
136
245
|
# Possible values are:
|
|
137
246
|
#
|
|
138
247
|
# - +:empty+ - All of the properties whose value is empty are omitted.
|
|
139
248
|
# - +:nil+ - All of the properties whose value is +nil+ are omitted.
|
|
140
|
-
#
|
|
141
|
-
# Raises an +ArgumentError+ when +:omit+ is other than +:empty+, +:nil+ or +nil+.
|
|
142
249
|
def api_response(result, operation_name = nil, omit: nil, status: nil)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
response_model =
|
|
250
|
+
status = Status::Code.from(status)
|
|
251
|
+
operation = _api_operation(operation_name)
|
|
252
|
+
response_model = _api_response_model(operation, status)
|
|
146
253
|
|
|
147
|
-
Response.new(
|
|
254
|
+
Response.new(
|
|
255
|
+
result,
|
|
256
|
+
_api_media_type_and_content_model(response_model).second,
|
|
257
|
+
omit: omit,
|
|
258
|
+
locale: response_model.locale
|
|
259
|
+
)
|
|
148
260
|
end
|
|
149
261
|
|
|
150
262
|
private
|
|
151
263
|
|
|
152
|
-
def
|
|
153
|
-
|
|
264
|
+
def _api_media_type_and_content_model(response_model)
|
|
265
|
+
response_model.media_type_and_content_for(
|
|
266
|
+
*(request.headers['Accept']&.split(',').presence || [Media::Range::ALL])
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def _api_operation(operation_name)
|
|
271
|
+
operation = api_definitions.find_operation(operation_name)
|
|
272
|
+
return operation if operation
|
|
273
|
+
|
|
274
|
+
raise OperationNotFound.new(operation_name)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def _api_params(operation, strong:)
|
|
278
|
+
(operation.model || Model::Base).new(
|
|
154
279
|
Parameters.new(
|
|
155
280
|
params.except(:action, :controller, :format).permit!,
|
|
156
281
|
request,
|
|
157
|
-
|
|
158
|
-
definitions,
|
|
282
|
+
operation,
|
|
159
283
|
strong: strong
|
|
160
284
|
)
|
|
161
285
|
)
|
|
162
286
|
end
|
|
163
287
|
|
|
164
|
-
def
|
|
165
|
-
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
raise "status code not defined: #{status}"
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def _find_api_operation_model(operation_name, definitions)
|
|
172
|
-
operation = definitions.find_operation(operation_name)
|
|
173
|
-
return operation if operation
|
|
288
|
+
def _api_response_model(operation, status)
|
|
289
|
+
response_model = operation.find_response(status)
|
|
290
|
+
return response_model if response_model
|
|
174
291
|
|
|
175
|
-
raise
|
|
292
|
+
raise ResponseNotFound.new(operation, status)
|
|
176
293
|
end
|
|
177
294
|
end
|
|
178
295
|
end
|
|
@@ -8,25 +8,23 @@ module Jsapi
|
|
|
8
8
|
|
|
9
9
|
attr_reader :raw_additional_attributes, :raw_attributes
|
|
10
10
|
|
|
11
|
-
# Creates a new instance that wraps +params+ according to +operation+.
|
|
12
|
-
# resolved to API components in +definitions+.
|
|
11
|
+
# Creates a new instance that wraps +params+ according to +operation+.
|
|
13
12
|
#
|
|
14
13
|
# If +strong+ is true+ parameters that can be mapped are accepted only. That means that
|
|
15
14
|
# the instance created is invalid if +params+ contains any parameters that can't be
|
|
16
15
|
# mapped to a parameter or a request body property of +operation+.
|
|
17
|
-
def initialize(params, request, operation,
|
|
16
|
+
def initialize(params, request, operation, strong: false)
|
|
18
17
|
params = params.to_h
|
|
19
18
|
unassigned_params = params.dup
|
|
20
19
|
|
|
21
20
|
@params_to_be_validated = strong == true ? params.dup : {}
|
|
22
|
-
@raw_attributes = {}
|
|
23
21
|
|
|
24
22
|
# Parameters
|
|
25
|
-
operation.
|
|
26
|
-
|
|
23
|
+
@raw_attributes = operation.parameters.transform_values do |parameter_model|
|
|
24
|
+
JSON.wrap(
|
|
27
25
|
case parameter_model.in
|
|
28
26
|
when 'header'
|
|
29
|
-
request.headers[name]
|
|
27
|
+
request.headers[parameter_model.name]
|
|
30
28
|
when 'querystring'
|
|
31
29
|
query_params = request.query_parameters
|
|
32
30
|
keys = query_params.keys
|
|
@@ -36,27 +34,23 @@ module Jsapi
|
|
|
36
34
|
|
|
37
35
|
parameter_model.object? ? params.slice(*keys) : query_params.to_query
|
|
38
36
|
else
|
|
39
|
-
unassigned_params.delete(name)
|
|
37
|
+
unassigned_params.delete(parameter_model.name)
|
|
40
38
|
end,
|
|
41
|
-
parameter_model.schema
|
|
42
|
-
definitions,
|
|
39
|
+
parameter_model.schema,
|
|
43
40
|
context: :request
|
|
44
41
|
)
|
|
45
42
|
end
|
|
46
43
|
|
|
47
44
|
# Request body
|
|
48
|
-
request_body_schema = operation.request_body&.
|
|
49
|
-
&.schema&.resolve(definitions)
|
|
45
|
+
request_body_schema = operation.request_body&.content_for(request.media_type)&.schema
|
|
50
46
|
if request_body_schema&.object?
|
|
51
47
|
request_body = JSON.wrap(
|
|
52
48
|
unassigned_params,
|
|
53
49
|
request_body_schema,
|
|
54
|
-
definitions,
|
|
55
50
|
context: :request
|
|
56
51
|
)
|
|
57
52
|
@raw_attributes.merge!(request_body.raw_attributes)
|
|
58
53
|
@raw_additional_attributes = request_body.raw_additional_attributes
|
|
59
|
-
@params_to_be_validated.except!(*@raw_additional_attributes.keys)
|
|
60
54
|
else
|
|
61
55
|
@raw_additional_attributes = {}
|
|
62
56
|
end
|
|
@@ -66,18 +60,24 @@ module Jsapi
|
|
|
66
60
|
# otherwise. Detected errors are added to +errors+.
|
|
67
61
|
def validate(errors)
|
|
68
62
|
validate_attributes(errors) &&
|
|
69
|
-
validate_parameters(@params_to_be_validated,
|
|
63
|
+
validate_parameters(@params_to_be_validated, self, errors)
|
|
70
64
|
end
|
|
71
65
|
|
|
72
66
|
private
|
|
73
67
|
|
|
74
|
-
def validate_parameters(params,
|
|
68
|
+
def validate_parameters(params, model, errors, path = [])
|
|
75
69
|
params.each.map do |key, value|
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
!value.respond_to?(:keys) || validate_parameters(
|
|
70
|
+
if model.raw_attributes.key?(key)
|
|
71
|
+
validate_parameter(
|
|
79
72
|
value,
|
|
80
|
-
|
|
73
|
+
model.raw_attributes[key],
|
|
74
|
+
errors,
|
|
75
|
+
path + [key]
|
|
76
|
+
)
|
|
77
|
+
elsif model.raw_additional_attributes.key?(key)
|
|
78
|
+
validate_parameter(
|
|
79
|
+
value,
|
|
80
|
+
model.raw_additional_attributes[key],
|
|
81
81
|
errors,
|
|
82
82
|
path + [key]
|
|
83
83
|
)
|
|
@@ -94,6 +94,10 @@ module Jsapi
|
|
|
94
94
|
end
|
|
95
95
|
end.all?
|
|
96
96
|
end
|
|
97
|
+
|
|
98
|
+
def validate_parameter(value, model, errors, path)
|
|
99
|
+
!model.schema.object? || validate_parameters(value, model, errors, path)
|
|
100
|
+
end
|
|
97
101
|
end
|
|
98
102
|
end
|
|
99
103
|
end
|
|
@@ -15,8 +15,34 @@ module Jsapi
|
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
class HashReader # :nodoc:
|
|
19
|
+
delegate_missing_to :@hash
|
|
20
|
+
|
|
21
|
+
def initialize(hash)
|
|
22
|
+
@hash = hash
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def [](key)
|
|
26
|
+
return unless @hash.key?(key)
|
|
27
|
+
|
|
28
|
+
(@read_keys ||= []) << key
|
|
29
|
+
@hash[key]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def additional_properties
|
|
33
|
+
if @hash.key?('additional_properties')
|
|
34
|
+
@hash['additional_properties']
|
|
35
|
+
elsif @hash.key?(:additional_properties)
|
|
36
|
+
@hash[:additional_properties]
|
|
37
|
+
elsif @read_keys
|
|
38
|
+
@hash.except(*@read_keys)
|
|
39
|
+
else
|
|
40
|
+
@hash
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Creates a new instance to jsonify +object+ according to +content_model+.
|
|
20
46
|
#
|
|
21
47
|
# The +:omit+ option specifies on which conditions properties are omitted.
|
|
22
48
|
# Possible values are:
|
|
@@ -25,10 +51,9 @@ module Jsapi
|
|
|
25
51
|
# - +:nil+ - All of the properties whose value is +nil+ are omitted.
|
|
26
52
|
#
|
|
27
53
|
# Raises an +ArgumentError+ when +:omit+ is other than +:empty+, +:nil+ or +nil+.
|
|
28
|
-
def initialize(object,
|
|
54
|
+
def initialize(object, content_model, omit: nil, locale: nil)
|
|
29
55
|
@object = object
|
|
30
|
-
@
|
|
31
|
-
@definitions = definitions
|
|
56
|
+
@content_model = content_model
|
|
32
57
|
|
|
33
58
|
@omittable_check =
|
|
34
59
|
case omit
|
|
@@ -39,8 +64,14 @@ module Jsapi
|
|
|
39
64
|
when :empty
|
|
40
65
|
->(value, schema) { schema.omittable? && value.try(:empty?) }
|
|
41
66
|
else
|
|
42
|
-
raise
|
|
67
|
+
raise ArgumentError, Messages.invalid_value(
|
|
68
|
+
name: 'omit',
|
|
69
|
+
value: omit,
|
|
70
|
+
valid_values: %i[empty nil]
|
|
71
|
+
)
|
|
43
72
|
end
|
|
73
|
+
|
|
74
|
+
@locale = locale
|
|
44
75
|
end
|
|
45
76
|
|
|
46
77
|
def inspect # :nodoc:
|
|
@@ -49,17 +80,16 @@ module Jsapi
|
|
|
49
80
|
|
|
50
81
|
# Returns the \JSON representation of the response as a string.
|
|
51
82
|
def to_json(*)
|
|
52
|
-
|
|
53
|
-
with_locale { jsonify(@object, schema) }.to_json
|
|
83
|
+
with_locale { jsonify(@object, @content_model.schema) }.to_json
|
|
54
84
|
end
|
|
55
85
|
|
|
56
86
|
# Writes the response in \JSON sequence text format to +stream+.
|
|
57
87
|
def write_json_seq_to(stream)
|
|
58
|
-
schema = @
|
|
88
|
+
schema = @content_model.schema
|
|
59
89
|
with_locale do
|
|
60
90
|
items, item_schema =
|
|
61
91
|
if schema.array? && @object.respond_to?(:each)
|
|
62
|
-
[@object, schema.items
|
|
92
|
+
[@object, schema.items]
|
|
63
93
|
else
|
|
64
94
|
[[@object], schema]
|
|
65
95
|
end
|
|
@@ -76,7 +106,7 @@ module Jsapi
|
|
|
76
106
|
private
|
|
77
107
|
|
|
78
108
|
def jsonify(object, schema)
|
|
79
|
-
object = schema.default_value(
|
|
109
|
+
object = schema.default_value(context: :response) if object.nil?
|
|
80
110
|
|
|
81
111
|
if object.nil?
|
|
82
112
|
raise JsonifyError, "can't be nil" unless schema.nullable?
|
|
@@ -112,7 +142,7 @@ module Jsapi
|
|
|
112
142
|
end
|
|
113
143
|
|
|
114
144
|
def jsonify_array(array, schema)
|
|
115
|
-
item_schema = schema.items
|
|
145
|
+
item_schema = schema.items
|
|
116
146
|
index = 0
|
|
117
147
|
|
|
118
148
|
Array(array).map do |item|
|
|
@@ -125,39 +155,41 @@ module Jsapi
|
|
|
125
155
|
end
|
|
126
156
|
|
|
127
157
|
def jsonify_object(object, schema)
|
|
128
|
-
schema = schema.resolve_schema(object,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
end
|
|
142
|
-
# Add additional properties
|
|
143
|
-
if (additional_properties = schema.additional_properties)
|
|
144
|
-
additional_properties_schema = additional_properties.schema.resolve(@definitions)
|
|
145
|
-
|
|
146
|
-
additional_properties.source.call(object)&.each do |key, value|
|
|
147
|
-
next if properties.key?(key = key.to_s)
|
|
148
|
-
|
|
149
|
-
properties[key] = jsonify(value, additional_properties_schema)
|
|
158
|
+
schema = schema.resolve_schema(object, context: :response)
|
|
159
|
+
additional_properties = schema.additional_properties
|
|
160
|
+
object = HashReader.new(object) if additional_properties && object.is_a?(Hash)
|
|
161
|
+
|
|
162
|
+
{}.tap do |properties|
|
|
163
|
+
# Add properties
|
|
164
|
+
schema.resolve_properties(context: :response).each_value do |property|
|
|
165
|
+
property_schema = property.schema
|
|
166
|
+
property_value = property.reader.call(object)
|
|
167
|
+
property_value = property_schema.default if property_value.nil?
|
|
168
|
+
next if @omittable_check&.call(property_value, property_schema)
|
|
169
|
+
|
|
170
|
+
properties[property.name] = jsonify(property_value, property_schema)
|
|
150
171
|
rescue JsonifyError => e
|
|
151
|
-
raise e.prepend(".#{
|
|
172
|
+
raise e.prepend(".#{property.name}")
|
|
152
173
|
end
|
|
153
|
-
|
|
174
|
+
# Add additional properties
|
|
175
|
+
if additional_properties
|
|
176
|
+
additional_properties_schema = additional_properties.schema
|
|
177
|
+
|
|
178
|
+
additional_properties.source.call(object)&.each do |key, value|
|
|
179
|
+
key = key.to_s
|
|
180
|
+
next if properties.key?(key)
|
|
154
181
|
|
|
155
|
-
|
|
182
|
+
properties[key] = jsonify(value, additional_properties_schema)
|
|
183
|
+
rescue JsonifyError => e
|
|
184
|
+
raise e.prepend(".#{key}")
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end.presence
|
|
156
188
|
end
|
|
157
189
|
|
|
158
190
|
def with_locale(&block)
|
|
159
|
-
if @
|
|
160
|
-
I18n.with_locale(@
|
|
191
|
+
if @locale
|
|
192
|
+
I18n.with_locale(@locale, &block)
|
|
161
193
|
else
|
|
162
194
|
yield
|
|
163
195
|
end
|
data/lib/jsapi/controller.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'controller/parameters_invalid'
|
|
4
3
|
require_relative 'controller/error'
|
|
4
|
+
require_relative 'controller/authentication'
|
|
5
5
|
require_relative 'controller/parameters'
|
|
6
6
|
require_relative 'controller/response'
|
|
7
7
|
require_relative 'controller/methods'
|
|
8
|
+
require_relative 'controller/actions'
|
|
8
9
|
require_relative 'controller/base'
|
|
9
10
|
|
|
10
11
|
module Jsapi
|