jsapi 1.4 → 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 +209 -116
- 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 +102 -0
- data/lib/jsapi/media/type.rb +70 -0
- data/lib/jsapi/media/type_and_subtype.rb +38 -0
- data/lib/jsapi/media.rb +9 -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 +299 -153
- data/lib/jsapi/meta/example/base.rb +41 -8
- data/lib/jsapi/meta/existence.rb +4 -2
- 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 +16 -4
- data/lib/jsapi/meta/operation.rb +177 -71
- data/lib/jsapi/meta/parameter/base.rb +10 -6
- 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 +6 -3
- 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 +82 -58
- 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/boundary.rb +1 -0
- 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 +3 -3
- metadata +36 -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
|
#
|
|
@@ -12,18 +67,36 @@ module Jsapi
|
|
|
12
67
|
self.class.api_definitions
|
|
13
68
|
end
|
|
14
69
|
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
70
|
+
##
|
|
71
|
+
# :method: api_operation
|
|
72
|
+
# :args: operation_name = nil, omit: nil, status: nil, strong: false, &block
|
|
73
|
+
#
|
|
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:
|
|
21
89
|
#
|
|
22
90
|
# api_operation('foo') do |api_params|
|
|
23
91
|
# # ...
|
|
24
92
|
# end
|
|
25
93
|
#
|
|
26
|
-
# +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.
|
|
27
100
|
#
|
|
28
101
|
# If +:strong+ is +true+, parameters that can be mapped are accepted only. That means
|
|
29
102
|
# that the model passed to the block is invalid if there are any request parameters
|
|
@@ -35,42 +108,106 @@ module Jsapi
|
|
|
35
108
|
# - +:empty+ - All of the properties whose value is empty are omitted.
|
|
36
109
|
# - +:nil+ - All of the properties whose value is +nil+ are omitted.
|
|
37
110
|
#
|
|
38
|
-
# Raises an
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
strong: false,
|
|
43
|
-
&block)
|
|
44
|
-
_api_operation(
|
|
45
|
-
operation_name,
|
|
46
|
-
bang: false,
|
|
47
|
-
omit: omit,
|
|
48
|
-
status: status,
|
|
49
|
-
strong: strong,
|
|
50
|
-
&block
|
|
51
|
-
)
|
|
52
|
-
end
|
|
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.
|
|
53
115
|
|
|
54
|
-
|
|
55
|
-
#
|
|
116
|
+
##
|
|
117
|
+
# :method: api_operation!
|
|
118
|
+
# :args: operation_name = nil, omit: nil, status: nil, strong: false, &block
|
|
119
|
+
#
|
|
120
|
+
# Like +api_operation+, except that a ParametersInvalid exception is raised
|
|
121
|
+
# when request parameters are invalid.
|
|
56
122
|
#
|
|
57
123
|
# api_operation!('foo') do |api_params|
|
|
58
124
|
# # ...
|
|
59
125
|
# end
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
126
|
+
|
|
127
|
+
[true, false].each do |bang|
|
|
128
|
+
define_method(bang ? :api_operation! : :api_operation) \
|
|
129
|
+
do |operation_name = nil, omit: nil, status: nil, strong: false, &block|
|
|
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)
|
|
181
|
+
|
|
182
|
+
api_response = Response.new(
|
|
183
|
+
result,
|
|
184
|
+
content_model,
|
|
185
|
+
omit: omit,
|
|
186
|
+
locale: response_model.locale
|
|
187
|
+
)
|
|
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
|
|
201
|
+
self.content_type = media_type.to_s
|
|
202
|
+
response.status = status
|
|
203
|
+
|
|
204
|
+
response.stream.tap do |stream|
|
|
205
|
+
api_response.write_json_seq_to(stream)
|
|
206
|
+
ensure
|
|
207
|
+
stream.close
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
74
211
|
end
|
|
75
212
|
|
|
76
213
|
# Returns the request parameters as an instance of the operation's model class.
|
|
@@ -78,7 +215,10 @@ module Jsapi
|
|
|
78
215
|
#
|
|
79
216
|
# params = api_params('foo')
|
|
80
217
|
#
|
|
81
|
-
# +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.
|
|
82
222
|
#
|
|
83
223
|
# If +strong+ is +true+, parameters that can be mapped are accepted only. That means
|
|
84
224
|
# that the model returned is invalid if there are any request parameters that can't be
|
|
@@ -86,12 +226,7 @@ module Jsapi
|
|
|
86
226
|
#
|
|
87
227
|
# Note that each call of +api_params+ returns a newly created instance.
|
|
88
228
|
def api_params(operation_name = nil, strong: false)
|
|
89
|
-
|
|
90
|
-
_api_params(
|
|
91
|
-
_find_api_operation(operation_name, definitions),
|
|
92
|
-
definitions,
|
|
93
|
-
strong: strong
|
|
94
|
-
)
|
|
229
|
+
_api_params(_api_operation(operation_name), strong: strong)
|
|
95
230
|
end
|
|
96
231
|
|
|
97
232
|
# Returns a Response to serialize the JSON representation of +result+ according to the
|
|
@@ -99,104 +234,62 @@ module Jsapi
|
|
|
99
234
|
#
|
|
100
235
|
# render(json: api_response(bar, 'foo', status: 200))
|
|
101
236
|
#
|
|
102
|
-
# +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.
|
|
103
243
|
#
|
|
104
244
|
# The +:omit+ option specifies on which conditions properties are omitted.
|
|
105
245
|
# Possible values are:
|
|
106
246
|
#
|
|
107
247
|
# - +:empty+ - All of the properties whose value is empty are omitted.
|
|
108
248
|
# - +:nil+ - All of the properties whose value is +nil+ are omitted.
|
|
109
|
-
#
|
|
110
|
-
# Raises an +ArgumentError+ when +:omit+ is other than +:empty+, +:nil+ or +nil+.
|
|
111
249
|
def api_response(result, operation_name = nil, omit: nil, status: nil)
|
|
112
|
-
|
|
113
|
-
operation =
|
|
114
|
-
response_model =
|
|
250
|
+
status = Status::Code.from(status)
|
|
251
|
+
operation = _api_operation(operation_name)
|
|
252
|
+
response_model = _api_response_model(operation, status)
|
|
115
253
|
|
|
116
|
-
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
|
+
)
|
|
117
260
|
end
|
|
118
261
|
|
|
119
262
|
private
|
|
120
263
|
|
|
121
|
-
def
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
response_model = _api_response(operation, status, definitions)
|
|
127
|
-
head(status) && return unless block
|
|
128
|
-
|
|
129
|
-
params = _api_params(operation, definitions, strong: strong)
|
|
130
|
-
|
|
131
|
-
result = begin
|
|
132
|
-
raise ParametersInvalid.new(params) if bang && params.invalid?
|
|
133
|
-
|
|
134
|
-
block.call(params)
|
|
135
|
-
rescue StandardError => e
|
|
136
|
-
# Lookup a rescue handler
|
|
137
|
-
rescue_handler = definitions.rescue_handler_for(e)
|
|
138
|
-
raise e if rescue_handler.nil?
|
|
139
|
-
|
|
140
|
-
# Change the HTTP status code and response model
|
|
141
|
-
status = rescue_handler.status
|
|
142
|
-
response_model = operation.response(status)&.resolve(definitions)
|
|
143
|
-
raise e if response_model.nil?
|
|
144
|
-
|
|
145
|
-
# Call on_rescue callbacks
|
|
146
|
-
definitions.on_rescue_callbacks.each do |callback|
|
|
147
|
-
if callback.respond_to?(:call)
|
|
148
|
-
callback.call(e)
|
|
149
|
-
else
|
|
150
|
-
send(callback, e)
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
Error.new(e, status: status)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Write response
|
|
158
|
-
return unless response_model.json_type? || response_model.json_seq_type?
|
|
159
|
-
|
|
160
|
-
response = Response.new(result, response_model, definitions, omit: omit)
|
|
161
|
-
self.content_type = response_model.content_type
|
|
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
|
|
162
269
|
|
|
163
|
-
|
|
164
|
-
|
|
270
|
+
def _api_operation(operation_name)
|
|
271
|
+
operation = api_definitions.find_operation(operation_name)
|
|
272
|
+
return operation if operation
|
|
165
273
|
|
|
166
|
-
|
|
167
|
-
response.write_json_seq_to(stream)
|
|
168
|
-
ensure
|
|
169
|
-
stream.close
|
|
170
|
-
end
|
|
171
|
-
else
|
|
172
|
-
render(json: response, status: status)
|
|
173
|
-
end
|
|
274
|
+
raise OperationNotFound.new(operation_name)
|
|
174
275
|
end
|
|
175
276
|
|
|
176
|
-
def _api_params(operation,
|
|
277
|
+
def _api_params(operation, strong:)
|
|
177
278
|
(operation.model || Model::Base).new(
|
|
178
279
|
Parameters.new(
|
|
179
280
|
params.except(:action, :controller, :format).permit!,
|
|
180
281
|
request,
|
|
181
282
|
operation,
|
|
182
|
-
definitions,
|
|
183
283
|
strong: strong
|
|
184
284
|
)
|
|
185
285
|
)
|
|
186
286
|
end
|
|
187
287
|
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
raise "status code not defined: #{status}"
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def _find_api_operation(operation_name, definitions)
|
|
196
|
-
operation = definitions.find_operation(operation_name)
|
|
197
|
-
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
|
|
198
291
|
|
|
199
|
-
raise
|
|
292
|
+
raise ResponseNotFound.new(operation, status)
|
|
200
293
|
end
|
|
201
294
|
end
|
|
202
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
|