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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jsapi/controller/actions/class_methods.rb +61 -0
  3. data/lib/jsapi/controller/actions.rb +13 -0
  4. data/lib/jsapi/controller/authentication/class_methods.rb +65 -0
  5. data/lib/jsapi/controller/authentication/credentials/api_key.rb +24 -0
  6. data/lib/jsapi/controller/authentication/credentials/http/base.rb +25 -0
  7. data/lib/jsapi/controller/authentication/credentials/http/basic.rb +34 -0
  8. data/lib/jsapi/controller/authentication/credentials/http/bearer.rb +30 -0
  9. data/lib/jsapi/controller/authentication/credentials/http.rb +5 -0
  10. data/lib/jsapi/controller/authentication/credentials.rb +38 -0
  11. data/lib/jsapi/controller/authentication.rb +70 -0
  12. data/lib/jsapi/controller/base.rb +5 -4
  13. data/lib/jsapi/controller/methods/callbacks/callback.rb +80 -0
  14. data/lib/jsapi/controller/methods/callbacks/class_methods.rb +62 -0
  15. data/lib/jsapi/controller/methods/callbacks.rb +54 -0
  16. data/lib/jsapi/controller/methods.rb +188 -71
  17. data/lib/jsapi/controller/parameters.rb +24 -20
  18. data/lib/jsapi/controller/response.rb +71 -39
  19. data/lib/jsapi/controller.rb +2 -1
  20. data/lib/jsapi/dsl/base.rb +38 -5
  21. data/lib/jsapi/dsl/class_methods.rb +2 -2
  22. data/lib/jsapi/dsl/definitions.rb +41 -27
  23. data/lib/jsapi/dsl/operation.rb +10 -109
  24. data/lib/jsapi/dsl/parameter.rb +1 -1
  25. data/lib/jsapi/dsl/path.rb +41 -18
  26. data/lib/jsapi/dsl/request_body.rb +1 -1
  27. data/lib/jsapi/dsl/response.rb +9 -6
  28. data/lib/jsapi/dsl/schema.rb +11 -5
  29. data/lib/jsapi/dsl/shared_operation_methods.rb +140 -0
  30. data/lib/jsapi/dsl.rb +1 -2
  31. data/lib/jsapi/json/array.rb +2 -2
  32. data/lib/jsapi/json/object.rb +6 -6
  33. data/lib/jsapi/json.rb +4 -6
  34. data/lib/jsapi/media/range.rb +39 -6
  35. data/lib/jsapi/media/type.rb +8 -4
  36. data/lib/jsapi/media.rb +5 -0
  37. data/lib/jsapi/messages.rb +19 -0
  38. data/lib/jsapi/meta/callback/base.rb +63 -8
  39. data/lib/jsapi/meta/content.rb +59 -0
  40. data/lib/jsapi/meta/definitions.rb +291 -141
  41. data/lib/jsapi/meta/example/base.rb +41 -8
  42. data/lib/jsapi/meta/existence.rb +1 -1
  43. data/lib/jsapi/meta/header/base.rb +4 -2
  44. data/lib/jsapi/meta/info.rb +3 -1
  45. data/lib/jsapi/meta/license.rb +11 -5
  46. data/lib/jsapi/meta/model/attributes/class_methods.rb +150 -0
  47. data/lib/jsapi/meta/model/attributes/frozen_error.rb +16 -0
  48. data/lib/jsapi/meta/model/attributes/type_caster.rb +56 -0
  49. data/lib/jsapi/meta/model/attributes.rb +24 -118
  50. data/lib/jsapi/meta/model/base.rb +2 -5
  51. data/lib/jsapi/meta/model/reference.rb +46 -10
  52. data/lib/jsapi/meta/model/wrappable.rb +23 -0
  53. data/lib/jsapi/meta/model/wrapper.rb +26 -0
  54. data/lib/jsapi/meta/model.rb +2 -1
  55. data/lib/jsapi/meta/oauth_flow.rb +1 -1
  56. data/lib/jsapi/meta/openapi/extensions.rb +5 -6
  57. data/lib/jsapi/meta/openapi/version.rb +2 -2
  58. data/lib/jsapi/meta/operation.rb +177 -71
  59. data/lib/jsapi/meta/parameter/base.rb +9 -5
  60. data/lib/jsapi/meta/parameter/wrapper.rb +13 -0
  61. data/lib/jsapi/meta/parameter.rb +3 -0
  62. data/lib/jsapi/meta/path.rb +59 -13
  63. data/lib/jsapi/meta/pathname.rb +3 -0
  64. data/lib/jsapi/meta/property.rb +10 -0
  65. data/lib/jsapi/meta/request_body/base.rb +69 -32
  66. data/lib/jsapi/meta/request_body/wrapper.rb +13 -0
  67. data/lib/jsapi/meta/request_body.rb +3 -0
  68. data/lib/jsapi/meta/rescue_handler.rb +18 -17
  69. data/lib/jsapi/meta/response/base.rb +86 -49
  70. data/lib/jsapi/meta/response/reference.rb +11 -1
  71. data/lib/jsapi/meta/response/wrapper.rb +26 -0
  72. data/lib/jsapi/meta/response.rb +3 -0
  73. data/lib/jsapi/meta/schema/additional_properties.rb +8 -0
  74. data/lib/jsapi/meta/schema/array.rb +20 -8
  75. data/lib/jsapi/meta/schema/base.rb +10 -9
  76. data/lib/jsapi/meta/schema/numeric.rb +26 -20
  77. data/lib/jsapi/meta/schema/object.rb +60 -44
  78. data/lib/jsapi/meta/schema/reference.rb +1 -8
  79. data/lib/jsapi/meta/schema/string.rb +12 -6
  80. data/lib/jsapi/meta/schema/wrapper.rb +31 -0
  81. data/lib/jsapi/meta/schema.rb +22 -9
  82. data/lib/jsapi/meta/security_requirement.rb +2 -2
  83. data/lib/jsapi/meta/security_scheme/api_key.rb +5 -2
  84. data/lib/jsapi/meta/security_scheme/base.rb +7 -5
  85. data/lib/jsapi/meta/security_scheme/http/basic.rb +5 -7
  86. data/lib/jsapi/meta/security_scheme/http/bearer.rb +5 -5
  87. data/lib/jsapi/meta/security_scheme/http/other.rb +1 -3
  88. data/lib/jsapi/meta/security_scheme/mutual_tls.rb +1 -3
  89. data/lib/jsapi/meta/security_scheme/oauth2.rb +18 -13
  90. data/lib/jsapi/meta/security_scheme/open_id_connect.rb +4 -4
  91. data/lib/jsapi/meta/security_scheme.rb +4 -4
  92. data/lib/jsapi/meta/server.rb +4 -2
  93. data/lib/jsapi/meta/tag.rb +9 -3
  94. data/lib/jsapi/meta.rb +2 -1
  95. data/lib/jsapi/model/base.rb +1 -1
  96. data/lib/jsapi/status/base.rb +35 -0
  97. data/lib/jsapi/status/code.rb +113 -0
  98. data/lib/jsapi/status/default.rb +16 -0
  99. data/lib/jsapi/status/range.rb +35 -0
  100. data/lib/jsapi/status.rb +37 -0
  101. data/lib/jsapi/version.rb +1 -1
  102. data/lib/jsapi.rb +2 -3
  103. metadata +32 -10
  104. data/lib/jsapi/controller/parameters_invalid.rb +0 -27
  105. data/lib/jsapi/dsl/callback.rb +0 -21
  106. data/lib/jsapi/dsl/error.rb +0 -36
  107. data/lib/jsapi/invalid_argument_error.rb +0 -12
  108. data/lib/jsapi/invalid_value_error.rb +0 -12
  109. data/lib/jsapi/invalid_value_helper.rb +0 -17
  110. data/lib/jsapi/meta/model/type_caster.rb +0 -50
  111. data/lib/jsapi/meta/schema/delegator.rb +0 -26
@@ -7,22 +7,29 @@ module Jsapi
7
7
 
8
8
  ##
9
9
  # :attr: base_path
10
- # The base path of the API. Applies to \OpenAPI 2.0.
10
+ # The base path of the API.
11
+ #
12
+ # Applies to \OpenAPI 2.0.
11
13
  attribute :base_path, Pathname
12
14
 
13
15
  ##
14
16
  # :attr: callbacks
15
- # The reusable Callback objects. Applies to \OpenAPI 3.0 and higher.
17
+ # The reusable callbacks. Maps strings to Callback objects or references.
18
+ #
19
+ # Applies to \OpenAPI 3.0 and higher.
16
20
  attribute :callbacks, { String => Callback }
17
21
 
18
22
  ##
19
23
  # :attr: defaults
20
- # The Defaults.
24
+ # The registered default values for different schema types. Maps schema
25
+ # types to Defaults object.
21
26
  attribute :defaults, { String => Defaults }, keys: Schema::TYPES
22
27
 
23
28
  ##
24
29
  # :attr: examples
25
- # The reusable Example objects. Applies to \OpenAPI 3.0 and higher.
30
+ # The reusable examples. Maps example names to Example objects or references.
31
+ #
32
+ # Applies to \OpenAPI 3.0 and higher.
26
33
  attribute :examples, { String => Example }
27
34
 
28
35
  ##
@@ -32,12 +39,16 @@ module Jsapi
32
39
 
33
40
  ##
34
41
  # :attr: headers
35
- # The reusable Header objects. Applies to \OpenAPI 3.0 and higher.
42
+ # The reusable headers. Maps header names to Header objects or references.
43
+ #
44
+ # Applies to \OpenAPI 3.0 and higher.
36
45
  attribute :headers, { String => Header }
37
46
 
38
47
  ##
39
48
  # :attr: host
40
- # The host serving the API. Applies to \OpenAPI 2.0.
49
+ # The host serving the API.
50
+ #
51
+ # Applies to \OpenAPI 2.0.
41
52
  attribute :host, String
42
53
 
43
54
  ##
@@ -47,7 +58,9 @@ module Jsapi
47
58
 
48
59
  ##
49
60
  # :attr: links
50
- # The reusable Link objects. Applies to \OpenAPI 3.0 and higher.
61
+ # The reusable links. Maps link names to Link objects.
62
+ #
63
+ # Applies to \OpenAPI 3.0 and higher.
51
64
  attribute :links, { String => Link }
52
65
 
53
66
  ##
@@ -57,42 +70,45 @@ module Jsapi
57
70
 
58
71
  ##
59
72
  # :attr: operations
60
- # The Operation objects.
73
+ # The operations. Maps operation names to Operation objects.
61
74
  attribute :operations, { String => Operation }, accessors: %i[reader writer]
62
75
 
63
76
  ##
64
77
  # :attr: parameters
65
- # The reusable Parameter objects.
78
+ # The reusable parameters. Maps parameter names to Parameter objects
79
+ # or references.
66
80
  attribute :parameters, { String => Parameter }, accessors: %i[reader writer]
67
81
 
68
82
  ##
69
83
  # :attr: paths
70
- # The Path objects.
84
+ # The paths. Maps instances of Pathname to Path objects.
71
85
  attribute :paths, { Pathname => Path }, accessors: %i[reader writer]
72
86
 
73
87
  ##
74
88
  # :attr: rescue_handlers
75
- # The RescueHandler objects.
89
+ # The registered rescue handlers.
76
90
  attribute :rescue_handlers, [RescueHandler]
77
91
 
78
92
  ##
79
93
  # :attr: request_bodies
80
- # The reusable RequestBody objects.
94
+ # The reusable request bodies. Maps request body names to RequestBody
95
+ # objects or references.
81
96
  attribute :request_bodies, { String => RequestBody }
82
97
 
83
98
  ##
84
99
  # :attr: responses
85
- # The reusable Response objects.
100
+ # The reusable responses. Maps response names to Response objects or
101
+ # references.
86
102
  attribute :responses, { String => Response }
87
103
 
88
104
  ##
89
105
  # :attr: schemas
90
- # The reusable Schema objects.
106
+ # The reusable schemas. Maps schema names to Schema objects or references.
91
107
  attribute :schemas, { String => Schema }
92
108
 
93
109
  ##
94
110
  # :attr: schemes
95
- # The array of transfer protocols supported by the API. Possible values are:
111
+ # The transfer protocols supported by the API. Can contain one or more of:
96
112
  #
97
113
  # - <code>"http"</code>
98
114
  # - <code>"https"</code>
@@ -104,24 +120,26 @@ module Jsapi
104
120
 
105
121
  ##
106
122
  # :attr: security_requirements
107
- # The array of SecurityRequirement objects.
123
+ # The top-level security requirements.
108
124
  attribute :security_requirements, [SecurityRequirement]
109
125
 
110
126
  alias add_security add_security_requirement
111
127
 
112
128
  ##
113
129
  # :attr: security_schemes
114
- # The SecurityScheme objects.
130
+ # The security schemes.
115
131
  attribute :security_schemes, { String => SecurityScheme }
116
132
 
117
133
  ##
118
134
  # :attr: servers
119
- # The array of Server objects. Applies to \OpenAPI 3.0 and higher.
135
+ # The servers providing the API.
136
+ #
137
+ # Applies to \OpenAPI 3.0 and higher.
120
138
  attribute :servers, [Server]
121
139
 
122
140
  ##
123
141
  # :attr: tags
124
- # The array of Tag objects.
142
+ # The tags.
125
143
  attribute :tags, [Tag]
126
144
 
127
145
  # The class to which this instance is assigned.
@@ -144,30 +162,34 @@ module Jsapi
144
162
  end
145
163
 
146
164
  def add_operation(name, parent_path = nil, keywords = {}) # :nodoc:
147
- parent_path, keywords = nil, parent_path if parent_path.is_a?(Hash)
165
+ try_modify_attribute!(:operations) do
166
+ parent_path, keywords = nil, parent_path if parent_path.is_a?(Hash)
148
167
 
149
- name = name.nil? ? default_operation_name : name.to_s
150
- parent_path ||= default_operation_name unless keywords[:path].present?
168
+ name = name.nil? ? default_operation_name : name.to_s
169
+ parent_path ||= default_operation_name unless keywords[:path].present?
151
170
 
152
- (@operations ||= {})[name] = Operation.new(name, parent_path, keywords)
171
+ (@operations ||= {})[name] = Operation.new(name, parent_path, keywords)
172
+ end
153
173
  end
154
174
 
155
175
  def add_parameter(name, keywords = {}) # :nodoc:
156
- name = name.to_s
176
+ try_modify_attribute!(:parameters) do
177
+ name = name.to_s
157
178
 
158
- Parameter.new(name, keywords).tap do |parameter|
159
- (@parameters ||= {})[name] = parameter
160
- attribute_changed(:parameters)
179
+ (@parameters ||= {})[name] = Parameter.new(name, keywords)
161
180
  end
162
181
  end
163
182
 
164
183
  def add_path(name, keywords = {}) # :nodoc:
165
- pathname = Pathname.from(name)
166
- (@paths ||= {})[pathname] ||= Path.new(pathname, self, keywords)
184
+ try_modify_attribute!(:paths) do
185
+ pathname = Pathname.from(name)
186
+
187
+ (@paths ||= {})[pathname] = Path.new(pathname, self, keywords)
188
+ end
167
189
  end
168
190
 
169
191
  # Returns an array containing itself and all of the +Definitions+ instances
170
- # inherited/included.
192
+ # inherited or included.
171
193
  def ancestors
172
194
  @ancestors ||= [self].tap do |ancestors|
173
195
  [@included_definitions, @parent].flatten.each do |definitions|
@@ -176,37 +198,145 @@ module Jsapi
176
198
  end.uniq
177
199
  end
178
200
 
201
+ ##
202
+ # :method: common_description
203
+ # :args: pathname
204
+ # Returns the most accurate description for the specified path.
205
+
206
+ ##
207
+ # :method: common_model
208
+ # :args: pathname
209
+ # Returns the common model of all operations in the specified path.
210
+
211
+ ##
212
+ # :method: common_response_body
213
+ # :args: pathname
214
+ # Returns the common request body of all operations in the specified path.
215
+
216
+ ##
217
+ # :method: common_servers
218
+ # :args: pathname
219
+ # Returns the most accurate servers for the specified path.
220
+
221
+ ##
222
+ # :method: common_summary
223
+ # :args: pathname
224
+ # Returns the most accurate summary for the specified path.
225
+
226
+ %i[description model request_body servers summary].each do |name|
227
+ define_method(:"common_#{name}") do |arg|
228
+ arg = Pathname.from(arg || '')
229
+
230
+ cache_path_attribute(arg, name) do
231
+ arg.ancestors.lazy.filter_map do |pathname|
232
+ ancestors.lazy.filter_map do |definitions|
233
+ definitions.path(pathname)&.public_send(name).presence
234
+ end.first
235
+ end.first
236
+ end
237
+ end
238
+ end
239
+
240
+ ##
241
+ # :method: common_parameters
242
+ # :args: pathname
243
+ # Returns the parameters that apply to all operations in the
244
+ # specified path.
245
+
246
+ ##
247
+ # :method: common_responses
248
+ # :args: pathname
249
+ # Returns the responses that can be produced by all operations in the
250
+ # specified path.
251
+
252
+ %i[parameters responses].each do |name|
253
+ define_method(:"common_#{name}") do |arg|
254
+ arg = Pathname.from(arg || '')
255
+
256
+ cache_path_attribute(arg, name) do
257
+ arg.ancestors.flat_map do |pathname|
258
+ ancestors.filter_map do |definitions|
259
+ definitions.path(pathname)&.send(name)
260
+ end
261
+ end.reduce({}, &:reverse_merge).presence
262
+ end
263
+ end
264
+ end
265
+
266
+ ##
267
+ # :method: common_security_requirements
268
+ # :args: pathname
269
+ # Returns the security requirements that apply to all operations in
270
+ # the specified path.
271
+
272
+ ##
273
+ # :method: common_tags
274
+ # :args: pathname
275
+ # Returns the tags that apply to all operations in the specified path.
276
+
277
+ %i[security_requirements tags].each do |name|
278
+ define_method(:"common_#{name}") do |arg|
279
+ arg = Pathname.from(arg || '')
280
+
281
+ cache_path_attribute(arg, name) do
282
+ arg.ancestors.filter_map do |pathname|
283
+ ancestors.filter_map do |definitions|
284
+ definitions.path(pathname)&.send(name)
285
+ end
286
+ end.flatten.uniq.presence
287
+ end
288
+ end
289
+ end
290
+
179
291
  # Returns the default value for +type+ within +context+.
180
292
  def default_value(type, context: nil)
181
- objects.dig(:defaults, type.to_s)&.value(context: context)
293
+ cached_attributes.dig(:defaults, type.to_s)&.value(context: context)
294
+ end
295
+
296
+ # The security requirements that apply by default to all operations.
297
+ def default_security_requirements
298
+ cached_attributes[:security_requirements]
182
299
  end
183
300
 
184
301
  # Returns the operation with the specified name.
185
302
  def find_operation(name = nil)
186
- return objects.dig(:operations, name.to_s) if name.present?
187
-
188
- # Return the one and only operation
189
- operations.values.first if operations.one?
303
+ name = name&.to_s
304
+
305
+ cache_operation(name) do
306
+ if name.present?
307
+ # Select the operation with the given name
308
+ cached_attributes.dig(:operations, name)
309
+ elsif operations.one?
310
+ # Select the one and only operation
311
+ operations.values.first
312
+ end
313
+ end
190
314
  end
191
315
 
316
+ ##
317
+ # :method: find_parameter
192
318
  # Returns the reusable parameter with the specified name.
193
- def find_parameter(name)
194
- objects.dig(:parameters, name&.to_s)
195
- end
196
319
 
320
+ ##
321
+ # :method: find_request_body
197
322
  # Returns the reusable request body with the specified name.
198
- def find_request_body(name)
199
- objects.dig(:request_bodies, name&.to_s)
200
- end
201
323
 
324
+ ##
325
+ # :method: find_response
202
326
  # Returns the reusable response with the specified name.
203
- def find_response(name)
204
- objects.dig(:responses, name&.to_s)
205
- end
206
327
 
328
+ ##
329
+ # :method: find_schema
207
330
  # Returns the reusable schema with the specified name.
208
- def find_schema(name)
209
- objects.dig(:schemas, name&.to_s)
331
+
332
+ ##
333
+ # :method: find_security_scheme
334
+ # Returns the security scheme with the specified name.
335
+
336
+ %i[parameters request_bodies responses schemas security_schemes].each do |attribute_name|
337
+ define_method(:"find_#{attribute_name.to_s.singularize}") do |name|
338
+ cached_attributes.dig(attribute_name, name&.to_s)
339
+ end
210
340
  end
211
341
 
212
342
  # Includes +definitions+.
@@ -224,26 +354,44 @@ module Jsapi
224
354
  self
225
355
  end
226
356
 
227
- # Resets the memoized parameters for the given path.
228
- def invalidate_path_parameters(pathname)
357
+ # Invalidates cached ancestors.
358
+ def invalidate_ancestors
359
+ @ancestors = nil
360
+ @cache = nil
361
+ each_descendant(&:invalidate_ancestors)
362
+ end
363
+
364
+ # Invalidates cached attributes.
365
+ def invalidate_attributes
366
+ @cache = nil
367
+ each_descendant(&:invalidate_attributes)
368
+ end
369
+
370
+ # Invalidates the given path attribute.
371
+ def invalidate_path_attribute(pathname, name)
229
372
  pathname = Pathname.from(pathname)
373
+ name = name.to_sym
230
374
 
231
- @path_parameters&.delete(pathname)
232
- each_descendant { |descendant| descendant.invalidate_path_parameters(pathname) }
375
+ cached_path_attributes.fetch(pathname, nil)&.delete(name)
376
+ @cache[:operations] = nil
377
+
378
+ each_descendant do |descendant|
379
+ descendant.invalidate_path_attribute(pathname, name)
380
+ end
233
381
  end
234
382
 
235
383
  # Returns a hash representing the \JSON \Schema document for +name+.
236
384
  def json_schema_document(name)
237
385
  find_schema(name)&.to_json_schema&.tap do |json_schema_document|
238
- if (schemas = objects[:schemas].except(name.to_s)).any?
386
+ if (schemas = cached_attributes[:schemas].except(name.to_s)).any?
239
387
  json_schema_document[:definitions] = schemas.transform_values(&:to_json_schema)
240
388
  end
241
- end
389
+ end&.as_json
242
390
  end
243
391
 
244
392
  # Returns the methods or procs to be called when rescuing an exception.
245
393
  def on_rescue_callbacks
246
- objects[:on_rescues]
394
+ cached_attributes[:on_rescues]
247
395
  end
248
396
 
249
397
  # Returns a hash representing the \OpenAPI document for +version+.
@@ -251,17 +399,17 @@ module Jsapi
251
399
  # Raises an +ArgumentError+ if +version+ is not supported.
252
400
  def openapi_document(version = nil)
253
401
  version = OpenAPI::Version.from(version)
254
- operations = objects[:operations].values
402
+ operations = cached_attributes[:operations].values
255
403
 
256
404
  openapi_paths = operations.group_by(&:full_path).to_h do |key, value|
257
405
  [
258
- key.to_s,
406
+ key,
259
407
  OpenAPI::PathItem.new(
260
408
  value,
261
- description: path_description(key),
262
- parameters: path_parameters(key),
263
- summary: path_summary(key),
264
- servers: path_servers(key)
409
+ description: common_description(key),
410
+ parameters: common_parameters(key),
411
+ summary: common_summary(key),
412
+ servers: common_servers(key)
265
413
  ).to_openapi(version, self)
266
414
  ]
267
415
  end.presence
@@ -274,25 +422,40 @@ module Jsapi
274
422
  else
275
423
  %i[callbacks examples headers links request_bodies servers]
276
424
  end
277
- ).index_with { |key| object_to_openapi(objects[key], version).presence }
425
+ ).index_with do |key|
426
+ value = cached_attributes[key]
427
+ if key == :responses
428
+ value = value.reject do |_name, response|
429
+ response.resolve(self).nodoc?
430
+ end
431
+ end
432
+ object_to_openapi(value, version).presence
433
+ end
278
434
 
279
435
  with_openapi_extensions(
280
436
  if version == OpenAPI::V2_0
281
- openapi_server = objects[:servers].first || default_server
437
+ openapi_server = cached_attributes[:servers].first || default_server
282
438
  uri = URI(openapi_server.url) if openapi_server
283
439
  {
284
440
  # Order according to the OpenAPI specification 2.x
285
441
  swagger: '2.0',
286
442
  info: openapi_objects[:info],
287
443
  host: openapi_objects[:host] || uri&.hostname,
288
- basePath: openapi_objects[:base_path]&.to_s || uri&.path,
444
+ basePath: openapi_objects[:base_path] || uri&.path,
289
445
  schemes: openapi_objects[:schemes] || Array(uri&.scheme).presence,
290
- consumes: operations.filter_map do |operation|
291
- operation.consumes(self)
292
- end.uniq.sort.presence,
293
- produces: operations.flat_map do |operation|
294
- operation.produces(self).map(&:to_s)
295
- end.uniq.sort.presence,
446
+ consumes:
447
+ Media::Range.reduce(
448
+ operations.filter_map do |operation|
449
+ operation.request_body&.resolve(self)&.default_media_range
450
+ end
451
+ ).presence,
452
+ produces:
453
+ operations.flat_map do |operation|
454
+ operation.responses.values.filter_map do |response|
455
+ response = response.resolve(self)
456
+ response.default_media_type unless response.nodoc?
457
+ end
458
+ end.uniq.sort.presence,
296
459
  paths: openapi_paths,
297
460
  definitions: openapi_objects[:schemas],
298
461
  parameters: openapi_objects[:parameters],
@@ -302,7 +465,7 @@ module Jsapi
302
465
  else
303
466
  {
304
467
  # Order according to the OpenAPI specification 3.x
305
- openapi: version.to_s,
468
+ openapi: version,
306
469
  info: openapi_objects[:info],
307
470
  servers:
308
471
  openapi_objects[:servers] ||
@@ -325,50 +488,13 @@ module Jsapi
325
488
  tags: openapi_objects[:tags],
326
489
  externalDocs: openapi_objects[:external_docs]
327
490
  ).compact
328
- )
329
- end
330
-
331
- ##
332
- # :method: path_description
333
- # :args: pathname
334
- # Returns the most accurate description for the given path.
335
-
336
- ##
337
- # :method: path_servers
338
- # :args: pathname
339
- # Returns the most accurate Server objects for the given path.
340
-
341
- ##
342
- # :method: path_summary
343
- # :args: pathname
344
- # Returns the most accurate summary for the given path.
345
-
346
- %i[description servers summary].each do |name|
347
- define_method(:"path_#{name}") do |arg|
348
- Pathname.from(arg).ancestors.each do |pathname|
349
- ancestors.each do |ancestor|
350
- value = ancestor.path(pathname)&.public_send(name)
351
- return value if value.present?
352
- end
353
- end
354
- nil
355
- end
356
- end
357
-
358
- # Returns a hash containing the Parameter objects that are applicable to all
359
- # operations in the given path.
360
- # :args: pathname
361
- def path_parameters(arg)
362
- arg = Pathname.from(arg || '')
363
-
364
- (@path_parameters ||= {})[arg] ||= arg.ancestors.flat_map do |pathname|
365
- ancestors.filter_map { |ancestor| ancestor.path(pathname)&.parameters }
366
- end.reduce(&:reverse_merge) || {}
491
+ ).as_json
367
492
  end
368
493
 
369
- # Returns the first RescueHandler to handle +exception+, or nil if no one could be found.
494
+ # Returns the first RescueHandler capable to handle +exception+, or nil
495
+ # if no one could be found.
370
496
  def rescue_handler_for(exception)
371
- objects[:rescue_handlers].find { |r| r.match?(exception) }
497
+ cached_attributes[:rescue_handlers].find { |r| r.match?(exception) }
372
498
  end
373
499
 
374
500
  protected
@@ -383,7 +509,8 @@ module Jsapi
383
509
  attr_reader :included_definitions
384
510
 
385
511
  def attribute_changed(*) # :nodoc:
386
- invalidate_objects
512
+ invalidate_attributes
513
+ super
387
514
  end
388
515
 
389
516
  # Invoked whenever it is included in another +Definitions+ instance.
@@ -400,21 +527,63 @@ module Jsapi
400
527
 
401
528
  # rubocop:enable Lint/MissingSuper
402
529
 
403
- # Invalidates cached ancestors.
404
- def invalidate_ancestors
405
- @ancestors = nil
406
- @objects = nil
407
- @path_parameters = nil
408
- each_descendant(&:invalidate_ancestors)
530
+ private
531
+
532
+ def cache
533
+ @cache ||= {}
409
534
  end
410
535
 
411
- # Invalidates cached objects.
412
- def invalidate_objects
413
- @objects = nil
414
- each_descendant(&:invalidate_objects)
536
+ def cached_attributes
537
+ cache[:attributes] ||= ancestors.each_with_object({}) do |ancestor, attr|
538
+ self.class.attribute_names.each do |key|
539
+ case value = ancestor.send(key)
540
+ when Array
541
+ (attr[key] ||= []).push(*value)
542
+ when Hash
543
+ if (hash = attr[key])
544
+ value.each { |k, v| hash[k] = v unless hash.key?(k) }
545
+ else
546
+ attr[key] = value.dup
547
+ end
548
+ else
549
+ attr[key] ||= value
550
+ end
551
+ end
552
+ end
415
553
  end
416
554
 
417
- private
555
+ def cached_operations
556
+ cache[:operations] ||= {}
557
+ end
558
+
559
+ def cached_path_attributes
560
+ cache[:path_attributes] ||=
561
+ cached_attributes[:operations]
562
+ .values
563
+ .map(&:full_path)
564
+ .flat_map(&:ancestors)
565
+ .uniq
566
+ .to_h { |pathname| [pathname, {}] }
567
+ end
568
+
569
+ def cache_operation(name)
570
+ operation = cached_operations[name]
571
+ return operation if operation
572
+
573
+ operation = yield
574
+ cached_operations[name] = Operation.wrap(operation, self) if operation
575
+ end
576
+
577
+ def cache_path_attribute(pathname, name)
578
+ path_attributes = cached_path_attributes[pathname]
579
+ return path_attributes[name] if path_attributes&.key?(name)
580
+
581
+ value = yield
582
+ return unless value || path_attributes
583
+
584
+ path_attributes ||= cached_path_attributes[pathname] = {}
585
+ path_attributes[name] = value
586
+ end
418
587
 
419
588
  def circular_dependency?(other)
420
589
  return true if other == self
@@ -446,25 +615,6 @@ module Jsapi
446
615
  nil
447
616
  end
448
617
 
449
- def objects
450
- @objects ||= ancestors.each_with_object({}) do |ancestor, objects|
451
- self.class.attribute_names.each do |key|
452
- case value = ancestor.send(key)
453
- when Array
454
- (objects[key] ||= []).push(*value)
455
- when Hash
456
- if (hash = objects[key])
457
- value.each { |k, v| hash[k] = v unless hash.key?(k) }
458
- else
459
- objects[key] = value.dup
460
- end
461
- else
462
- objects[key] ||= value
463
- end
464
- end
465
- end
466
- end
467
-
468
618
  def object_to_openapi(object, version)
469
619
  case object
470
620
  when Array