jsapi 1.3 → 1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2abec73a53f67bfebe51e0466e662be1dfc393f3fe5946612a12c94fde8b463b
4
- data.tar.gz: f9c69c6df40d4f88e5175beb185a528ad76f2c6b4cbf6a8f88ac15915943f706
3
+ metadata.gz: 87f305e41facfb84c96e5e2406b636aa9e10a27545a25d42a5029f4d4dd93488
4
+ data.tar.gz: 6723179cd4981c590a4e8f0c45808d9b69a0436be48e1e89f06c7ef0a4b56ddb
5
5
  SHA512:
6
- metadata.gz: ca369ab50a38016fa37e9199e5a38f9248d2cbcfa51d80c39461dd206d8b09fb6ae60ea29861d211b9a939e408b6e48f35008da577c9f3a789bb6ebb592fafda
7
- data.tar.gz: 8093446b1d7601d9a0e89cae395fa0ced837901b1a9eb0866ebcfd721cebb9e07d611c851735c847a699d71cec4ca474d04e39710ebc244476f6deb635e7fdc7
6
+ metadata.gz: 7ddefbaaa678ed70e29055dbe4452ee0f4f738fa2386e8e3f5dbaa42e682ca973fa0bd04e5c6b22b2570bfd290fd4fcfbb42c8f7e50650ffda50d0cfbb160cc7
7
+ data.tar.gz: 3fc210ea9c236f319f1c94dcb741d7d910eb7edb90020aafcbbee2a033bd232a9bb88dd26fc306869f7c3be715487f4655a517e5968f63461a6bbad57074099a
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cgi'
4
-
5
3
  module Jsapi
6
4
  module Controller
7
5
  # Used to wrap request parameters.
@@ -24,9 +22,7 @@ module Jsapi
24
22
  @raw_attributes = {}
25
23
 
26
24
  # Parameters
27
- operation.parameters.each do |name, parameter_model|
28
- parameter_model = parameter_model.resolve(definitions)
29
-
25
+ operation.resolved_parameters(definitions).each do |name, parameter_model|
30
26
  @raw_attributes[name] = JSON.wrap(
31
27
  case parameter_model.in
32
28
  when 'header'
@@ -147,6 +147,17 @@ module Jsapi
147
147
  api_definitions { parameter(name, **keywords, &block) }
148
148
  end
149
149
 
150
+ # Groups operations by path.
151
+ #
152
+ # api_path 'api' do
153
+ # operation 'foo'
154
+ # operation 'bar'
155
+ # end
156
+ #
157
+ def api_path(name, &block)
158
+ api_definitions { path(name, &block) }
159
+ end
160
+
150
161
  # Defines a reusable request body.
151
162
  #
152
163
  # api_request_body 'foo', type: 'string'
@@ -155,6 +155,20 @@ module Jsapi
155
155
  end
156
156
  end
157
157
 
158
+ # Groups operations by path.
159
+ #
160
+ # path 'api' do
161
+ # operation 'foo'
162
+ # operation 'bar'
163
+ # end
164
+ #
165
+ def path(name = nil, &block)
166
+ define('path', name&.inspect) do
167
+ path_model = @meta_model.add_path(name)
168
+ Path.new(path_model, &block) if block
169
+ end
170
+ end
171
+
158
172
  # Specifies a reusable request body.
159
173
  #
160
174
  # request_body 'foo', type: 'string'
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module DSL
5
+ class Path < Base
6
+ # Specifies an operation within the current path.
7
+ #
8
+ # operation 'foo' do
9
+ # parameter 'bar', type: 'string'
10
+ # response do
11
+ # property 'foo', type: 'string'
12
+ # end
13
+ # end
14
+ #
15
+ def operation(name = nil, **keywords, &block)
16
+ define('operation', name&.inspect) do
17
+ operation_model = @meta_model.owner.add_operation(name, @meta_model.name, keywords)
18
+ Operation.new(operation_model, &block) if block
19
+ end
20
+ end
21
+
22
+ # Specifies a parameter applicable for all operations in this path.
23
+ #
24
+ # parameter 'foo', type: 'string'
25
+ #
26
+ # See Meta::Path#parameters for further information.
27
+ def parameter(name, **keywords, &block)
28
+ define('parameter', name.inspect) do
29
+ parameter_model = @meta_model.add_parameter(name, keywords)
30
+ Parameter.new(parameter_model, &block) if block
31
+ end
32
+ end
33
+
34
+ # Specifies a nested path.
35
+ def path(name = nil, &block)
36
+ define('path', name&.inspect) do
37
+ path_model = @meta_model.owner.add_path(@meta_model.name + name.to_s)
38
+ Path.new(path_model, &block) if block
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/jsapi/dsl.rb CHANGED
@@ -9,6 +9,7 @@ require_relative 'dsl/request_body'
9
9
  require_relative 'dsl/response'
10
10
  require_relative 'dsl/callback'
11
11
  require_relative 'dsl/operation'
12
+ require_relative 'dsl/path'
12
13
  require_relative 'dsl/definitions'
13
14
  require_relative 'dsl/class_methods'
14
15
 
@@ -8,7 +8,7 @@ module Jsapi
8
8
  ##
9
9
  # :attr: base_path
10
10
  # The base path of the API. Applies to \OpenAPI 2.0.
11
- attribute :base_path, String
11
+ attribute :base_path, Pathname
12
12
 
13
13
  ##
14
14
  # :attr: callbacks
@@ -65,6 +65,11 @@ module Jsapi
65
65
  # The reusable Parameter objects.
66
66
  attribute :parameters, { String => Parameter }, accessors: %i[reader writer]
67
67
 
68
+ ##
69
+ # :attr: paths
70
+ # The Path objects.
71
+ attribute :paths, { Pathname => Path }, accessors: %i[reader writer]
72
+
68
73
  ##
69
74
  # :attr: rescue_handlers
70
75
  # The RescueHandler objects.
@@ -138,15 +143,27 @@ module Jsapi
138
143
  @parent&.inherited(self)
139
144
  end
140
145
 
141
- def add_operation(name = nil, keywords = {}) # :nodoc:
146
+ def add_operation(name, parent_path = nil, keywords = {}) # :nodoc:
147
+ parent_path, keywords = nil, parent_path if parent_path.is_a?(Hash)
148
+
142
149
  name = name.nil? ? default_operation_name : name.to_s
143
- keywords = keywords.reverse_merge(path: default_operation_path)
144
- (@operations ||= {})[name] = Operation.new(name, keywords)
150
+ parent_path ||= default_operation_name unless keywords[:path].present?
151
+
152
+ (@operations ||= {})[name] = Operation.new(name, parent_path, keywords)
145
153
  end
146
154
 
147
155
  def add_parameter(name, keywords = {}) # :nodoc:
148
156
  name = name.to_s
149
- (@parameters ||= {})[name] = Parameter.new(name, keywords)
157
+
158
+ Parameter.new(name, keywords).tap do |parameter|
159
+ (@parameters ||= {})[name] = parameter
160
+ attribute_changed(:parameters)
161
+ end
162
+ end
163
+
164
+ def add_path(name, keywords = {}) # :nodoc:
165
+ pathname = Pathname.from(name)
166
+ (@paths ||= {})[pathname] ||= Path.new(pathname, self, keywords)
150
167
  end
151
168
 
152
169
  # Returns an array containing itself and all of the +Definitions+ instances
@@ -207,6 +224,14 @@ module Jsapi
207
224
  self
208
225
  end
209
226
 
227
+ # Resets the memoized parameters for the given path.
228
+ def invalidate_path_parameters(pathname)
229
+ pathname = Pathname.from(pathname)
230
+
231
+ @path_parameters&.delete(pathname)
232
+ each_descendant { |descendant| descendant.invalidate_path_parameters(pathname) }
233
+ end
234
+
210
235
  # Returns a hash representing the \JSON \Schema document for +name+.
211
236
  def json_schema_document(name)
212
237
  find_schema(name)&.to_json_schema&.tap do |json_schema_document|
@@ -228,11 +253,18 @@ module Jsapi
228
253
  version = OpenAPI::Version.from(version)
229
254
  operations = objects[:operations].values
230
255
 
231
- openapi_paths =
232
- operations.group_by { |operation| operation.path || default_operation_path }
233
- .transform_values do |operations_by_path|
234
- OpenAPI::PathItem.new(operations_by_path).to_openapi(version, self)
235
- end.presence
256
+ openapi_paths = operations.group_by(&:full_path).to_h do |key, value|
257
+ [
258
+ key.to_s,
259
+ OpenAPI::PathItem.new(
260
+ value,
261
+ description: path_description(key),
262
+ parameters: path_parameters(key),
263
+ summary: path_summary(key),
264
+ servers: path_servers(key)
265
+ ).to_openapi(version, self)
266
+ ]
267
+ end.presence
236
268
 
237
269
  openapi_objects =
238
270
  if version.major == 2
@@ -252,7 +284,7 @@ module Jsapi
252
284
  swagger: '2.0',
253
285
  info: openapi_objects[:info],
254
286
  host: openapi_objects[:host] || uri&.hostname,
255
- basePath: openapi_objects[:base_path] || uri&.path,
287
+ basePath: openapi_objects[:base_path]&.to_s || uri&.path,
256
288
  schemes: openapi_objects[:schemes] || Array(uri&.scheme).presence,
257
289
  consumes: operations.filter_map do |operation|
258
290
  operation.consumes(self)
@@ -300,6 +332,44 @@ module Jsapi
300
332
  )
301
333
  end
302
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) || {}
371
+ end
372
+
303
373
  # Returns the first RescueHandler to handle +exception+, or nil if no one could be found.
304
374
  def rescue_handler_for(exception)
305
375
  objects[:rescue_handlers].find { |r| r.match?(exception) }
@@ -338,15 +408,14 @@ module Jsapi
338
408
  def invalidate_ancestors
339
409
  @ancestors = nil
340
410
  @objects = nil
341
- @children&.each(&:invalidate_ancestors)
342
- @dependent_definitions&.each(&:invalidate_ancestors)
411
+ @path_parameters = nil
412
+ each_descendant(&:invalidate_ancestors)
343
413
  end
344
414
 
345
415
  # Invalidates cached objects.
346
416
  def invalidate_objects
347
417
  @objects = nil
348
- @children&.each(&:invalidate_objects)
349
- @dependent_definitions&.each(&:invalidate_objects)
418
+ each_descendant(&:invalidate_objects)
350
419
  end
351
420
 
352
421
  private
@@ -365,10 +434,6 @@ module Jsapi
365
434
  end
366
435
  end
367
436
 
368
- def default_operation_path
369
- @default_operation_path ||= "/#{default_operation_name}"
370
- end
371
-
372
437
  def default_server
373
438
  @default_server ||=
374
439
  if (name = @owner.try(:name))
@@ -380,6 +445,11 @@ module Jsapi
380
445
  end
381
446
  end
382
447
 
448
+ def each_descendant(&block)
449
+ [*@children, *dependent_definitions].each(&block)
450
+ nil
451
+ end
452
+
383
453
  def objects
384
454
  @objects ||= ancestors.each_with_object({}) do |ancestor, objects|
385
455
  self.class.attribute_names.each do |key|
@@ -4,15 +4,25 @@ module Jsapi
4
4
  module Meta
5
5
  module OpenAPI
6
6
  class PathItem # :nodoc:
7
- def initialize(operations)
7
+ def initialize(operations, keywords = {})
8
8
  @operations = operations
9
+ @summary = keywords[:summary]
10
+ @description = keywords[:description]
11
+ @servers = keywords[:servers]
12
+ @parameters = keywords[:parameters]
9
13
  end
10
14
 
11
15
  def to_openapi(version, definitions)
12
16
  version = OpenAPI::Version.from(version)
13
17
 
14
18
  {}.tap do |fields|
15
- @operations.each do |operation|
19
+ if version >= OpenAPI::V3_0
20
+ fields[:summary] = @summary if @summary.present?
21
+ fields[:description] = @description if @description.present?
22
+ end
23
+
24
+ # Operations
25
+ @operations&.each do |operation|
16
26
  method = operation.method
17
27
  standardized_method = method.downcase
18
28
 
@@ -23,6 +33,18 @@ module Jsapi
23
33
  additional_operations[method] = operation.to_openapi(version, definitions)
24
34
  end
25
35
  end
36
+
37
+ # Servers
38
+ if version >= OpenAPI::V3_0 && @servers.present?
39
+ fields[:servers] = @servers.map { |server| server.to_openapi(version) }
40
+ end
41
+
42
+ # Parameters
43
+ if @parameters.present?
44
+ fields[:parameters] = @parameters.values.map do |parameter|
45
+ parameter.to_openapi(version, definitions)
46
+ end
47
+ end
26
48
  end
27
49
  end
28
50
 
@@ -46,10 +46,15 @@ module Jsapi
46
46
  # The parameters of the operation.
47
47
  attribute :parameters, { String => Parameter }, accessors: %i[reader writer]
48
48
 
49
+ ##
50
+ # :attr_reader: parent_path
51
+ # The parent path as a Pathname.
52
+ attribute :parent_path, Pathname, accessors: %i[reader]
53
+
49
54
  ##
50
55
  # :attr: path
51
- # The relative path of the operation.
52
- attribute :path, String
56
+ # The relative path of the operation as a Pathname.
57
+ attribute :path, Pathname
53
58
 
54
59
  ##
55
60
  # :attr: request_body
@@ -95,8 +100,11 @@ module Jsapi
95
100
  # The tags used to group operations in an \OpenAPI document.
96
101
  attribute :tags, [String]
97
102
 
98
- def initialize(name = nil, keywords = {})
103
+ def initialize(name, parent_path = nil, keywords = {})
104
+ parent_path, keywords = nil, parent_path if parent_path.is_a?(Hash)
105
+
99
106
  @name = name&.to_s
107
+ @parent_path = Pathname.from(parent_path)
100
108
  super(keywords)
101
109
  end
102
110
 
@@ -104,6 +112,11 @@ module Jsapi
104
112
  (@parameters ||= {})[name.to_s] = Parameter.new(name, keywords)
105
113
  end
106
114
 
115
+ # Returns the full path of the operation as a Pathname.
116
+ def full_path
117
+ parent_path + path
118
+ end
119
+
107
120
  # Returns the MIME type consumed by the operation.
108
121
  def consumes(definitions)
109
122
  request_body&.resolve(definitions)&.content_type
@@ -116,6 +129,13 @@ module Jsapi
116
129
  end.uniq.sort
117
130
  end
118
131
 
132
+ # Merges the parameters of this operation and the common parameters of all
133
+ # parent pathes and resolves them.
134
+ def resolved_parameters(definitions)
135
+ (definitions.path_parameters(full_path).presence&.merge(parameters) || parameters)
136
+ .transform_values { |parameter| parameter.resolve(definitions) }
137
+ end
138
+
119
139
  # Returns a hash representing the \OpenAPI operation object.
120
140
  def to_openapi(version, definitions)
121
141
  version = OpenAPI::Version.from(version)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Meta
5
+ # Specifies a path.
6
+ class Path < Model::Base
7
+ ##
8
+ # :attr: description
9
+ # The description that applies to all operations in this path.
10
+ # Applies to \OpenAPI 3.0 and higher.
11
+ attribute :description, String
12
+
13
+ ##
14
+ # :attr_reader: name
15
+ # The relative path as a Pathname.
16
+ attribute :name, Pathname, accessors: %i[reader]
17
+
18
+ ##
19
+ # :attr: parameters
20
+ # The Parameter objects applicable for all operations in this path.
21
+ attribute :parameters, { String => Parameter }, accessors: %i[reader writer]
22
+
23
+ ##
24
+ # :attr_reader: owner
25
+ attribute :owner, accessors: %i[reader]
26
+
27
+ ##
28
+ # :attr: summary
29
+ # The summary that applies to all operations in this path.
30
+ # Applies to \OpenAPI 3.0 and higher.
31
+ attribute :summary, String
32
+
33
+ ##
34
+ # :attr: servers
35
+ # The Server objects that applies to all operations in this path.
36
+ # Applies to \OpenAPI 3.0 and higher.
37
+ attribute :servers, [Server]
38
+
39
+ # Creates a new path with the given name and owner.
40
+ def initialize(name, owner, keywords = {})
41
+ @name = Pathname.from(name)
42
+ @owner = owner
43
+ super(keywords)
44
+ end
45
+
46
+ def add_parameter(name, keywords = {}) # :nodoc:
47
+ name = name.to_s
48
+
49
+ Parameter.new(name, keywords).tap do |parameter|
50
+ (@parameters ||= {})[name] = parameter
51
+ @owner.try(:invalidate_path_parameters, self.name)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Meta
5
+ # Represents a relative path name.
6
+ class Pathname
7
+ class << self
8
+ # Creates a Pathname from +name+.
9
+ def from(name)
10
+ return name if name.is_a?(Pathname)
11
+
12
+ name.nil? ? new : new(name)
13
+ end
14
+ end
15
+
16
+ attr_reader :segments
17
+
18
+ delegate :hash, to: :segments
19
+
20
+ def initialize(*segments) # :nodoc:
21
+ @segments = segments.flat_map do |segment|
22
+ segment = segment.to_s.delete_prefix('/')
23
+ segment.present? ? segment.split('/', -1) : ''
24
+ end
25
+ end
26
+
27
+ def ==(other) # :nodoc:
28
+ other.is_a?(Pathname) && segments == other.segments
29
+ end
30
+
31
+ alias eql? ==
32
+
33
+ # Creates a new Pathname by appending +other+ to +self+.
34
+ # Returns +self+ if +other+ is nil.
35
+ def +(other)
36
+ return self if other.nil?
37
+
38
+ Pathname.new(*@segments, *Pathname.from(other).segments)
39
+ end
40
+
41
+ # Returns an array containing itself and all parent pathnames.
42
+ def ancestors
43
+ @ancestors ||= @segments.count.downto(0).map do |i|
44
+ Pathname.new(*@segments[0, i])
45
+ end
46
+ end
47
+
48
+ def inspect # :nodoc:
49
+ "#<#{self.class} #{to_s.inspect}>"
50
+ end
51
+
52
+ # Returns the relative path name as a string.
53
+ def to_s
54
+ @to_s ||= @segments.presence&.each_with_index&.map do |segment, index|
55
+ index.zero? && segment.blank? ? '//' : "/#{segment}"
56
+ end&.join || '/'
57
+ end
58
+ end
59
+ end
60
+ end
data/lib/jsapi/meta.rb CHANGED
@@ -24,6 +24,8 @@ require_relative 'meta/property'
24
24
  require_relative 'meta/schema'
25
25
  require_relative 'meta/request_body'
26
26
  require_relative 'meta/parameter'
27
+ require_relative 'meta/pathname'
28
+ require_relative 'meta/path'
27
29
  require_relative 'meta/response'
28
30
  require_relative 'meta/operation'
29
31
  require_relative 'meta/rescue_handler'
data/lib/jsapi/version.rb CHANGED
@@ -5,6 +5,6 @@ module Jsapi
5
5
  # NOTE: See https://bundler.io/guides/creating_gem.html
6
6
 
7
7
  # The current GEM version.
8
- VERSION = '1.3'
8
+ VERSION = '1.4'
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsapi
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.3'
4
+ version: '1.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Göller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-02 00:00:00.000000000 Z
11
+ date: 2025-11-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: denis@dmgoeller.de
@@ -34,6 +34,7 @@ files:
34
34
  - lib/jsapi/dsl/examples.rb
35
35
  - lib/jsapi/dsl/operation.rb
36
36
  - lib/jsapi/dsl/parameter.rb
37
+ - lib/jsapi/dsl/path.rb
37
38
  - lib/jsapi/dsl/request_body.rb
38
39
  - lib/jsapi/dsl/response.rb
39
40
  - lib/jsapi/dsl/schema.rb
@@ -86,6 +87,8 @@ files:
86
87
  - lib/jsapi/meta/parameter.rb
87
88
  - lib/jsapi/meta/parameter/base.rb
88
89
  - lib/jsapi/meta/parameter/reference.rb
90
+ - lib/jsapi/meta/path.rb
91
+ - lib/jsapi/meta/pathname.rb
89
92
  - lib/jsapi/meta/property.rb
90
93
  - lib/jsapi/meta/reference_error.rb
91
94
  - lib/jsapi/meta/request_body.rb