rage-rb 1.10.1 → 1.12.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile +2 -0
- data/README.md +6 -2
- data/lib/rage/cable/adapters/base.rb +16 -0
- data/lib/rage/cable/adapters/redis.rb +127 -0
- data/lib/rage/cable/cable.rb +23 -5
- data/lib/rage/cable/channel.rb +1 -1
- data/lib/rage/cable/protocol/actioncable_v1_json.rb +32 -7
- data/lib/rage/code_loader.rb +8 -0
- data/lib/rage/configuration.rb +50 -0
- data/lib/rage/controller/api.rb +120 -29
- data/lib/rage/cookies.rb +1 -1
- data/lib/rage/ext/setup.rb +6 -4
- data/lib/rage/openapi/builder.rb +85 -0
- data/lib/rage/openapi/collector.rb +44 -0
- data/lib/rage/openapi/converter.rb +141 -0
- data/lib/rage/openapi/index.html.erb +22 -0
- data/lib/rage/openapi/nodes/method.rb +27 -0
- data/lib/rage/openapi/nodes/parent.rb +16 -0
- data/lib/rage/openapi/nodes/root.rb +56 -0
- data/lib/rage/openapi/openapi.rb +146 -0
- data/lib/rage/openapi/parser.rb +204 -0
- data/lib/rage/openapi/parsers/ext/active_record.rb +62 -0
- data/lib/rage/openapi/parsers/ext/alba.rb +285 -0
- data/lib/rage/openapi/parsers/request.rb +18 -0
- data/lib/rage/openapi/parsers/response.rb +19 -0
- data/lib/rage/openapi/parsers/shared_reference.rb +25 -0
- data/lib/rage/openapi/parsers/yaml.rb +66 -0
- data/lib/rage/router/backend.rb +1 -0
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +5 -0
- metadata +19 -2
data/lib/rage/controller/api.rb
CHANGED
@@ -11,13 +11,10 @@ class RageController::API
|
|
11
11
|
def __register_action(action)
|
12
12
|
raise Rage::Errors::RouterError, "The action '#{action}' could not be found for #{self}" unless method_defined?(action)
|
13
13
|
|
14
|
-
|
15
|
-
filtered_before_actions = @__before_actions.select do |h|
|
16
|
-
(!h[:only] || h[:only].include?(action)) &&
|
17
|
-
(!h[:except] || !h[:except].include?(action))
|
18
|
-
end
|
14
|
+
around_actions_total = 0
|
19
15
|
|
20
|
-
|
16
|
+
before_actions_chunk = if @__before_actions
|
17
|
+
lines = __before_actions_for(action).map do |h|
|
21
18
|
condition = if h[:if] && h[:unless]
|
22
19
|
"if #{h[:if]} && !#{h[:unless]}"
|
23
20
|
elsif h[:if]
|
@@ -26,10 +23,30 @@ class RageController::API
|
|
26
23
|
"unless #{h[:unless]}"
|
27
24
|
end
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
if h[:around]
|
27
|
+
around_actions_total += 1
|
28
|
+
|
29
|
+
if condition
|
30
|
+
<<~RUBY
|
31
|
+
__should_apply_around_action = #{condition}
|
32
|
+
!@__before_callback_rendered
|
33
|
+
end
|
34
|
+
#{h[:wrapper]}(__should_apply_around_action) do
|
35
|
+
RUBY
|
36
|
+
else
|
37
|
+
<<~RUBY
|
38
|
+
__should_apply_around_action = !@__before_callback_rendered
|
39
|
+
#{h[:wrapper]}(__should_apply_around_action) do
|
40
|
+
RUBY
|
41
|
+
end
|
42
|
+
else
|
43
|
+
<<~RUBY
|
44
|
+
unless @__before_callback_rendered
|
45
|
+
#{h[:name]} #{condition}
|
46
|
+
@__before_callback_rendered = true if @__rendered
|
47
|
+
end
|
48
|
+
RUBY
|
49
|
+
end
|
33
50
|
end
|
34
51
|
|
35
52
|
lines.join("\n")
|
@@ -37,13 +54,10 @@ class RageController::API
|
|
37
54
|
""
|
38
55
|
end
|
39
56
|
|
40
|
-
|
41
|
-
filtered_after_actions = @__after_actions.select do |h|
|
42
|
-
(!h[:only] || h[:only].include?(action)) &&
|
43
|
-
(!h[:except] || !h[:except].include?(action))
|
44
|
-
end
|
57
|
+
around_actions_end_chunk = around_actions_total.times.reduce("") { |memo| memo + "end\n" }
|
45
58
|
|
46
|
-
|
59
|
+
after_actions_chunk = if @__after_actions
|
60
|
+
lines = __after_actions_for(action).map do |h|
|
47
61
|
condition = if h[:if] && h[:unless]
|
48
62
|
"if #{h[:if]} && !#{h[:unless]}"
|
49
63
|
elsif h[:if]
|
@@ -106,12 +120,15 @@ class RageController::API
|
|
106
120
|
|
107
121
|
#{wrap_parameters_chunk}
|
108
122
|
#{before_actions_chunk}
|
109
|
-
#{action}
|
123
|
+
#{action} unless @__before_callback_rendered
|
124
|
+
#{around_actions_end_chunk}
|
110
125
|
|
111
126
|
#{if !after_actions_chunk.empty?
|
112
127
|
<<~RUBY
|
113
|
-
@
|
114
|
-
|
128
|
+
unless @__before_callback_rendered
|
129
|
+
@__rendered = true
|
130
|
+
#{after_actions_chunk}
|
131
|
+
end
|
115
132
|
RUBY
|
116
133
|
end}
|
117
134
|
|
@@ -161,13 +178,29 @@ class RageController::API
|
|
161
178
|
end
|
162
179
|
|
163
180
|
# @private
|
164
|
-
@@
|
181
|
+
@@__dynamic_name_seed = ("a".."i").to_a.permutation
|
182
|
+
|
183
|
+
# @private
|
184
|
+
# define a method based on a block
|
185
|
+
def define_dynamic_method(block)
|
186
|
+
name = @@__dynamic_name_seed.next.join
|
187
|
+
define_method("__rage_dynamic_#{name}", block)
|
188
|
+
end
|
165
189
|
|
166
190
|
# @private
|
167
|
-
# define
|
168
|
-
def
|
169
|
-
name = @@
|
170
|
-
|
191
|
+
# define a method that will call a specified method if a condition is `true` or yield if `false`
|
192
|
+
def define_maybe_yield(method_name)
|
193
|
+
name = @@__dynamic_name_seed.next.join
|
194
|
+
|
195
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
196
|
+
def __rage_dynamic_#{name}(condition)
|
197
|
+
if condition
|
198
|
+
#{method_name} { yield }
|
199
|
+
else
|
200
|
+
yield
|
201
|
+
end
|
202
|
+
end
|
203
|
+
RUBY
|
171
204
|
end
|
172
205
|
|
173
206
|
############
|
@@ -193,7 +226,7 @@ class RageController::API
|
|
193
226
|
def rescue_from(*klasses, with: nil, &block)
|
194
227
|
unless with
|
195
228
|
if block_given?
|
196
|
-
with =
|
229
|
+
with = define_dynamic_method(block)
|
197
230
|
else
|
198
231
|
raise ArgumentError, "No handler provided. Pass the `with` keyword argument or provide a block."
|
199
232
|
end
|
@@ -249,6 +282,39 @@ class RageController::API
|
|
249
282
|
end
|
250
283
|
end
|
251
284
|
|
285
|
+
# Register a new `around_action` hook. Calls with the same `action_name` will overwrite the previous ones.
|
286
|
+
#
|
287
|
+
# @param action_name [Symbol, nil] the name of the callback to add
|
288
|
+
# @param [Hash] opts action options
|
289
|
+
# @option opts [Symbol, Array<Symbol>] :only restrict the callback to run only for specific actions
|
290
|
+
# @option opts [Symbol, Array<Symbol>] :except restrict the callback to run for all actions except specified
|
291
|
+
# @option opts [Symbol, Proc] :if only run the callback if the condition is true
|
292
|
+
# @option opts [Symbol, Proc] :unless only run the callback if the condition is false
|
293
|
+
# @example
|
294
|
+
# around_action :wrap_in_transaction
|
295
|
+
#
|
296
|
+
# def wrap_in_transaction
|
297
|
+
# ActiveRecord::Base.transaction do
|
298
|
+
# yield
|
299
|
+
# end
|
300
|
+
# end
|
301
|
+
def around_action(action_name = nil, **opts, &block)
|
302
|
+
action = prepare_action_params(action_name, **opts, &block)
|
303
|
+
action.merge!(around: true, wrapper: define_maybe_yield(action[:name]))
|
304
|
+
|
305
|
+
if @__before_actions && @__before_actions.frozen?
|
306
|
+
@__before_actions = @__before_actions.dup
|
307
|
+
end
|
308
|
+
|
309
|
+
if @__before_actions.nil?
|
310
|
+
@__before_actions = [action]
|
311
|
+
elsif (i = @__before_actions.find_index { |a| a[:name] == action_name })
|
312
|
+
@__before_actions[i] = action
|
313
|
+
else
|
314
|
+
@__before_actions << action
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
252
318
|
# Register a new `after_action` hook. Calls with the same `action_name` will overwrite the previous ones.
|
253
319
|
#
|
254
320
|
# @param action_name [Symbol, nil] the name of the callback to add
|
@@ -283,7 +349,7 @@ class RageController::API
|
|
283
349
|
# @example
|
284
350
|
# skip_before_action :find_photo, only: :create
|
285
351
|
def skip_before_action(action_name, only: nil, except: nil)
|
286
|
-
i = @__before_actions&.find_index { |a| a[:name] == action_name }
|
352
|
+
i = @__before_actions&.find_index { |a| a[:name] == action_name && !a[:around] }
|
287
353
|
raise ArgumentError, "The following action was specified to be skipped but couldn't be found: #{self}##{action_name}" unless i
|
288
354
|
|
289
355
|
@__before_actions = @__before_actions.dup if @__before_actions.frozen?
|
@@ -319,12 +385,37 @@ class RageController::API
|
|
319
385
|
@__wrap_parameters_options = { include:, exclude: }
|
320
386
|
end
|
321
387
|
|
388
|
+
# @private
|
389
|
+
def __before_action_exists?(name)
|
390
|
+
@__before_actions.any? { |h| h[:name] == name && !h[:around] }
|
391
|
+
end
|
392
|
+
|
393
|
+
# @private
|
394
|
+
def __before_actions_for(action_name)
|
395
|
+
return [] unless @__before_actions
|
396
|
+
|
397
|
+
@__before_actions.select do |h|
|
398
|
+
(!h[:only] || h[:only].include?(action_name)) &&
|
399
|
+
(!h[:except] || !h[:except].include?(action_name))
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# @private
|
404
|
+
def __after_actions_for(action_name)
|
405
|
+
return [] unless @__after_actions
|
406
|
+
|
407
|
+
@__after_actions.select do |h|
|
408
|
+
(!h[:only] || h[:only].include?(action_name)) &&
|
409
|
+
(!h[:except] || !h[:except].include?(action_name))
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
322
413
|
private
|
323
414
|
|
324
415
|
# used by `before_action` and `after_action`
|
325
416
|
def prepare_action_params(action_name = nil, **opts, &block)
|
326
417
|
if block_given?
|
327
|
-
action_name =
|
418
|
+
action_name = define_dynamic_method(block)
|
328
419
|
elsif action_name.nil?
|
329
420
|
raise ArgumentError, "No handler provided. Pass the `action_name` parameter or provide a block."
|
330
421
|
end
|
@@ -339,8 +430,8 @@ class RageController::API
|
|
339
430
|
unless: _unless
|
340
431
|
}
|
341
432
|
|
342
|
-
action[:if] =
|
343
|
-
action[:unless] =
|
433
|
+
action[:if] = define_dynamic_method(action[:if]) if action[:if].is_a?(Proc)
|
434
|
+
action[:unless] = define_dynamic_method(action[:unless]) if action[:unless].is_a?(Proc)
|
344
435
|
|
345
436
|
action
|
346
437
|
end
|
data/lib/rage/cookies.rb
CHANGED
data/lib/rage/ext/setup.rb
CHANGED
@@ -7,11 +7,13 @@ if defined?(ActiveSupport::IsolatedExecutionState)
|
|
7
7
|
ActiveSupport::IsolatedExecutionState.isolation_level = :fiber
|
8
8
|
end
|
9
9
|
|
10
|
-
# patch Active Record 6.0 to accept the role argument
|
11
|
-
|
10
|
+
# patch Active Record 6.0 to accept the role argument;
|
11
|
+
# for Active Record 6.1 and 7.0 with `legacy_connection_handling == false` this also
|
12
|
+
# allows to correctly handle the `:all` argument by ignoring it
|
13
|
+
if defined?(ActiveRecord) && ActiveRecord.version < Gem::Version.create("7.1")
|
12
14
|
%i(active_connections? connection_pool_list clear_active_connections!).each do |m|
|
13
|
-
ActiveRecord::Base.connection_handler.define_singleton_method(m) do |
|
14
|
-
super()
|
15
|
+
ActiveRecord::Base.connection_handler.define_singleton_method(m) do |role = nil|
|
16
|
+
role == :all ? super() : super(role)
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Build OpenAPI specification for the app. Consists of three steps:
|
5
|
+
#
|
6
|
+
# * `Rage::OpenAPI::Builder` - build a tree of action nodes;
|
7
|
+
# * `Rage::OpenAPI::Parser` - parse OpenAPI tags and save the result into the nodes;
|
8
|
+
# * `Rage::OpenAPI::Converter` - convert the tree into an OpenAPI spec;
|
9
|
+
#
|
10
|
+
class Rage::OpenAPI::Builder
|
11
|
+
class ParsingError < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param namespace [String, Module]
|
15
|
+
def initialize(namespace: nil)
|
16
|
+
@namespace = namespace.to_s if namespace
|
17
|
+
|
18
|
+
@collectors_cache = {}
|
19
|
+
@nodes = Rage::OpenAPI::Nodes::Root.new
|
20
|
+
@routes = Rage.__router.routes.group_by { |route| route[:meta][:controller_class] }
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
parser = Rage::OpenAPI::Parser.new
|
25
|
+
|
26
|
+
@routes.each do |controller, routes|
|
27
|
+
next if skip_controller?(controller)
|
28
|
+
|
29
|
+
parent_nodes = fetch_ancestors(controller).map do |klass|
|
30
|
+
@nodes.new_parent_node(klass) { |node| parser.parse_dangling_comments(node, parse_class(klass).dangling_comments) }
|
31
|
+
end
|
32
|
+
|
33
|
+
routes.each do |route|
|
34
|
+
action = route[:meta][:action]
|
35
|
+
|
36
|
+
method_comments = fetch_ancestors(controller).filter_map { |klass|
|
37
|
+
parse_class(klass).method_comments(action)
|
38
|
+
}.first
|
39
|
+
|
40
|
+
method_node = @nodes.new_method_node(controller, action, parent_nodes)
|
41
|
+
method_node.http_method, method_node.http_path = route[:method], route[:path]
|
42
|
+
|
43
|
+
parser.parse_method_comments(method_node, method_comments)
|
44
|
+
end
|
45
|
+
|
46
|
+
rescue ParsingError
|
47
|
+
Rage::OpenAPI.__log_warn "skipping #{controller.name} because of parsing error"
|
48
|
+
next
|
49
|
+
end
|
50
|
+
|
51
|
+
Rage::OpenAPI::Converter.new(@nodes).run
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def skip_controller?(controller)
|
57
|
+
should_skip_controller = controller.nil? || !controller.ancestors.include?(RageController::API)
|
58
|
+
should_skip_controller ||= !controller.name.start_with?(@namespace) if @namespace
|
59
|
+
|
60
|
+
should_skip_controller
|
61
|
+
end
|
62
|
+
|
63
|
+
def fetch_ancestors(controller)
|
64
|
+
controller.ancestors.take_while { |klass| klass != RageController::API }
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_class(klass)
|
68
|
+
@collectors_cache[klass] ||= begin
|
69
|
+
source_path, _ = Object.const_source_location(klass.name)
|
70
|
+
ast = Prism.parse_file(source_path)
|
71
|
+
|
72
|
+
raise ParsingError if ast.errors.any?
|
73
|
+
|
74
|
+
# save the "comment => file" association
|
75
|
+
ast.comments.each do |comment|
|
76
|
+
comment.location.define_singleton_method(:__source_path) { source_path }
|
77
|
+
end
|
78
|
+
|
79
|
+
collector = Rage::OpenAPI::Collector.new(ast.comments)
|
80
|
+
ast.value.accept(collector)
|
81
|
+
|
82
|
+
collector
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Collect all global comments or comments attached to methods in a class.
|
5
|
+
# At this point we don't care whether these are Rage OpenAPI comments or not.
|
6
|
+
#
|
7
|
+
class Rage::OpenAPI::Collector < Prism::Visitor
|
8
|
+
# @param comments [Array<Prism::InlineComment>]
|
9
|
+
def initialize(comments)
|
10
|
+
@comments = comments.dup
|
11
|
+
@method_comments = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def dangling_comments
|
15
|
+
@comments
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_comments(method_name)
|
19
|
+
@method_comments[method_name.to_s]
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_def_node(node)
|
23
|
+
method_comments = []
|
24
|
+
start_line = node.location.start_line - 1
|
25
|
+
|
26
|
+
loop do
|
27
|
+
comment_i = @comments.find_index { |comment| comment.location.start_line == start_line }
|
28
|
+
if comment_i
|
29
|
+
comment = @comments.delete_at(comment_i)
|
30
|
+
method_comments << comment
|
31
|
+
start_line -= 1
|
32
|
+
end
|
33
|
+
|
34
|
+
break unless comment
|
35
|
+
end
|
36
|
+
|
37
|
+
@method_comments[node.name.to_s] = method_comments.reverse
|
38
|
+
|
39
|
+
# reject comments inside methods
|
40
|
+
@comments.reject! do |comment|
|
41
|
+
comment.location.start_line >= node.location.start_line && comment.location.start_line <= node.location.end_line
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::OpenAPI::Converter
|
4
|
+
# @param nodes [Rage::OpenAPI::Nodes::Root]
|
5
|
+
def initialize(nodes)
|
6
|
+
@nodes = nodes
|
7
|
+
@used_tags = Set.new
|
8
|
+
@used_security_schemes = Set.new
|
9
|
+
|
10
|
+
@spec = {
|
11
|
+
"openapi" => "3.0.0",
|
12
|
+
"info" => {},
|
13
|
+
"components" => {},
|
14
|
+
"tags" => [],
|
15
|
+
"paths" => {}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
@spec["info"] = {
|
21
|
+
"version" => @nodes.version || "1.0.0",
|
22
|
+
"title" => @nodes.title || build_app_name
|
23
|
+
}
|
24
|
+
|
25
|
+
@spec["paths"] = @nodes.leaves.each_with_object({}) do |node, memo|
|
26
|
+
next if node.private || node.parents.any?(&:private)
|
27
|
+
|
28
|
+
path_parameters = []
|
29
|
+
path = node.http_path.gsub(/:(\w+)/) do
|
30
|
+
path_parameters << $1
|
31
|
+
"{#{$1}}"
|
32
|
+
end
|
33
|
+
|
34
|
+
unless memo.key?(path)
|
35
|
+
memo[path] = {}
|
36
|
+
path_parameters.each do |parameter|
|
37
|
+
(memo[path]["parameters"] ||= []) << {
|
38
|
+
"in" => "path",
|
39
|
+
"name" => parameter,
|
40
|
+
"required" => true,
|
41
|
+
"schema" => { "type" => parameter.end_with?("id") ? "integer" : "string" }
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
method = node.http_method.downcase
|
47
|
+
memo[path][method] = {
|
48
|
+
"summary" => node.summary || "",
|
49
|
+
"description" => node.description&.join(" ") || "",
|
50
|
+
"deprecated" => !!(node.deprecated || node.parents.any?(&:deprecated)),
|
51
|
+
"security" => build_security(node),
|
52
|
+
"tags" => build_tags(node)
|
53
|
+
}
|
54
|
+
|
55
|
+
responses = node.parents.reverse.map(&:responses).reduce(&:merge).merge(node.responses)
|
56
|
+
|
57
|
+
memo[path][method]["responses"] = if responses.any?
|
58
|
+
responses.each_with_object({}) do |(status, response), memo|
|
59
|
+
memo[status] = if response.nil?
|
60
|
+
{ "description" => "" }
|
61
|
+
elsif response.key?("$ref") && response["$ref"].start_with?("#/components/responses")
|
62
|
+
response
|
63
|
+
else
|
64
|
+
{ "description" => "", "content" => { "application/json" => { "schema" => response } } }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
else
|
68
|
+
{ "200" => { "description" => "" } }
|
69
|
+
end
|
70
|
+
|
71
|
+
if node.request
|
72
|
+
if node.request.key?("$ref") && node.request["$ref"].start_with?("#/components/requestBodies")
|
73
|
+
memo[path][method]["requestBody"] = node.request
|
74
|
+
else
|
75
|
+
memo[path][method]["requestBody"] = { "content" => { "application/json" => { "schema" => node.request } } }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if @used_security_schemes.any?
|
81
|
+
@spec["components"]["securitySchemes"] = @used_security_schemes.each_with_object({}) do |auth_entry, memo|
|
82
|
+
memo[auth_entry[:name]] = auth_entry[:definition]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
if (shared_components = Rage::OpenAPI.__shared_components["components"])
|
87
|
+
shared_components.each do |definition_type, definitions|
|
88
|
+
(@spec["components"][definition_type] ||= {}).merge!(definitions)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
@spec["tags"] = @used_tags.sort.map { |tag| { "name" => tag } }
|
93
|
+
|
94
|
+
@spec
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def build_app_name
|
100
|
+
basename = Rage.root.basename.to_s
|
101
|
+
basename.capitalize.gsub(/[\s\-_]([a-zA-Z0-9]+)/) { " #{$1.capitalize}" }
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_security(node)
|
105
|
+
available_before_actions = node.controller.__before_actions_for(node.action.to_sym)
|
106
|
+
|
107
|
+
node.auth.filter_map do |auth_entry|
|
108
|
+
if available_before_actions.any? { |action_entry| action_entry[:name] == auth_entry[:method].to_sym }
|
109
|
+
auth_name = auth_entry[:name].gsub(/[^A-Za-z0-9\-._]/, "")
|
110
|
+
@used_security_schemes << auth_entry.merge(name: auth_name)
|
111
|
+
|
112
|
+
{ auth_name => [] }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def build_tags(node)
|
118
|
+
controller_name = node.controller.name.sub(/Controller$/, "")
|
119
|
+
namespace_i = controller_name.rindex("::")
|
120
|
+
|
121
|
+
if namespace_i
|
122
|
+
module_name, class_name = controller_name[0...namespace_i], controller_name[namespace_i + 2..]
|
123
|
+
else
|
124
|
+
module_name, class_name = "", controller_name
|
125
|
+
end
|
126
|
+
|
127
|
+
tag = if module_name =~ /::(V\d+)/
|
128
|
+
"#{$1.downcase}/#{class_name}"
|
129
|
+
else
|
130
|
+
class_name
|
131
|
+
end
|
132
|
+
|
133
|
+
if (custom_tag_resolver = Rage.config.openapi.tag_resolver)
|
134
|
+
tag = custom_tag_resolver.call(node.controller, node.action.to_sym, tag)
|
135
|
+
end
|
136
|
+
|
137
|
+
Array(tag).tap do |node_tags|
|
138
|
+
@used_tags += node_tags
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8" />
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
6
|
+
<meta name="description" content="SwaggerUI" />
|
7
|
+
<title>SwaggerUI</title>
|
8
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@^5/swagger-ui.css" />
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<div id="swagger-ui"></div>
|
12
|
+
<script src="https://unpkg.com/swagger-ui-dist@^5/swagger-ui-bundle.js" crossorigin></script>
|
13
|
+
<script>
|
14
|
+
window.onload = () => {
|
15
|
+
window.ui = SwaggerUIBundle({
|
16
|
+
url: '<%= spec_url %>',
|
17
|
+
dom_id: '#swagger-ui',
|
18
|
+
});
|
19
|
+
};
|
20
|
+
</script>
|
21
|
+
</body>
|
22
|
+
</html>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::OpenAPI::Nodes::Method
|
4
|
+
attr_reader :controller, :action, :parents
|
5
|
+
attr_accessor :http_method, :http_path, :summary, :tag, :deprecated, :private, :description,
|
6
|
+
:request, :responses, :parameters
|
7
|
+
|
8
|
+
# @param controller [RageController::API]
|
9
|
+
# @param action [String]
|
10
|
+
# @param parents [Array<Rage::OpenAPI::Nodes::Parent>]
|
11
|
+
def initialize(controller, action, parents)
|
12
|
+
@controller = controller
|
13
|
+
@action = action
|
14
|
+
@parents = parents
|
15
|
+
|
16
|
+
@responses = {}
|
17
|
+
@parameters = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def root
|
21
|
+
@parents[0].root
|
22
|
+
end
|
23
|
+
|
24
|
+
def auth
|
25
|
+
@parents.flat_map(&:auth)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::OpenAPI::Nodes::Parent
|
4
|
+
attr_reader :root, :controller
|
5
|
+
attr_accessor :deprecated, :private, :auth, :responses
|
6
|
+
|
7
|
+
# @param root [Rage::OpenAPI::Nodes::Root]
|
8
|
+
# @param controller [RageController::API]
|
9
|
+
def initialize(root, controller)
|
10
|
+
@root = root
|
11
|
+
@controller = controller
|
12
|
+
|
13
|
+
@auth = []
|
14
|
+
@responses = {}
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Represents a tree of method nodes. The tree consists of:
|
5
|
+
#
|
6
|
+
# * a root node;
|
7
|
+
# * method nodes, each of which represents an action in a controller;
|
8
|
+
# * parent nodes attached to one or several method nodes;
|
9
|
+
#
|
10
|
+
# A method node together with its parent nodes represent a complete inheritance chain.
|
11
|
+
#
|
12
|
+
# Nodes::Root
|
13
|
+
# |
|
14
|
+
# Nodes::Parent<ApplicationController>
|
15
|
+
# |
|
16
|
+
# Nodes::Parent<Api::BaseController>
|
17
|
+
# / \
|
18
|
+
# Nodes::Parent<Api::V1::UsersController> Nodes::Parent<Api::V2::UsersController>
|
19
|
+
# / \ |
|
20
|
+
# Nodes::Method<index> Nodes::Method<show> Nodes::Method<show>
|
21
|
+
#
|
22
|
+
class Rage::OpenAPI::Nodes::Root
|
23
|
+
attr_reader :leaves
|
24
|
+
attr_accessor :version, :title
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@parent_nodes_cache = {}
|
28
|
+
@leaves = []
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Array<Rage::OpenAPI::Nodes::Parent>]
|
32
|
+
def parent_nodes
|
33
|
+
@parent_nodes_cache.values
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param controller [RageController::API]
|
37
|
+
# @param action [String]
|
38
|
+
# @param parent_nodes [Array<Rage::OpenAPI::Nodes::Parent>]
|
39
|
+
# @return [Rage::OpenAPI::Nodes::Method]
|
40
|
+
def new_method_node(controller, action, parent_nodes)
|
41
|
+
node = Rage::OpenAPI::Nodes::Method.new(controller, action, parent_nodes)
|
42
|
+
@leaves << node
|
43
|
+
|
44
|
+
node
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param controller [RageController::API]
|
48
|
+
# @return [Rage::OpenAPI::Nodes::Parent]
|
49
|
+
def new_parent_node(controller)
|
50
|
+
@parent_nodes_cache[controller] ||= begin
|
51
|
+
node = Rage::OpenAPI::Nodes::Parent.new(self, controller)
|
52
|
+
yield(node)
|
53
|
+
node
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|