jsapi 1.3 → 1.4.1
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/methods.rb +72 -96
- data/lib/jsapi/controller/parameters.rb +1 -5
- data/lib/jsapi/dsl/class_methods.rb +11 -0
- data/lib/jsapi/dsl/definitions.rb +14 -0
- data/lib/jsapi/dsl/path.rb +43 -0
- data/lib/jsapi/dsl.rb +1 -0
- data/lib/jsapi/media/range.rb +69 -0
- data/lib/jsapi/media/type.rb +66 -0
- data/lib/jsapi/media/type_and_subtype.rb +38 -0
- data/lib/jsapi/media.rb +4 -0
- data/lib/jsapi/meta/definitions.rb +101 -35
- data/lib/jsapi/meta/existence.rb +3 -1
- data/lib/jsapi/meta/openapi/path_item.rb +24 -2
- data/lib/jsapi/meta/openapi/version.rb +16 -4
- data/lib/jsapi/meta/operation.rb +26 -6
- data/lib/jsapi/meta/parameter/base.rb +1 -1
- data/lib/jsapi/meta/path.rb +56 -0
- data/lib/jsapi/meta/pathname.rb +60 -0
- data/lib/jsapi/meta/response/base.rb +6 -19
- data/lib/jsapi/meta/schema/boundary.rb +1 -0
- data/lib/jsapi/meta.rb +2 -0
- data/lib/jsapi/version.rb +1 -1
- data/lib/jsapi.rb +1 -0
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7651bda7e379186840e78ede1870f022b69deca559b6ba5ad6d0cbc8aac5c840
|
|
4
|
+
data.tar.gz: 16a661d4c24e1195f23d6fa0a23d27ba53fb944819b2f9a57c2174456f5ca913
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e1eebb7f9d53a3e2f28a562edac148093b506ade987384e093530f5d337b85a5aabeb5acae70ca80233f75215e1dbc158b4a786724a8eb77670e63ee0be348d
|
|
7
|
+
data.tar.gz: 51d183148b0575ba9d437c2d3208257ab6e0b1cdec9d3024aa63a6261a56542e9bee8907acc4bb5f042360f90114bf668f1e46836e97dcf1332d30b616f8d264
|
|
@@ -12,12 +12,16 @@ module Jsapi
|
|
|
12
12
|
self.class.api_definitions
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
##
|
|
16
|
+
# :method: api_operation
|
|
17
|
+
# :args: operation_name = nil, omit: nil, status: nil, strong: false, &block
|
|
18
|
+
#
|
|
15
19
|
# Performs an API operation by calling the given block. The request parameters are
|
|
16
20
|
# passed as an instance of the operation's model class to the block. The object
|
|
17
21
|
# returned by the block is implicitly rendered according to the appropriate +response+
|
|
18
|
-
# specification when the content type is a JSON MIME type. When content type is
|
|
22
|
+
# specification when the content type is a \JSON MIME type. When content type is
|
|
19
23
|
# <code>application/json-seq</code>, the object returned by the block is streamed in
|
|
20
|
-
# JSON sequence text format.
|
|
24
|
+
# \JSON sequence text format.
|
|
21
25
|
#
|
|
22
26
|
# api_operation('foo') do |api_params|
|
|
23
27
|
# # ...
|
|
@@ -36,41 +40,68 @@ module Jsapi
|
|
|
36
40
|
# - +:nil+ - All of the properties whose value is +nil+ are omitted.
|
|
37
41
|
#
|
|
38
42
|
# Raises an +ArgumentError+ when +:omit+ is other than +:empty+, +:nil+ or +nil+.
|
|
39
|
-
def api_operation(operation_name = nil,
|
|
40
|
-
omit: nil,
|
|
41
|
-
status: nil,
|
|
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
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
#
|
|
44
|
+
##
|
|
45
|
+
# :method: api_operation!
|
|
46
|
+
# :args: operation_name = nil, omit: nil, status: nil, strong: false, &block
|
|
47
|
+
#
|
|
48
|
+
# Like +api_operation+, except that a ParametersInvalid exception is raised
|
|
49
|
+
# when request parameters are invalid.
|
|
56
50
|
#
|
|
57
51
|
# api_operation!('foo') do |api_params|
|
|
58
52
|
# # ...
|
|
59
53
|
# end
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
54
|
+
|
|
55
|
+
[true, false].each do |bang|
|
|
56
|
+
define_method(bang ? :api_operation! : :api_operation) \
|
|
57
|
+
do |operation_name = nil, omit: nil, status: nil, strong: false, &block|
|
|
58
|
+
definitions = api_definitions
|
|
59
|
+
operation_model = _find_api_operation_model(operation_name, definitions)
|
|
60
|
+
response_model = _find_api_response_model(operation_model, status, definitions)
|
|
61
|
+
head(status) && return unless block
|
|
62
|
+
|
|
63
|
+
# Perform operation
|
|
64
|
+
api_params = _api_params(operation_model, definitions, strong: strong)
|
|
65
|
+
api_response = Response.new(
|
|
66
|
+
begin
|
|
67
|
+
raise ParametersInvalid.new(api_params) if bang && api_params.invalid?
|
|
68
|
+
|
|
69
|
+
block.call(api_params)
|
|
70
|
+
rescue StandardError => e
|
|
71
|
+
# Lookup a rescue handler
|
|
72
|
+
rescue_handler = definitions.rescue_handler_for(e)
|
|
73
|
+
raise e if rescue_handler.nil?
|
|
74
|
+
|
|
75
|
+
# Change the HTTP status code and response model
|
|
76
|
+
status = rescue_handler.status
|
|
77
|
+
response_model = operation_model.response(status)&.resolve(definitions)
|
|
78
|
+
raise e if response_model.nil?
|
|
79
|
+
|
|
80
|
+
# Call on_rescue callbacks
|
|
81
|
+
definitions.on_rescue_callbacks.each do |callback|
|
|
82
|
+
callback.respond_to?(:call) ? callback.call(e) : send(callback, e)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
Error.new(e, status: status)
|
|
86
|
+
end,
|
|
87
|
+
response_model, definitions, omit: omit
|
|
88
|
+
)
|
|
89
|
+
# Write response
|
|
90
|
+
media_type = response_model.content_type
|
|
91
|
+
|
|
92
|
+
if media_type == Media::Type::APPLICATION_JSON_SEQ
|
|
93
|
+
self.content_type = media_type.to_s
|
|
94
|
+
response.status = status
|
|
95
|
+
|
|
96
|
+
response.stream.tap do |stream|
|
|
97
|
+
api_response.write_json_seq_to(stream)
|
|
98
|
+
ensure
|
|
99
|
+
stream.close
|
|
100
|
+
end
|
|
101
|
+
elsif media_type.json?
|
|
102
|
+
render(json: api_response, status: status, content_type: media_type.to_s)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
74
105
|
end
|
|
75
106
|
|
|
76
107
|
# Returns the request parameters as an instance of the operation's model class.
|
|
@@ -88,7 +119,7 @@ module Jsapi
|
|
|
88
119
|
def api_params(operation_name = nil, strong: false)
|
|
89
120
|
definitions = api_definitions
|
|
90
121
|
_api_params(
|
|
91
|
-
|
|
122
|
+
_find_api_operation_model(operation_name, definitions),
|
|
92
123
|
definitions,
|
|
93
124
|
strong: strong
|
|
94
125
|
)
|
|
@@ -110,89 +141,34 @@ module Jsapi
|
|
|
110
141
|
# Raises an +ArgumentError+ when +:omit+ is other than +:empty+, +:nil+ or +nil+.
|
|
111
142
|
def api_response(result, operation_name = nil, omit: nil, status: nil)
|
|
112
143
|
definitions = api_definitions
|
|
113
|
-
|
|
114
|
-
response_model =
|
|
144
|
+
operation_model = _find_api_operation_model(operation_name, definitions)
|
|
145
|
+
response_model = _find_api_response_model(operation_model, status, definitions)
|
|
115
146
|
|
|
116
|
-
Response.new(result, response_model,
|
|
147
|
+
Response.new(result, response_model, definitions, omit: omit)
|
|
117
148
|
end
|
|
118
149
|
|
|
119
150
|
private
|
|
120
151
|
|
|
121
|
-
def
|
|
122
|
-
|
|
123
|
-
operation = _find_api_operation(operation_name, definitions)
|
|
124
|
-
|
|
125
|
-
# Perform operation
|
|
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
|
|
162
|
-
|
|
163
|
-
if response_model.json_seq_type?
|
|
164
|
-
self.response.status = status
|
|
165
|
-
|
|
166
|
-
self.response.stream.tap do |stream|
|
|
167
|
-
response.write_json_seq_to(stream)
|
|
168
|
-
ensure
|
|
169
|
-
stream.close
|
|
170
|
-
end
|
|
171
|
-
else
|
|
172
|
-
render(json: response, status: status)
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def _api_params(operation, definitions, strong:)
|
|
177
|
-
(operation.model || Model::Base).new(
|
|
152
|
+
def _api_params(operation_model, definitions, strong:)
|
|
153
|
+
(operation_model.model || Model::Base).new(
|
|
178
154
|
Parameters.new(
|
|
179
155
|
params.except(:action, :controller, :format).permit!,
|
|
180
156
|
request,
|
|
181
|
-
|
|
157
|
+
operation_model,
|
|
182
158
|
definitions,
|
|
183
159
|
strong: strong
|
|
184
160
|
)
|
|
185
161
|
)
|
|
186
162
|
end
|
|
187
163
|
|
|
188
|
-
def
|
|
164
|
+
def _find_api_response_model(operation, status, definitions)
|
|
189
165
|
response = operation.response(status)
|
|
190
166
|
return response.resolve(definitions) if response
|
|
191
167
|
|
|
192
168
|
raise "status code not defined: #{status}"
|
|
193
169
|
end
|
|
194
170
|
|
|
195
|
-
def
|
|
171
|
+
def _find_api_operation_model(operation_name, definitions)
|
|
196
172
|
operation = definitions.find_operation(operation_name)
|
|
197
173
|
return operation if operation
|
|
198
174
|
|
|
@@ -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.
|
|
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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'type_and_subtype'
|
|
4
|
+
|
|
5
|
+
module Jsapi
|
|
6
|
+
module Media
|
|
7
|
+
# Represents a media range.
|
|
8
|
+
class Range
|
|
9
|
+
include Comparable
|
|
10
|
+
include TypeAndSubtype
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# Transforms +value+ to an instance of this class.
|
|
14
|
+
#
|
|
15
|
+
# Raises an ArgumentError when +value+ could not be transformed.
|
|
16
|
+
def from(value)
|
|
17
|
+
media_range = try_from(value)
|
|
18
|
+
return media_range unless media_range.nil?
|
|
19
|
+
|
|
20
|
+
raise ArgumentError, "invalid media range: #{value.inspect}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Tries to transform +value+ to an instance of this class. Returns nil
|
|
24
|
+
# if +value+ could not be transformed.
|
|
25
|
+
def try_from(value)
|
|
26
|
+
return value if value.is_a?(Range)
|
|
27
|
+
|
|
28
|
+
type_and_subtype = pattern.match(value.to_s)&.captures
|
|
29
|
+
new(*type_and_subtype) if type_and_subtype&.count == 2
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def pattern
|
|
35
|
+
@pattern ||= begin
|
|
36
|
+
name = '[0-9a-zA-Z-]+'
|
|
37
|
+
%r{(\*|#{name})/(\*|(?:#{name}(?:\.#{name})?(?:\+#{name})?))}.freeze
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Compares it with +other+ by +priority+.
|
|
43
|
+
def <=>(other)
|
|
44
|
+
return unless other.is_a?(self.class)
|
|
45
|
+
|
|
46
|
+
result = priority <=> other.priority
|
|
47
|
+
return result unless result.zero?
|
|
48
|
+
|
|
49
|
+
result = type <=> other.type
|
|
50
|
+
return result unless result.zero?
|
|
51
|
+
|
|
52
|
+
subtype <=> other.subtype
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns true if the given media type matches the media range.
|
|
56
|
+
def match?(media_type)
|
|
57
|
+
media_type = Type.from(media_type) unless media_type.nil?
|
|
58
|
+
|
|
59
|
+
(type == '*' || type == media_type&.type) &&
|
|
60
|
+
(subtype == '*' || subtype == media_type&.subtype)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns the level of priority of the media range.
|
|
64
|
+
def priority
|
|
65
|
+
@priority ||= (type == '*' ? 2 : 0) + (subtype == '*' ? 1 : 0) + 1
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'type_and_subtype'
|
|
4
|
+
|
|
5
|
+
module Jsapi
|
|
6
|
+
module Media
|
|
7
|
+
# Represents a media type.
|
|
8
|
+
class Type
|
|
9
|
+
include Comparable
|
|
10
|
+
include TypeAndSubtype
|
|
11
|
+
|
|
12
|
+
# The <code>"application/json"</code> media type.
|
|
13
|
+
APPLICATION_JSON = Type.new('application', 'json')
|
|
14
|
+
|
|
15
|
+
# The <code>"application/json-seq"</code> media type.
|
|
16
|
+
APPLICATION_JSON_SEQ = Type.new('application', 'json-seq')
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# Transforms +value+ to an instance of this class.
|
|
20
|
+
#
|
|
21
|
+
# Raises an ArgumentError when +value+ could not be transformed.
|
|
22
|
+
def from(value)
|
|
23
|
+
media_type = try_from(value)
|
|
24
|
+
return media_type unless media_type.nil?
|
|
25
|
+
|
|
26
|
+
raise ArgumentError, "invalid media type: #{value.inspect}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Tries to transform +value+ to an instance of this class. Returns nil
|
|
30
|
+
# if +value+ could not be transformed.
|
|
31
|
+
def try_from(value)
|
|
32
|
+
return value if value.is_a?(Type)
|
|
33
|
+
|
|
34
|
+
type_and_subtype = pattern.match(value.to_s)&.captures
|
|
35
|
+
new(*type_and_subtype) if type_and_subtype&.count == 2
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def pattern
|
|
41
|
+
@pattern ||= begin
|
|
42
|
+
name = '[0-9a-zA-Z-]+'
|
|
43
|
+
%r{(#{name})/(#{name}(?:\.#{name})?(?:\+#{name})?)}.freeze
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Compares it with +other+ by +type+ and +subtype+.
|
|
49
|
+
def <=>(other)
|
|
50
|
+
return unless other.is_a?(self.class)
|
|
51
|
+
|
|
52
|
+
result = type <=> other.type
|
|
53
|
+
return result unless result.zero?
|
|
54
|
+
|
|
55
|
+
subtype <=> other.subtype
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns true if it represents a JSON media type as specified by
|
|
59
|
+
# https://mimesniff.spec.whatwg.org/#json-mime-type.
|
|
60
|
+
def json?
|
|
61
|
+
(type.in?(%w[application text]) && subtype == 'json') ||
|
|
62
|
+
subtype.end_with?('+json')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jsapi
|
|
4
|
+
module Media
|
|
5
|
+
module TypeAndSubtype # :nodoc:
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.attr_reader :type, :subtype
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(type, subtype)
|
|
11
|
+
@type = type.downcase
|
|
12
|
+
@subtype = subtype.downcase
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ==(other)
|
|
16
|
+
other.is_a?(self.class) &&
|
|
17
|
+
type == other.type &&
|
|
18
|
+
subtype == other.subtype
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
alias eql? ==
|
|
22
|
+
|
|
23
|
+
def hash
|
|
24
|
+
@hash ||= [type, subtype].hash
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def inspect
|
|
28
|
+
"#<#{self.class} #{to_s.inspect}>"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
@to_s ||= "#{type}/#{subtype}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
alias as_json to_s
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/jsapi/media.rb
ADDED
|
@@ -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,
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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,23 +253,31 @@ module Jsapi
|
|
|
228
253
|
version = OpenAPI::Version.from(version)
|
|
229
254
|
operations = objects[:operations].values
|
|
230
255
|
|
|
231
|
-
openapi_paths =
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
OpenAPI::PathItem.new(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
268
|
+
|
|
269
|
+
openapi_objects = (
|
|
270
|
+
%i[external_docs info parameters responses schemas
|
|
271
|
+
security_requirements security_schemes tags] +
|
|
272
|
+
if version == OpenAPI::V2_0
|
|
273
|
+
%i[base_path host schemes]
|
|
241
274
|
else
|
|
242
|
-
%i[callbacks examples
|
|
243
|
-
|
|
244
|
-
|
|
275
|
+
%i[callbacks examples headers links request_bodies servers]
|
|
276
|
+
end
|
|
277
|
+
).index_with { |key| object_to_openapi(objects[key], version).presence }
|
|
245
278
|
|
|
246
279
|
with_openapi_extensions(
|
|
247
|
-
if version
|
|
280
|
+
if version == OpenAPI::V2_0
|
|
248
281
|
openapi_server = objects[:servers].first || default_server
|
|
249
282
|
uri = URI(openapi_server.url) if openapi_server
|
|
250
283
|
{
|
|
@@ -252,13 +285,13 @@ module Jsapi
|
|
|
252
285
|
swagger: '2.0',
|
|
253
286
|
info: openapi_objects[:info],
|
|
254
287
|
host: openapi_objects[:host] || uri&.hostname,
|
|
255
|
-
basePath: openapi_objects[:base_path] || uri&.path,
|
|
288
|
+
basePath: openapi_objects[:base_path]&.to_s || uri&.path,
|
|
256
289
|
schemes: openapi_objects[:schemes] || Array(uri&.scheme).presence,
|
|
257
290
|
consumes: operations.filter_map do |operation|
|
|
258
291
|
operation.consumes(self)
|
|
259
292
|
end.uniq.sort.presence,
|
|
260
293
|
produces: operations.flat_map do |operation|
|
|
261
|
-
operation.produces(self)
|
|
294
|
+
operation.produces(self).map(&:to_s)
|
|
262
295
|
end.uniq.sort.presence,
|
|
263
296
|
paths: openapi_paths,
|
|
264
297
|
definitions: openapi_objects[:schemas],
|
|
@@ -269,12 +302,7 @@ module Jsapi
|
|
|
269
302
|
else
|
|
270
303
|
{
|
|
271
304
|
# Order according to the OpenAPI specification 3.x
|
|
272
|
-
openapi:
|
|
273
|
-
case version.minor
|
|
274
|
-
when 0 then '3.0.3'
|
|
275
|
-
when 1 then '3.1.1'
|
|
276
|
-
when 2 then '3.2.0'
|
|
277
|
-
end,
|
|
305
|
+
openapi: version.to_s,
|
|
278
306
|
info: openapi_objects[:info],
|
|
279
307
|
servers:
|
|
280
308
|
openapi_objects[:servers] ||
|
|
@@ -300,6 +328,44 @@ module Jsapi
|
|
|
300
328
|
)
|
|
301
329
|
end
|
|
302
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) || {}
|
|
367
|
+
end
|
|
368
|
+
|
|
303
369
|
# Returns the first RescueHandler to handle +exception+, or nil if no one could be found.
|
|
304
370
|
def rescue_handler_for(exception)
|
|
305
371
|
objects[:rescue_handlers].find { |r| r.match?(exception) }
|
|
@@ -338,15 +404,14 @@ module Jsapi
|
|
|
338
404
|
def invalidate_ancestors
|
|
339
405
|
@ancestors = nil
|
|
340
406
|
@objects = nil
|
|
341
|
-
@
|
|
342
|
-
|
|
407
|
+
@path_parameters = nil
|
|
408
|
+
each_descendant(&:invalidate_ancestors)
|
|
343
409
|
end
|
|
344
410
|
|
|
345
411
|
# Invalidates cached objects.
|
|
346
412
|
def invalidate_objects
|
|
347
413
|
@objects = nil
|
|
348
|
-
|
|
349
|
-
@dependent_definitions&.each(&:invalidate_objects)
|
|
414
|
+
each_descendant(&:invalidate_objects)
|
|
350
415
|
end
|
|
351
416
|
|
|
352
417
|
private
|
|
@@ -365,10 +430,6 @@ module Jsapi
|
|
|
365
430
|
end
|
|
366
431
|
end
|
|
367
432
|
|
|
368
|
-
def default_operation_path
|
|
369
|
-
@default_operation_path ||= "/#{default_operation_name}"
|
|
370
|
-
end
|
|
371
|
-
|
|
372
433
|
def default_server
|
|
373
434
|
@default_server ||=
|
|
374
435
|
if (name = @owner.try(:name))
|
|
@@ -380,6 +441,11 @@ module Jsapi
|
|
|
380
441
|
end
|
|
381
442
|
end
|
|
382
443
|
|
|
444
|
+
def each_descendant(&block)
|
|
445
|
+
[*@children, *dependent_definitions].each(&block)
|
|
446
|
+
nil
|
|
447
|
+
end
|
|
448
|
+
|
|
383
449
|
def objects
|
|
384
450
|
@objects ||= ancestors.each_with_object({}) do |ancestor, objects|
|
|
385
451
|
self.class.attribute_names.each do |key|
|
data/lib/jsapi/meta/existence.rb
CHANGED
|
@@ -30,7 +30,9 @@ module Jsapi
|
|
|
30
30
|
# or must be +false+.
|
|
31
31
|
PRESENT = new(4)
|
|
32
32
|
|
|
33
|
-
#
|
|
33
|
+
# Transforms +value+ to an instance of this class.
|
|
34
|
+
#
|
|
35
|
+
# Raises an +ArgumentError+ if +value+ could not be transformed.
|
|
34
36
|
def self.from(value)
|
|
35
37
|
return value if value.is_a?(Existence)
|
|
36
38
|
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -6,9 +6,9 @@ module Jsapi
|
|
|
6
6
|
class Version
|
|
7
7
|
include Comparable
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# Transforms +version+ to an instance of this class.
|
|
10
10
|
#
|
|
11
|
-
# Raises an +ArgumentError+ if +version+
|
|
11
|
+
# Raises an +ArgumentError+ if +version+ could not be transformed.
|
|
12
12
|
def self.from(version)
|
|
13
13
|
return version if version.is_a?(Version)
|
|
14
14
|
|
|
@@ -48,13 +48,25 @@ module Jsapi
|
|
|
48
48
|
minor <=> other.minor
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def inspect
|
|
51
|
+
def inspect # :nodoc:
|
|
52
52
|
"<#{self.class.name} #{self}>"
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def to_s # :nodoc:
|
|
56
|
-
|
|
56
|
+
@to_s ||=
|
|
57
|
+
case [major, minor]
|
|
58
|
+
when [3, 0]
|
|
59
|
+
'3.0.3'
|
|
60
|
+
when [3, 1]
|
|
61
|
+
'3.1.1'
|
|
62
|
+
when [3, 2]
|
|
63
|
+
'3.2.0'
|
|
64
|
+
else
|
|
65
|
+
"#{major}.#{minor}"
|
|
66
|
+
end
|
|
57
67
|
end
|
|
68
|
+
|
|
69
|
+
alias as_json to_s
|
|
58
70
|
end
|
|
59
71
|
end
|
|
60
72
|
end
|
data/lib/jsapi/meta/operation.rb
CHANGED
|
@@ -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,
|
|
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,18 +112,30 @@ module Jsapi
|
|
|
104
112
|
(@parameters ||= {})[name.to_s] = Parameter.new(name, keywords)
|
|
105
113
|
end
|
|
106
114
|
|
|
107
|
-
# Returns the
|
|
115
|
+
# Returns the full path of the operation as a Pathname.
|
|
116
|
+
def full_path
|
|
117
|
+
parent_path + path
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Returns the media type consumed by the operation.
|
|
108
121
|
def consumes(definitions)
|
|
109
122
|
request_body&.resolve(definitions)&.content_type
|
|
110
123
|
end
|
|
111
124
|
|
|
112
|
-
# Returns an array containing the
|
|
125
|
+
# Returns an array containing the media types produced by the operation.
|
|
113
126
|
def produces(definitions)
|
|
114
127
|
responses.values.filter_map do |response|
|
|
115
128
|
response.resolve(definitions).content_type
|
|
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)
|
|
@@ -134,7 +154,7 @@ module Jsapi
|
|
|
134
154
|
result[:consumes] = [consumes]
|
|
135
155
|
end
|
|
136
156
|
if (produces = produces(definitions)).present?
|
|
137
|
-
result[:produces] = produces
|
|
157
|
+
result[:produces] = produces.map(&:to_s)
|
|
138
158
|
end
|
|
139
159
|
result[:schemes] = schemes if schemes.present?
|
|
140
160
|
elsif servers.present?
|
|
@@ -11,7 +11,7 @@ module Jsapi
|
|
|
11
11
|
|
|
12
12
|
##
|
|
13
13
|
# :attr: content_type
|
|
14
|
-
# The
|
|
14
|
+
# The media type used to describe complex parameters in \OpenAPI 3.0 and higher.
|
|
15
15
|
attribute :content_type, String
|
|
16
16
|
|
|
17
17
|
##
|
|
@@ -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
|
+
# Transforms +name+ to an instance of this class.
|
|
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 itself.
|
|
34
|
+
# Returns itself 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
|
|
@@ -7,15 +7,12 @@ module Jsapi
|
|
|
7
7
|
class Base < Model::Base
|
|
8
8
|
include OpenAPI::Extensions
|
|
9
9
|
|
|
10
|
-
JSON_TYPE = %r{(^application/|^text/|\+)json$}.freeze # :nodoc:
|
|
11
|
-
JSON_SEQ_TYPE = 'application/json-seq' # :nodoc:
|
|
12
|
-
|
|
13
10
|
delegate_missing_to :schema
|
|
14
11
|
|
|
15
12
|
##
|
|
16
13
|
# :attr: content_type
|
|
17
|
-
# The
|
|
18
|
-
attribute :content_type,
|
|
14
|
+
# The media type of the response, <code>"application/json"</code> by default.
|
|
15
|
+
attribute :content_type, Media::Type, default: Media::Type::APPLICATION_JSON
|
|
19
16
|
|
|
20
17
|
##
|
|
21
18
|
# :attr: description
|
|
@@ -66,17 +63,6 @@ module Jsapi
|
|
|
66
63
|
@schema = Schema.new(keywords)
|
|
67
64
|
end
|
|
68
65
|
|
|
69
|
-
# Returns true if content type is a JSON MIME type as specified by
|
|
70
|
-
# https://mimesniff.spec.whatwg.org/#json-mime-type.
|
|
71
|
-
def json_type?
|
|
72
|
-
content_type.match?(JSON_TYPE)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Returns true if content type is <code>"application/json-seq"</code>.
|
|
76
|
-
def json_seq_type?
|
|
77
|
-
content_type == JSON_SEQ_TYPE
|
|
78
|
-
end
|
|
79
|
-
|
|
80
66
|
# Returns a hash representing the \OpenAPI response object.
|
|
81
67
|
def to_openapi(version, definitions)
|
|
82
68
|
version = OpenAPI::Version.from(version)
|
|
@@ -91,7 +77,7 @@ module Jsapi
|
|
|
91
77
|
end.compact.presence,
|
|
92
78
|
examples: (
|
|
93
79
|
if (example = examples.values.first).present?
|
|
94
|
-
{ content_type => example.resolve(definitions).value }
|
|
80
|
+
{ content_type.to_s => example.resolve(definitions).value }
|
|
95
81
|
end
|
|
96
82
|
)
|
|
97
83
|
}
|
|
@@ -103,8 +89,9 @@ module Jsapi
|
|
|
103
89
|
header.to_openapi(version)
|
|
104
90
|
end.presence,
|
|
105
91
|
content: {
|
|
106
|
-
content_type => {
|
|
107
|
-
**if
|
|
92
|
+
content_type.to_s => {
|
|
93
|
+
**if content_type == Media::Type::APPLICATION_JSON_SEQ &&
|
|
94
|
+
schema.array? && version >= OpenAPI::V3_2
|
|
108
95
|
{ itemSchema: schema.items.to_openapi(version) }
|
|
109
96
|
else
|
|
110
97
|
{ schema: schema.to_openapi(version) }
|
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
data/lib/jsapi.rb
CHANGED
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:
|
|
4
|
+
version: 1.4.1
|
|
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-
|
|
11
|
+
date: 2025-11-23 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
|
|
@@ -49,6 +50,10 @@ files:
|
|
|
49
50
|
- lib/jsapi/json/object.rb
|
|
50
51
|
- lib/jsapi/json/string.rb
|
|
51
52
|
- lib/jsapi/json/value.rb
|
|
53
|
+
- lib/jsapi/media.rb
|
|
54
|
+
- lib/jsapi/media/range.rb
|
|
55
|
+
- lib/jsapi/media/type.rb
|
|
56
|
+
- lib/jsapi/media/type_and_subtype.rb
|
|
52
57
|
- lib/jsapi/meta.rb
|
|
53
58
|
- lib/jsapi/meta/callable.rb
|
|
54
59
|
- lib/jsapi/meta/callable/symbol_evaluator.rb
|
|
@@ -86,6 +91,8 @@ files:
|
|
|
86
91
|
- lib/jsapi/meta/parameter.rb
|
|
87
92
|
- lib/jsapi/meta/parameter/base.rb
|
|
88
93
|
- lib/jsapi/meta/parameter/reference.rb
|
|
94
|
+
- lib/jsapi/meta/path.rb
|
|
95
|
+
- lib/jsapi/meta/pathname.rb
|
|
89
96
|
- lib/jsapi/meta/property.rb
|
|
90
97
|
- lib/jsapi/meta/reference_error.rb
|
|
91
98
|
- lib/jsapi/meta/request_body.rb
|