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.
Files changed (113) 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 +209 -116
  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 +102 -0
  35. data/lib/jsapi/media/type.rb +70 -0
  36. data/lib/jsapi/media/type_and_subtype.rb +38 -0
  37. data/lib/jsapi/media.rb +9 -0
  38. data/lib/jsapi/messages.rb +19 -0
  39. data/lib/jsapi/meta/callback/base.rb +63 -8
  40. data/lib/jsapi/meta/content.rb +59 -0
  41. data/lib/jsapi/meta/definitions.rb +299 -153
  42. data/lib/jsapi/meta/example/base.rb +41 -8
  43. data/lib/jsapi/meta/existence.rb +4 -2
  44. data/lib/jsapi/meta/header/base.rb +4 -2
  45. data/lib/jsapi/meta/info.rb +3 -1
  46. data/lib/jsapi/meta/license.rb +11 -5
  47. data/lib/jsapi/meta/model/attributes/class_methods.rb +150 -0
  48. data/lib/jsapi/meta/model/attributes/frozen_error.rb +16 -0
  49. data/lib/jsapi/meta/model/attributes/type_caster.rb +56 -0
  50. data/lib/jsapi/meta/model/attributes.rb +24 -118
  51. data/lib/jsapi/meta/model/base.rb +2 -5
  52. data/lib/jsapi/meta/model/reference.rb +46 -10
  53. data/lib/jsapi/meta/model/wrappable.rb +23 -0
  54. data/lib/jsapi/meta/model/wrapper.rb +26 -0
  55. data/lib/jsapi/meta/model.rb +2 -1
  56. data/lib/jsapi/meta/oauth_flow.rb +1 -1
  57. data/lib/jsapi/meta/openapi/extensions.rb +5 -6
  58. data/lib/jsapi/meta/openapi/version.rb +16 -4
  59. data/lib/jsapi/meta/operation.rb +177 -71
  60. data/lib/jsapi/meta/parameter/base.rb +10 -6
  61. data/lib/jsapi/meta/parameter/wrapper.rb +13 -0
  62. data/lib/jsapi/meta/parameter.rb +3 -0
  63. data/lib/jsapi/meta/path.rb +59 -13
  64. data/lib/jsapi/meta/pathname.rb +6 -3
  65. data/lib/jsapi/meta/property.rb +10 -0
  66. data/lib/jsapi/meta/request_body/base.rb +69 -32
  67. data/lib/jsapi/meta/request_body/wrapper.rb +13 -0
  68. data/lib/jsapi/meta/request_body.rb +3 -0
  69. data/lib/jsapi/meta/rescue_handler.rb +18 -17
  70. data/lib/jsapi/meta/response/base.rb +82 -58
  71. data/lib/jsapi/meta/response/reference.rb +11 -1
  72. data/lib/jsapi/meta/response/wrapper.rb +26 -0
  73. data/lib/jsapi/meta/response.rb +3 -0
  74. data/lib/jsapi/meta/schema/additional_properties.rb +8 -0
  75. data/lib/jsapi/meta/schema/array.rb +20 -8
  76. data/lib/jsapi/meta/schema/base.rb +10 -9
  77. data/lib/jsapi/meta/schema/boundary.rb +1 -0
  78. data/lib/jsapi/meta/schema/numeric.rb +26 -20
  79. data/lib/jsapi/meta/schema/object.rb +60 -44
  80. data/lib/jsapi/meta/schema/reference.rb +1 -8
  81. data/lib/jsapi/meta/schema/string.rb +12 -6
  82. data/lib/jsapi/meta/schema/wrapper.rb +31 -0
  83. data/lib/jsapi/meta/schema.rb +22 -9
  84. data/lib/jsapi/meta/security_requirement.rb +2 -2
  85. data/lib/jsapi/meta/security_scheme/api_key.rb +5 -2
  86. data/lib/jsapi/meta/security_scheme/base.rb +7 -5
  87. data/lib/jsapi/meta/security_scheme/http/basic.rb +5 -7
  88. data/lib/jsapi/meta/security_scheme/http/bearer.rb +5 -5
  89. data/lib/jsapi/meta/security_scheme/http/other.rb +1 -3
  90. data/lib/jsapi/meta/security_scheme/mutual_tls.rb +1 -3
  91. data/lib/jsapi/meta/security_scheme/oauth2.rb +18 -13
  92. data/lib/jsapi/meta/security_scheme/open_id_connect.rb +4 -4
  93. data/lib/jsapi/meta/security_scheme.rb +4 -4
  94. data/lib/jsapi/meta/server.rb +4 -2
  95. data/lib/jsapi/meta/tag.rb +9 -3
  96. data/lib/jsapi/meta.rb +2 -1
  97. data/lib/jsapi/model/base.rb +1 -1
  98. data/lib/jsapi/status/base.rb +35 -0
  99. data/lib/jsapi/status/code.rb +113 -0
  100. data/lib/jsapi/status/default.rb +16 -0
  101. data/lib/jsapi/status/range.rb +35 -0
  102. data/lib/jsapi/status.rb +37 -0
  103. data/lib/jsapi/version.rb +1 -1
  104. data/lib/jsapi.rb +3 -3
  105. metadata +36 -10
  106. data/lib/jsapi/controller/parameters_invalid.rb +0 -27
  107. data/lib/jsapi/dsl/callback.rb +0 -21
  108. data/lib/jsapi/dsl/error.rb +0 -36
  109. data/lib/jsapi/invalid_argument_error.rb +0 -12
  110. data/lib/jsapi/invalid_value_error.rb +0 -12
  111. data/lib/jsapi/invalid_value_helper.rb +0 -17
  112. data/lib/jsapi/meta/model/type_caster.rb +0 -50
  113. 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
374
+
375
+ cached_path_attributes.fetch(pathname, nil)&.delete(name)
376
+ @cache[:operations] = nil
230
377
 
231
- @path_parameters&.delete(pathname)
232
- each_descendant { |descendant| descendant.invalidate_path_parameters(pathname) }
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,47 +399,63 @@ 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
268
416
 
269
- openapi_objects =
270
- if version.major == 2
271
- %i[base_path external_docs info host parameters responses parameters schemas
272
- schemes security_requirements security_schemes tags]
417
+ openapi_objects = (
418
+ %i[external_docs info parameters responses schemas
419
+ security_requirements security_schemes tags] +
420
+ if version == OpenAPI::V2_0
421
+ %i[base_path host schemes]
273
422
  else
274
- %i[callbacks examples external_docs headers info links parameters request_bodies
275
- responses schemas security_requirements security_schemes servers tags]
276
- end.to_h { |key| [key, object_to_openapi(objects[key], version).presence] }
423
+ %i[callbacks examples headers links request_bodies servers]
424
+ end
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
277
434
 
278
435
  with_openapi_extensions(
279
- if version.major == 2
280
- openapi_server = objects[:servers].first || default_server
436
+ if version == OpenAPI::V2_0
437
+ openapi_server = cached_attributes[:servers].first || default_server
281
438
  uri = URI(openapi_server.url) if openapi_server
282
439
  {
283
440
  # Order according to the OpenAPI specification 2.x
284
441
  swagger: '2.0',
285
442
  info: openapi_objects[:info],
286
443
  host: openapi_objects[:host] || uri&.hostname,
287
- basePath: openapi_objects[:base_path]&.to_s || uri&.path,
444
+ basePath: openapi_objects[:base_path] || uri&.path,
288
445
  schemes: openapi_objects[:schemes] || Array(uri&.scheme).presence,
289
- consumes: operations.filter_map do |operation|
290
- operation.consumes(self)
291
- end.uniq.sort.presence,
292
- produces: operations.flat_map do |operation|
293
- operation.produces(self)
294
- 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,
295
459
  paths: openapi_paths,
296
460
  definitions: openapi_objects[:schemas],
297
461
  parameters: openapi_objects[:parameters],
@@ -301,12 +465,7 @@ module Jsapi
301
465
  else
302
466
  {
303
467
  # Order according to the OpenAPI specification 3.x
304
- openapi:
305
- case version.minor
306
- when 0 then '3.0.3'
307
- when 1 then '3.1.1'
308
- when 2 then '3.2.0'
309
- end,
468
+ openapi: version,
310
469
  info: openapi_objects[:info],
311
470
  servers:
312
471
  openapi_objects[:servers] ||
@@ -329,50 +488,13 @@ module Jsapi
329
488
  tags: openapi_objects[:tags],
330
489
  externalDocs: openapi_objects[:external_docs]
331
490
  ).compact
332
- )
333
- end
334
-
335
- ##
336
- # :method: path_description
337
- # :args: pathname
338
- # Returns the most accurate description for the given path.
339
-
340
- ##
341
- # :method: path_servers
342
- # :args: pathname
343
- # Returns the most accurate Server objects for the given path.
344
-
345
- ##
346
- # :method: path_summary
347
- # :args: pathname
348
- # Returns the most accurate summary for the given path.
349
-
350
- %i[description servers summary].each do |name|
351
- define_method(:"path_#{name}") do |arg|
352
- Pathname.from(arg).ancestors.each do |pathname|
353
- ancestors.each do |ancestor|
354
- value = ancestor.path(pathname)&.public_send(name)
355
- return value if value.present?
356
- end
357
- end
358
- nil
359
- end
360
- end
361
-
362
- # Returns a hash containing the Parameter objects that are applicable to all
363
- # operations in the given path.
364
- # :args: pathname
365
- def path_parameters(arg)
366
- arg = Pathname.from(arg || '')
367
-
368
- (@path_parameters ||= {})[arg] ||= arg.ancestors.flat_map do |pathname|
369
- ancestors.filter_map { |ancestor| ancestor.path(pathname)&.parameters }
370
- end.reduce(&:reverse_merge) || {}
491
+ ).as_json
371
492
  end
372
493
 
373
- # 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.
374
496
  def rescue_handler_for(exception)
375
- objects[:rescue_handlers].find { |r| r.match?(exception) }
497
+ cached_attributes[:rescue_handlers].find { |r| r.match?(exception) }
376
498
  end
377
499
 
378
500
  protected
@@ -387,7 +509,8 @@ module Jsapi
387
509
  attr_reader :included_definitions
388
510
 
389
511
  def attribute_changed(*) # :nodoc:
390
- invalidate_objects
512
+ invalidate_attributes
513
+ super
391
514
  end
392
515
 
393
516
  # Invoked whenever it is included in another +Definitions+ instance.
@@ -404,21 +527,63 @@ module Jsapi
404
527
 
405
528
  # rubocop:enable Lint/MissingSuper
406
529
 
407
- # Invalidates cached ancestors.
408
- def invalidate_ancestors
409
- @ancestors = nil
410
- @objects = nil
411
- @path_parameters = nil
412
- each_descendant(&:invalidate_ancestors)
530
+ private
531
+
532
+ def cache
533
+ @cache ||= {}
413
534
  end
414
535
 
415
- # Invalidates cached objects.
416
- def invalidate_objects
417
- @objects = nil
418
- 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
419
553
  end
420
554
 
421
- 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
422
587
 
423
588
  def circular_dependency?(other)
424
589
  return true if other == self
@@ -450,25 +615,6 @@ module Jsapi
450
615
  nil
451
616
  end
452
617
 
453
- def objects
454
- @objects ||= ancestors.each_with_object({}) do |ancestor, objects|
455
- self.class.attribute_names.each do |key|
456
- case value = ancestor.send(key)
457
- when Array
458
- (objects[key] ||= []).push(*value)
459
- when Hash
460
- if (hash = objects[key])
461
- value.each { |k, v| hash[k] = v unless hash.key?(k) }
462
- else
463
- objects[key] = value.dup
464
- end
465
- else
466
- objects[key] ||= value
467
- end
468
- end
469
- end
470
- end
471
-
472
618
  def object_to_openapi(object, version)
473
619
  case object
474
620
  when Array