tina4ruby 3.13.41 → 3.13.43
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/tina4/queue.rb +4 -0
- data/lib/tina4/queue_backends/mongo_backend.rb +85 -3
- data/lib/tina4/swagger.rb +234 -19
- data/lib/tina4/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 209245b1743578b21530e31feb670940f068cc911725fff1b2a59ee6d8335e10
|
|
4
|
+
data.tar.gz: '059d0fde8d4fbc57db9e4aa408d0dd31f71b1c8065d5041258a8a502017ce7ab'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30babc583e40444ec46069088e1af2a94f0485546b585530ccda22cca2a8ba29fafca3464bd08845fd1d9f88e92342533f2c58a82b89f38a517fce607096f905
|
|
7
|
+
data.tar.gz: 84960f5d44cb50d7ec4d8dd31a6645e10640526aaf6c9f777e81c7376a43290a6027d33d903f45a317f369834eee59518ec9d215d05e8933c820e5d3020ae49e
|
data/lib/tina4/queue.rb
CHANGED
|
@@ -305,6 +305,10 @@ module Tina4
|
|
|
305
305
|
config = resolve_mongo_config
|
|
306
306
|
config[:visibility_timeout] = vt
|
|
307
307
|
config[:max_retries] = max_retries
|
|
308
|
+
# Thread retry_backoff through so a failed/retried job's available_at is
|
|
309
|
+
# reset to now (or now + retry_backoff) instead of being stranded for the
|
|
310
|
+
# full visibility window (Bug B).
|
|
311
|
+
config[:retry_backoff] = retry_backoff
|
|
308
312
|
Tina4::QueueBackends::MongoBackend.new(config)
|
|
309
313
|
else
|
|
310
314
|
raise ArgumentError, "Unknown queue backend: #{chosen.inspect}. Use 'lite', 'rabbitmq', 'kafka', or 'mongodb'."
|
|
@@ -5,11 +5,16 @@ module Tina4
|
|
|
5
5
|
class MongoBackend
|
|
6
6
|
# Reservation/visibility + retry policy (settable so a Queue can propagate
|
|
7
7
|
# its own onto a backend instance passed directly — legacy path).
|
|
8
|
-
attr_accessor :visibility_timeout, :max_retries
|
|
8
|
+
attr_accessor :visibility_timeout, :max_retries, :retry_backoff
|
|
9
9
|
|
|
10
10
|
def initialize(options = {})
|
|
11
11
|
require "mongo"
|
|
12
12
|
@max_retries = options[:max_retries] || 3
|
|
13
|
+
# Seconds to delay a requeued (failed/retried) job before it is eligible
|
|
14
|
+
# again. Default 0 = available on the very next dequeue, so a fail()'d job
|
|
15
|
+
# retries immediately (matching the lite backend) instead of waiting out
|
|
16
|
+
# the visibility window.
|
|
17
|
+
@retry_backoff = (options[:retry_backoff] || 0).to_f
|
|
13
18
|
|
|
14
19
|
uri = options[:uri] || ENV["TINA4_MONGO_URI"]
|
|
15
20
|
host = options[:host] || ENV.fetch("TINA4_MONGO_HOST", "localhost")
|
|
@@ -134,10 +139,58 @@ module Tina4
|
|
|
134
139
|
collection.delete_one(_id: message.id)
|
|
135
140
|
end
|
|
136
141
|
|
|
142
|
+
# Terminal success — the job is done and removed (mirrors the lite
|
|
143
|
+
# backend's complete()). Without this, job.complete() was a no-op on
|
|
144
|
+
# MongoDB and the document stayed "processing" forever.
|
|
145
|
+
def complete(message)
|
|
146
|
+
collection.delete_one(_id: message.id)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Record a failed attempt (mirrors the lite backend + the Python Mongo
|
|
150
|
+
# adapter). Increments attempts; while attempts < max_retries the job is
|
|
151
|
+
# re-queued to pending (visible again immediately, or after retry_backoff),
|
|
152
|
+
# otherwise it is dead-lettered. The Queue/Job lifecycle expects fail() to
|
|
153
|
+
# route the requeue here — previously MongoBackend had no fail(), so
|
|
154
|
+
# job.fail() degraded to in-memory bookkeeping and never touched Mongo.
|
|
155
|
+
def fail(job, error = "")
|
|
156
|
+
job.attempts += 1
|
|
157
|
+
if job.attempts >= @max_retries
|
|
158
|
+
collection.find_one_and_update(
|
|
159
|
+
{ _id: job.id },
|
|
160
|
+
{ "$set" => { status: "dead", topic: "#{job.topic}.dead_letter",
|
|
161
|
+
error: error, reserved_at: nil } },
|
|
162
|
+
upsert: true
|
|
163
|
+
)
|
|
164
|
+
else
|
|
165
|
+
requeue_with_error(job, error)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Explicit re-queue requested by the caller (job.retry()). Always
|
|
170
|
+
# re-enqueues regardless of the retry limit — a manual override, distinct
|
|
171
|
+
# from the automatic fail() path. Increments attempts, clears the error.
|
|
172
|
+
def retry(job, delay_seconds: 0)
|
|
173
|
+
job.attempts += 1
|
|
174
|
+
backoff = delay_seconds.to_f > 0 ? delay_seconds.to_f : @retry_backoff
|
|
175
|
+
collection.find_one_and_update(
|
|
176
|
+
{ _id: job.id },
|
|
177
|
+
{ "$set" => { status: "pending", error: nil, reserved_at: nil,
|
|
178
|
+
available_at: requeue_available_at(backoff) } },
|
|
179
|
+
upsert: true
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
137
183
|
def requeue(message)
|
|
184
|
+
# Reset available_at so the requeued job is visible again right away (or
|
|
185
|
+
# after retry_backoff) and clear reserved_at. dequeue() pushed
|
|
186
|
+
# available_at out to the reservation expiry; leaving it there stranded a
|
|
187
|
+
# requeued job for the full visibility window instead of retrying it on
|
|
188
|
+
# the next pop().
|
|
138
189
|
collection.find_one_and_update(
|
|
139
190
|
{ _id: message.id },
|
|
140
|
-
{ "$set" => { status: "pending"
|
|
191
|
+
{ "$set" => { status: "pending", reserved_at: nil,
|
|
192
|
+
available_at: requeue_available_at(@retry_backoff) },
|
|
193
|
+
"$inc" => { attempts: 1 } },
|
|
141
194
|
upsert: true
|
|
142
195
|
)
|
|
143
196
|
end
|
|
@@ -172,7 +225,11 @@ module Tina4
|
|
|
172
225
|
def retry_failed(topic, max_retries: 3)
|
|
173
226
|
result = collection.update_many(
|
|
174
227
|
{ topic: topic, status: "failed", attempts: { "$lt" => max_retries } },
|
|
175
|
-
|
|
228
|
+
# Reset available_at so re-queued failed jobs are visible again — they
|
|
229
|
+
# were reserved with available_at in the future at dequeue. Clear
|
|
230
|
+
# reserved_at too. (Same Bug B reason as requeue/fail.)
|
|
231
|
+
{ "$set" => { status: "pending", error: nil, reserved_at: nil,
|
|
232
|
+
available_at: requeue_available_at(@retry_backoff) } }
|
|
176
233
|
)
|
|
177
234
|
result.modified_count
|
|
178
235
|
end
|
|
@@ -183,6 +240,31 @@ module Tina4
|
|
|
183
240
|
|
|
184
241
|
private
|
|
185
242
|
|
|
243
|
+
# Re-queue a failed job to pending, carrying the failure reason and
|
|
244
|
+
# resetting available_at (now, or now + retry_backoff) + clearing
|
|
245
|
+
# reserved_at so the next dequeue picks it up.
|
|
246
|
+
def requeue_with_error(job, error)
|
|
247
|
+
collection.find_one_and_update(
|
|
248
|
+
{ _id: job.id },
|
|
249
|
+
# Persist the incremented attempt count (fail() already did job.attempts
|
|
250
|
+
# += 1). Without this the doc's attempts stayed at its old value, so the
|
|
251
|
+
# next dequeue surfaced a stale count, fail()'s attempts >= max_retries
|
|
252
|
+
# check never tripped, and the job was re-queued forever instead of
|
|
253
|
+
# dead-lettering.
|
|
254
|
+
{ "$set" => { status: "pending", error: error, reserved_at: nil,
|
|
255
|
+
attempts: job.attempts,
|
|
256
|
+
available_at: requeue_available_at(@retry_backoff) } },
|
|
257
|
+
upsert: true
|
|
258
|
+
)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# available_at for a re-queued job: now (immediately retryable) or
|
|
262
|
+
# now + backoff when a backoff is configured.
|
|
263
|
+
def requeue_available_at(backoff)
|
|
264
|
+
b = (backoff || 0).to_f
|
|
265
|
+
b > 0 ? (Time.now.utc + b) : Time.now.utc
|
|
266
|
+
end
|
|
267
|
+
|
|
186
268
|
def collection
|
|
187
269
|
@db[@collection_name]
|
|
188
270
|
end
|
data/lib/tina4/swagger.rb
CHANGED
|
@@ -3,23 +3,68 @@ require "json"
|
|
|
3
3
|
|
|
4
4
|
module Tina4
|
|
5
5
|
module Swagger
|
|
6
|
+
# Process-wide registries for security schemes and reusable component
|
|
7
|
+
# schemas declared programmatically (add_security_scheme / add_schema).
|
|
8
|
+
# Kept module-level so app bootstrap can register before any generate()
|
|
9
|
+
# call; reset_registry clears them (tests).
|
|
10
|
+
@registered_schemes = {}
|
|
11
|
+
@registered_schemas = {}
|
|
12
|
+
|
|
6
13
|
class << self
|
|
14
|
+
# ── Programmatic registries ────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
# Register a named OpenAPI security scheme (e.g. an oauth2 scheme with
|
|
17
|
+
# scopes, or a custom apiKey). Call at app bootstrap, before generate().
|
|
18
|
+
#
|
|
19
|
+
# Tina4::Swagger.add_security_scheme("oauth2", {
|
|
20
|
+
# "type" => "oauth2",
|
|
21
|
+
# "flows" => { "clientCredentials" => {
|
|
22
|
+
# "tokenUrl" => "https://api.example.com/oauth/token",
|
|
23
|
+
# "scopes" => { "read:users" => "Read users", "write:users" => "Write users" }
|
|
24
|
+
# } }
|
|
25
|
+
# })
|
|
26
|
+
def add_security_scheme(name, definition)
|
|
27
|
+
@registered_schemes[name.to_s] = definition
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Register a reusable component schema, referenceable via
|
|
31
|
+
# swagger_meta[:request_schema] / [:response_schemas] or a raw $ref.
|
|
32
|
+
def add_schema(name, schema)
|
|
33
|
+
@registered_schemas[name.to_s] = schema
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Clear the security-scheme and schema registries (test helper).
|
|
37
|
+
def reset_registry
|
|
38
|
+
@registered_schemes = {}
|
|
39
|
+
@registered_schemas = {}
|
|
40
|
+
end
|
|
41
|
+
|
|
7
42
|
def generate(routes = [])
|
|
8
43
|
spec = base_spec
|
|
9
44
|
route_list = routes.empty? ? Tina4::Router.routes : routes
|
|
10
45
|
# Accumulators shared across routes: ORM models referenced
|
|
11
|
-
# (-> components.schemas),
|
|
12
|
-
#
|
|
13
|
-
|
|
46
|
+
# (-> components.schemas), registered custom-schema names referenced
|
|
47
|
+
# by routes, tags used (-> top-level tags[]), seen operationIds
|
|
48
|
+
# (de-dup — OpenAPI requires them unique).
|
|
49
|
+
ctx = { models: {}, ref_schemas: [], used_tags: [], seen_ids: [],
|
|
50
|
+
schemes: spec["components"]["securitySchemes"] }
|
|
14
51
|
route_list.each do |route|
|
|
52
|
+
next unless included?(route.path)
|
|
53
|
+
|
|
15
54
|
add_route_to_spec(spec, route, ctx)
|
|
16
55
|
end
|
|
17
56
|
|
|
18
|
-
unless ctx[:models].empty?
|
|
19
|
-
spec["components"]["schemas"]
|
|
57
|
+
unless ctx[:models].empty? && ctx[:ref_schemas].empty?
|
|
58
|
+
spec["components"]["schemas"] ||= {}
|
|
20
59
|
ctx[:models].each do |name, klass|
|
|
21
60
|
spec["components"]["schemas"][name] = model_schema(klass)
|
|
22
61
|
end
|
|
62
|
+
ctx[:ref_schemas].each do |name|
|
|
63
|
+
next unless @registered_schemas.key?(name)
|
|
64
|
+
next if spec["components"]["schemas"].key?(name)
|
|
65
|
+
|
|
66
|
+
spec["components"]["schemas"][name] = @registered_schemas[name]
|
|
67
|
+
end
|
|
23
68
|
end
|
|
24
69
|
spec["tags"] = ctx[:used_tags].map { |t| { "name" => t } } unless ctx[:used_tags].empty?
|
|
25
70
|
|
|
@@ -63,22 +108,88 @@ module Tina4
|
|
|
63
108
|
end
|
|
64
109
|
|
|
65
110
|
{
|
|
66
|
-
"openapi" => "
|
|
111
|
+
"openapi" => resolve_openapi_version(ENV["TINA4_SWAGGER_OPENAPI"]),
|
|
67
112
|
"info" => info,
|
|
68
113
|
"servers" => servers,
|
|
69
114
|
"paths" => {},
|
|
70
115
|
"components" => {
|
|
71
|
-
"securitySchemes" =>
|
|
72
|
-
"bearerAuth" => {
|
|
73
|
-
"type" => "http",
|
|
74
|
-
"scheme" => "bearer",
|
|
75
|
-
"bearerFormat" => "JWT"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
116
|
+
"securitySchemes" => security_schemes
|
|
78
117
|
}
|
|
79
118
|
}
|
|
80
119
|
end
|
|
81
120
|
|
|
121
|
+
# OpenAPI version — default 3.0.3 for broad tool compatibility; opt in to
|
|
122
|
+
# 3.1.0 via TINA4_SWAGGER_OPENAPI=3.1 (the schemas this generator emits
|
|
123
|
+
# are valid in both dialects).
|
|
124
|
+
def resolve_openapi_version(val)
|
|
125
|
+
v = val.to_s.strip
|
|
126
|
+
return "3.0.3" if v.empty?
|
|
127
|
+
return "3.1.0" if %w[3.1 3.1.0].include?(v)
|
|
128
|
+
return "3.0.3" if %w[3.0 3.0.3].include?(v)
|
|
129
|
+
|
|
130
|
+
v # honour an explicit full version verbatim
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Resolve components.securitySchemes from defaults + env + registry.
|
|
134
|
+
def security_schemes
|
|
135
|
+
schemes = {
|
|
136
|
+
"bearerAuth" => {
|
|
137
|
+
"type" => "http",
|
|
138
|
+
"scheme" => "bearer",
|
|
139
|
+
# Default bearer format (the built-in bearerAuth scheme). JWT unless an
|
|
140
|
+
# API uses opaque tokens / API keys as the bearer (e.g. sk_live_...).
|
|
141
|
+
"bearerFormat" => ENV["TINA4_SWAGGER_BEARER_FORMAT"] || "JWT"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Optional apiKey scheme — emitted as "apiKeyAuth" when a header/query
|
|
146
|
+
# name is configured (e.g. X-Api-Key).
|
|
147
|
+
api_key_name = ENV["TINA4_SWAGGER_API_KEY_NAME"]
|
|
148
|
+
if api_key_name && !api_key_name.empty?
|
|
149
|
+
api_key_in = ENV["TINA4_SWAGGER_API_KEY_IN"] || "header"
|
|
150
|
+
api_key_in = "header" unless %w[header query cookie].include?(api_key_in)
|
|
151
|
+
schemes["apiKeyAuth"] = {
|
|
152
|
+
"type" => "apiKey",
|
|
153
|
+
"name" => api_key_name,
|
|
154
|
+
"in" => api_key_in
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Registered schemes win (let an app override bearerAuth or add oauth2).
|
|
159
|
+
@registered_schemes.each { |name, defn| schemes[name] = defn }
|
|
160
|
+
schemes
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Which scheme secured routes use by default when no per-route security
|
|
164
|
+
# is declared.
|
|
165
|
+
def default_scheme
|
|
166
|
+
scheme = ENV["TINA4_SWAGGER_DEFAULT_SCHEME"]
|
|
167
|
+
scheme && !scheme.empty? ? scheme : "bearerAuth"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Path filtering. Framework internals (/swagger, /__dev) are ALWAYS
|
|
171
|
+
# excluded; then TINA4_SWAGGER_INCLUDE (allow-list) / _EXCLUDE apply.
|
|
172
|
+
def included?(raw_path)
|
|
173
|
+
["/swagger", "/__dev"].each do |internal|
|
|
174
|
+
return false if raw_path == internal || raw_path.start_with?("#{internal}/")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
includes = csv(ENV["TINA4_SWAGGER_INCLUDE"])
|
|
178
|
+
unless includes.empty?
|
|
179
|
+
matched = includes.any? { |p| raw_path == p || raw_path.start_with?(p) }
|
|
180
|
+
return false unless matched
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
excludes = csv(ENV["TINA4_SWAGGER_EXCLUDE"])
|
|
184
|
+
return false if excludes.any? { |p| raw_path == p || raw_path.start_with?(p) }
|
|
185
|
+
|
|
186
|
+
true
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def csv(val)
|
|
190
|
+
val.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
191
|
+
end
|
|
192
|
+
|
|
82
193
|
# servers[] — TINA4_SWAGGER_SERVERS (comma-separated) for a multi-server
|
|
83
194
|
# list, else SWAGGER_DEV_URL, else the relative "/" default.
|
|
84
195
|
def servers
|
|
@@ -124,31 +235,135 @@ module Tina4
|
|
|
124
235
|
"description" => meta[:description] || "",
|
|
125
236
|
"tags" => tags,
|
|
126
237
|
"parameters" => build_parameters(route),
|
|
127
|
-
"responses" => meta
|
|
238
|
+
"responses" => build_responses(meta, ref, ctx)
|
|
128
239
|
}
|
|
129
240
|
|
|
130
241
|
operation["deprecated"] = true if meta[:deprecated]
|
|
131
242
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
end
|
|
243
|
+
security = resolve_security(meta, route, ctx[:schemes])
|
|
244
|
+
operation["security"] = security unless security.nil?
|
|
135
245
|
|
|
136
246
|
if %w[post put patch].include?(method)
|
|
137
|
-
operation["requestBody"] = build_request_body(method, meta, ref)
|
|
247
|
+
operation["requestBody"] = build_request_body(method, meta, ref, ctx)
|
|
138
248
|
end
|
|
139
249
|
|
|
140
250
|
spec["paths"][path][method] = operation
|
|
141
251
|
end
|
|
142
252
|
|
|
143
|
-
|
|
253
|
+
# Auth — explicit per-route security wins (an explicit "public"/[] = no
|
|
254
|
+
# security); otherwise a secured route gets the default scheme. Returns
|
|
255
|
+
# nil when the route declares nothing and is not secured (omit the key).
|
|
256
|
+
def resolve_security(meta, route, schemes)
|
|
257
|
+
if meta.key?(:security)
|
|
258
|
+
reqs = normalize_security(meta[:security], meta[:scopes])
|
|
259
|
+
return reqs.empty? ? [] : sanitize_security(reqs, schemes)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
return sanitize_security([{ default_scheme => [] }], schemes) if route.auth_handler
|
|
263
|
+
|
|
264
|
+
nil
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Normalize swagger_meta[:security] to an OpenAPI security-requirement list.
|
|
268
|
+
#
|
|
269
|
+
# security: "oauth2", scopes: ["read"] -> [{"oauth2" => ["read"]}]
|
|
270
|
+
# security: { "bearerAuth" => [] } -> [{"bearerAuth" => []}] (AND within one map)
|
|
271
|
+
# security: [{"oauth2"=>["read"]}, {...}] -> verbatim (OR across maps)
|
|
272
|
+
# security: "public" / [] -> [] (explicitly no auth)
|
|
273
|
+
def normalize_security(value, scopes)
|
|
274
|
+
scope_list = Array(scopes).map(&:to_s)
|
|
275
|
+
if value.nil? || value == [] || (value.is_a?(String) && %w[public none].include?(value))
|
|
276
|
+
return []
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
case value
|
|
280
|
+
when String
|
|
281
|
+
[{ value => scope_list }]
|
|
282
|
+
when Hash
|
|
283
|
+
[value.each_with_object({}) { |(k, v), h| h[k.to_s] = Array(v).map(&:to_s) }]
|
|
284
|
+
when Array
|
|
285
|
+
value.map { |req| req.each_with_object({}) { |(k, v), h| h[k.to_s] = Array(v).map(&:to_s) } }
|
|
286
|
+
else
|
|
287
|
+
[]
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Keep a security-requirement list spec-valid: scopes are allowed only on
|
|
292
|
+
# oauth2/openIdConnect schemes; everything else gets an empty array
|
|
293
|
+
# (OpenAPI requires that for http/apiKey).
|
|
294
|
+
def sanitize_security(reqs, schemes)
|
|
295
|
+
scope_ok = %w[oauth2 openIdConnect]
|
|
296
|
+
reqs.map do |req|
|
|
297
|
+
req.each_with_object({}) do |(name, scopes), clean|
|
|
298
|
+
stype = (schemes[name] || {})["type"]
|
|
299
|
+
clean[name] = scope_ok.include?(stype) ? Array(scopes) : []
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def build_request_body(_method, meta, ref, ctx)
|
|
144
305
|
return meta[:request_body] if meta[:request_body]
|
|
145
306
|
|
|
307
|
+
# Registered custom request schema ($ref) wins over the ORM-model ref.
|
|
308
|
+
if (req_schema = meta[:request_schema])
|
|
309
|
+
name, content_type = request_schema_parts(req_schema)
|
|
310
|
+
ctx[:ref_schemas] << name unless ctx[:ref_schemas].include?(name)
|
|
311
|
+
content = { "schema" => { "$ref" => "#/components/schemas/#{name}" } }
|
|
312
|
+
content["example"] = meta[:example] if meta[:example]
|
|
313
|
+
return { "content" => { content_type => content } }
|
|
314
|
+
end
|
|
315
|
+
|
|
146
316
|
schema = ref ? { "$ref" => ref } : { "type" => "object" }
|
|
147
317
|
content = { "schema" => schema }
|
|
148
318
|
content["example"] = meta[:example] if meta[:example]
|
|
149
319
|
{ "content" => { "application/json" => content } }
|
|
150
320
|
end
|
|
151
321
|
|
|
322
|
+
# swagger_meta[:request_schema] accepts "Name" or { name:, content_type: }.
|
|
323
|
+
def request_schema_parts(req_schema)
|
|
324
|
+
if req_schema.is_a?(Hash)
|
|
325
|
+
[(req_schema[:name] || req_schema["name"]).to_s,
|
|
326
|
+
(req_schema[:content_type] || req_schema["content_type"] || "application/json").to_s]
|
|
327
|
+
else
|
|
328
|
+
[req_schema.to_s, "application/json"]
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def build_responses(meta, ref, ctx)
|
|
333
|
+
return meta[:responses] if meta[:responses]
|
|
334
|
+
|
|
335
|
+
responses = model_or_default_responses(ref, meta[:model_list])
|
|
336
|
+
|
|
337
|
+
# Registered response schemas ($ref) — explicit and authoritative.
|
|
338
|
+
if (resp_schemas = meta[:response_schemas])
|
|
339
|
+
resp_schemas.each do |status, spec|
|
|
340
|
+
name, is_list = response_schema_parts(spec)
|
|
341
|
+
ctx[:ref_schemas] << name unless ctx[:ref_schemas].include?(name)
|
|
342
|
+
sref = "#/components/schemas/#{name}"
|
|
343
|
+
schema = is_list ? { "type" => "array", "items" => { "$ref" => sref } } : { "$ref" => sref }
|
|
344
|
+
responses[status.to_s] = {
|
|
345
|
+
"description" => status.to_s.start_with?("2") ? "Successful response" : "Response",
|
|
346
|
+
"content" => { "application/json" => { "schema" => schema } }
|
|
347
|
+
}
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
responses
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# A response-schema entry is "Name", { name:, list: } or [name, is_list].
|
|
355
|
+
def response_schema_parts(spec)
|
|
356
|
+
case spec
|
|
357
|
+
when Hash
|
|
358
|
+
[(spec[:name] || spec["name"]).to_s,
|
|
359
|
+
!!(spec[:list] || spec["list"] || spec[:is_list] || spec["is_list"])]
|
|
360
|
+
when Array
|
|
361
|
+
[spec[0].to_s, !!spec[1]]
|
|
362
|
+
else
|
|
363
|
+
[spec.to_s, false]
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
152
367
|
def model_or_default_responses(ref, model_list)
|
|
153
368
|
return default_responses unless ref
|
|
154
369
|
|
data/lib/tina4/version.rb
CHANGED