rage-rb 1.10.1 → 1.11.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 +10 -0
- data/Gemfile +1 -0
- data/README.md +5 -0
- data/lib/rage/code_loader.rb +8 -0
- data/lib/rage/configuration.rb +23 -0
- data/lib/rage/controller/api.rb +27 -12
- data/lib/rage/ext/setup.rb +6 -4
- data/lib/rage/openapi/builder.rb +84 -0
- data/lib/rage/openapi/collector.rb +43 -0
- data/lib/rage/openapi/converter.rb +138 -0
- data/lib/rage/openapi/index.html.erb +22 -0
- data/lib/rage/openapi/nodes/method.rb +24 -0
- data/lib/rage/openapi/nodes/parent.rb +13 -0
- data/lib/rage/openapi/nodes/root.rb +49 -0
- data/lib/rage/openapi/openapi.rb +146 -0
- data/lib/rage/openapi/parser.rb +193 -0
- data/lib/rage/openapi/parsers/ext/active_record.rb +62 -0
- data/lib/rage/openapi/parsers/ext/alba.rb +281 -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 +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04ca7e51d4117d534058db889d82d6ce86af9c9edf389a03711baffa3a8bc484
|
4
|
+
data.tar.gz: 347fa6296d6fa65d99125146b7f82921a9ae2b99836d6f0191fc31dc135896c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8490a009689d8dbd9c98f47b01701e80dda65fe96dfa991540adc59dc0dd68859690263a15a6cb3f2f2ad23aa300c8bcf4eb6aed468a69b9997191af15806bae
|
7
|
+
data.tar.gz: 7b5d887f78d0d102a9ea92027fe531d61870bed70828a56a4b79c51540c560553dd907c7c27a72867b4adbc241e4e1a4a529b84db991786d6845b457c229bfb2
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.11.0] - 2024-12-18
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- `Rage::OpenAPI` (#109).
|
8
|
+
|
9
|
+
### Fixed
|
10
|
+
|
11
|
+
- Correctly handle ActiveRecord connections in the environments with `legacy_connection_handling == false` (#108).
|
12
|
+
|
3
13
|
## [1.10.1] - 2024-09-17
|
4
14
|
|
5
15
|
### Fixed
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -46,6 +46,11 @@ Start coding!
|
|
46
46
|
|
47
47
|
This gem is designed to be a drop-in replacement for Rails in API mode. Public API is expected to fully match Rails.
|
48
48
|
|
49
|
+
A Rage application can operate in two modes:
|
50
|
+
|
51
|
+
* **Rails Mode**: Integrate Rage into an existing Rails application to improve throughput and better handle traffic spikes. For more information, see [Rails Integration](https://github.com/rage-rb/rage/wiki/Rails-integration).
|
52
|
+
* **Standalone Mode**: Build high-performance services with minimal setup using Rage. To get started, run `rage new --help` for more details.
|
53
|
+
|
49
54
|
Check out in-depth API docs for more information:
|
50
55
|
|
51
56
|
- [Controller API](https://rage-rb.pages.dev/RageController/API)
|
data/lib/rage/code_loader.rb
CHANGED
@@ -37,6 +37,10 @@ class Rage::CodeLoader
|
|
37
37
|
unless Rage.autoload?(:Cable) # the `Cable` component is loaded
|
38
38
|
Rage::Cable.__router.reset
|
39
39
|
end
|
40
|
+
|
41
|
+
unless Rage.autoload?(:OpenAPI) # the `OpenAPI` component is loaded
|
42
|
+
Rage::OpenAPI.__reset_data_cache
|
43
|
+
end
|
40
44
|
end
|
41
45
|
|
42
46
|
# in Rails mode - reset the routes; everything else will be done by Rails
|
@@ -49,6 +53,10 @@ class Rage::CodeLoader
|
|
49
53
|
unless Rage.autoload?(:Cable) # the `Cable` component is loaded
|
50
54
|
Rage::Cable.__router.reset
|
51
55
|
end
|
56
|
+
|
57
|
+
unless Rage.autoload?(:OpenAPI) # the `OpenAPI` component is loaded
|
58
|
+
Rage::OpenAPI.__reset_data_cache
|
59
|
+
end
|
52
60
|
end
|
53
61
|
|
54
62
|
def reloading?
|
data/lib/rage/configuration.rb
CHANGED
@@ -122,6 +122,17 @@
|
|
122
122
|
#
|
123
123
|
# > Allows requests from any origin.
|
124
124
|
#
|
125
|
+
# # OpenAPI Configuration
|
126
|
+
# • _config.openapi.tag_resolver_
|
127
|
+
#
|
128
|
+
# > Specifies the proc to build tags for API operations. The proc accepts the controller class, the symbol name of the action, and the default tag built by Rage.
|
129
|
+
#
|
130
|
+
# > ```ruby
|
131
|
+
# config.openapi.tag_resolver = proc do |controller, action, default_tag|
|
132
|
+
# # ...
|
133
|
+
# end
|
134
|
+
# > ```
|
135
|
+
#
|
125
136
|
# # Transient Settings
|
126
137
|
#
|
127
138
|
# The settings described in this section should be configured using **environment variables** and are either temporary or will become the default in the future.
|
@@ -179,6 +190,10 @@ class Rage::Configuration
|
|
179
190
|
@public_file_server ||= PublicFileServer.new
|
180
191
|
end
|
181
192
|
|
193
|
+
def openapi
|
194
|
+
@openapi ||= OpenAPI.new
|
195
|
+
end
|
196
|
+
|
182
197
|
def internal
|
183
198
|
@internal ||= Internal.new
|
184
199
|
end
|
@@ -218,6 +233,10 @@ class Rage::Configuration
|
|
218
233
|
@middlewares = (@middlewares[0..index] + [[new_middleware, args, block]] + @middlewares[index + 1..]).uniq(&:first)
|
219
234
|
end
|
220
235
|
|
236
|
+
def include?(middleware)
|
237
|
+
!!find_middleware_index(middleware) rescue false
|
238
|
+
end
|
239
|
+
|
221
240
|
private
|
222
241
|
|
223
242
|
def find_middleware_index(middleware)
|
@@ -264,6 +283,10 @@ class Rage::Configuration
|
|
264
283
|
attr_accessor :enabled
|
265
284
|
end
|
266
285
|
|
286
|
+
class OpenAPI
|
287
|
+
attr_accessor :tag_resolver
|
288
|
+
end
|
289
|
+
|
267
290
|
# @private
|
268
291
|
class Internal
|
269
292
|
attr_accessor :rails_mode
|
data/lib/rage/controller/api.rb
CHANGED
@@ -12,12 +12,7 @@ class RageController::API
|
|
12
12
|
raise Rage::Errors::RouterError, "The action '#{action}' could not be found for #{self}" unless method_defined?(action)
|
13
13
|
|
14
14
|
before_actions_chunk = if @__before_actions
|
15
|
-
|
16
|
-
(!h[:only] || h[:only].include?(action)) &&
|
17
|
-
(!h[:except] || !h[:except].include?(action))
|
18
|
-
end
|
19
|
-
|
20
|
-
lines = filtered_before_actions.map do |h|
|
15
|
+
lines = __before_actions_for(action).map do |h|
|
21
16
|
condition = if h[:if] && h[:unless]
|
22
17
|
"if #{h[:if]} && !#{h[:unless]}"
|
23
18
|
elsif h[:if]
|
@@ -38,12 +33,7 @@ class RageController::API
|
|
38
33
|
end
|
39
34
|
|
40
35
|
after_actions_chunk = if @__after_actions
|
41
|
-
|
42
|
-
(!h[:only] || h[:only].include?(action)) &&
|
43
|
-
(!h[:except] || !h[:except].include?(action))
|
44
|
-
end
|
45
|
-
|
46
|
-
lines = filtered_after_actions.map! do |h|
|
36
|
+
lines = __after_actions_for(action).map do |h|
|
47
37
|
condition = if h[:if] && h[:unless]
|
48
38
|
"if #{h[:if]} && !#{h[:unless]}"
|
49
39
|
elsif h[:if]
|
@@ -319,6 +309,31 @@ class RageController::API
|
|
319
309
|
@__wrap_parameters_options = { include:, exclude: }
|
320
310
|
end
|
321
311
|
|
312
|
+
# @private
|
313
|
+
def __before_action_exists?(name)
|
314
|
+
@__before_actions.any? { |h| h[:name] == name && !h[:around] }
|
315
|
+
end
|
316
|
+
|
317
|
+
# @private
|
318
|
+
def __before_actions_for(action_name)
|
319
|
+
return [] unless @__before_actions
|
320
|
+
|
321
|
+
@__before_actions.select do |h|
|
322
|
+
(!h[:only] || h[:only].include?(action_name)) &&
|
323
|
+
(!h[:except] || !h[:except].include?(action_name))
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# @private
|
328
|
+
def __after_actions_for(action_name)
|
329
|
+
return [] unless @__after_actions
|
330
|
+
|
331
|
+
@__after_actions.select do |h|
|
332
|
+
(!h[:only] || h[:only].include?(action_name)) &&
|
333
|
+
(!h[:except] || !h[:except].include?(action_name))
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
322
337
|
private
|
323
338
|
|
324
339
|
# used by `before_action` and `after_action`
|
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,84 @@
|
|
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
|
+
def initialize(namespace: nil)
|
15
|
+
@namespace = namespace.to_s if namespace
|
16
|
+
|
17
|
+
@collectors_cache = {}
|
18
|
+
@nodes = Rage::OpenAPI::Nodes::Root.new
|
19
|
+
@routes = Rage.__router.routes.group_by { |route| route[:meta][:controller_class] }
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
parser = Rage::OpenAPI::Parser.new
|
24
|
+
|
25
|
+
@routes.each do |controller, routes|
|
26
|
+
next if skip_controller?(controller)
|
27
|
+
|
28
|
+
parent_nodes = fetch_ancestors(controller).map do |klass|
|
29
|
+
@nodes.new_parent_node(klass) { |node| parser.parse_dangling_comments(node, parse_class(klass).dangling_comments) }
|
30
|
+
end
|
31
|
+
|
32
|
+
routes.each do |route|
|
33
|
+
action = route[:meta][:action]
|
34
|
+
|
35
|
+
method_comments = fetch_ancestors(controller).filter_map { |klass|
|
36
|
+
parse_class(klass).method_comments(action)
|
37
|
+
}.first
|
38
|
+
|
39
|
+
method_node = @nodes.new_method_node(controller, action, parent_nodes)
|
40
|
+
method_node.http_method, method_node.http_path = route[:method], route[:path]
|
41
|
+
|
42
|
+
parser.parse_method_comments(method_node, method_comments)
|
43
|
+
end
|
44
|
+
|
45
|
+
rescue ParsingError
|
46
|
+
Rage::OpenAPI.__log_warn "skipping #{controller.name} because of parsing error"
|
47
|
+
next
|
48
|
+
end
|
49
|
+
|
50
|
+
Rage::OpenAPI::Converter.new(@nodes).run
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def skip_controller?(controller)
|
56
|
+
should_skip_controller = controller.nil? || !controller.ancestors.include?(RageController::API)
|
57
|
+
should_skip_controller ||= !controller.name.start_with?(@namespace) if @namespace
|
58
|
+
|
59
|
+
should_skip_controller
|
60
|
+
end
|
61
|
+
|
62
|
+
def fetch_ancestors(controller)
|
63
|
+
controller.ancestors.take_while { |klass| klass != RageController::API }
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_class(klass)
|
67
|
+
@collectors_cache[klass] ||= begin
|
68
|
+
source_path, _ = Object.const_source_location(klass.name)
|
69
|
+
ast = Prism.parse_file(source_path)
|
70
|
+
|
71
|
+
raise ParsingError if ast.errors.any?
|
72
|
+
|
73
|
+
# save the "comment => file" association
|
74
|
+
ast.comments.each do |comment|
|
75
|
+
comment.location.define_singleton_method(:__source_path) { source_path }
|
76
|
+
end
|
77
|
+
|
78
|
+
collector = Rage::OpenAPI::Collector.new(ast.comments)
|
79
|
+
ast.value.accept(collector)
|
80
|
+
|
81
|
+
collector
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,43 @@
|
|
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
|
+
def initialize(comments)
|
9
|
+
@comments = comments.dup
|
10
|
+
@method_comments = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def dangling_comments
|
14
|
+
@comments
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_comments(method_name)
|
18
|
+
@method_comments[method_name.to_s]
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit_def_node(node)
|
22
|
+
method_comments = []
|
23
|
+
start_line = node.location.start_line - 1
|
24
|
+
|
25
|
+
loop do
|
26
|
+
comment_i = @comments.find_index { |comment| comment.location.start_line == start_line }
|
27
|
+
if comment_i
|
28
|
+
comment = @comments.delete_at(comment_i)
|
29
|
+
method_comments << comment
|
30
|
+
start_line -= 1
|
31
|
+
end
|
32
|
+
|
33
|
+
break unless comment
|
34
|
+
end
|
35
|
+
|
36
|
+
@method_comments[node.name.to_s] = method_comments.reverse
|
37
|
+
|
38
|
+
# reject comments inside methods
|
39
|
+
@comments.reject! do |comment|
|
40
|
+
comment.location.start_line >= node.location.start_line && comment.location.start_line <= node.location.end_line
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::OpenAPI::Converter
|
4
|
+
def initialize(nodes)
|
5
|
+
@nodes = nodes
|
6
|
+
@used_tags = Set.new
|
7
|
+
@used_security_schemes = Set.new
|
8
|
+
|
9
|
+
@spec = {
|
10
|
+
"openapi" => "3.0.0",
|
11
|
+
"info" => {},
|
12
|
+
"components" => {},
|
13
|
+
"tags" => [],
|
14
|
+
"paths" => {}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
@spec["info"] = {
|
20
|
+
"version" => @nodes.version || "1.0.0",
|
21
|
+
"title" => @nodes.title || build_app_name
|
22
|
+
}
|
23
|
+
|
24
|
+
@spec["paths"] = @nodes.leaves.each_with_object({}) do |node, memo|
|
25
|
+
next if node.private || node.parents.any?(&:private)
|
26
|
+
|
27
|
+
path_parameters = []
|
28
|
+
path = node.http_path.gsub(/:(\w+)/) do
|
29
|
+
path_parameters << $1
|
30
|
+
"{#{$1}}"
|
31
|
+
end
|
32
|
+
|
33
|
+
unless memo.key?(path)
|
34
|
+
memo[path] = {}
|
35
|
+
path_parameters.each do |parameter|
|
36
|
+
(memo[path]["parameters"] ||= []) << {
|
37
|
+
"in" => "path",
|
38
|
+
"name" => parameter,
|
39
|
+
"required" => true,
|
40
|
+
"schema" => { "type" => parameter.end_with?("id") ? "integer" : "string" }
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
method = node.http_method.downcase
|
46
|
+
memo[path][method] = {
|
47
|
+
"summary" => node.summary || "",
|
48
|
+
"description" => node.description&.join(" ") || "",
|
49
|
+
"deprecated" => !!(node.deprecated || node.parents.any?(&:deprecated)),
|
50
|
+
"security" => build_security(node),
|
51
|
+
"tags" => build_tags(node)
|
52
|
+
}
|
53
|
+
|
54
|
+
memo[path][method]["responses"] = if node.responses.any?
|
55
|
+
node.responses.each_with_object({}) do |(status, response), memo|
|
56
|
+
memo[status] = if response.nil?
|
57
|
+
{ "description" => "" }
|
58
|
+
elsif response.key?("$ref") && response["$ref"].start_with?("#/components/responses")
|
59
|
+
response
|
60
|
+
else
|
61
|
+
{ "description" => "", "content" => { "application/json" => { "schema" => response } } }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
else
|
65
|
+
{ "200" => { "description" => "" } }
|
66
|
+
end
|
67
|
+
|
68
|
+
if node.request
|
69
|
+
if node.request.key?("$ref") && node.request["$ref"].start_with?("#/components/requestBodies")
|
70
|
+
memo[path][method]["requestBody"] = node.request
|
71
|
+
else
|
72
|
+
memo[path][method]["requestBody"] = { "content" => { "application/json" => { "schema" => node.request } } }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if @used_security_schemes.any?
|
78
|
+
@spec["components"]["securitySchemes"] = @used_security_schemes.each_with_object({}) do |auth_entry, memo|
|
79
|
+
memo[auth_entry[:name]] = auth_entry[:definition]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if (shared_components = Rage::OpenAPI.__shared_components["components"])
|
84
|
+
shared_components.each do |definition_type, definitions|
|
85
|
+
(@spec["components"][definition_type] ||= {}).merge!(definitions)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@spec["tags"] = @used_tags.sort.map { |tag| { "name" => tag } }
|
90
|
+
|
91
|
+
@spec
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def build_app_name
|
97
|
+
basename = Rage.root.basename.to_s
|
98
|
+
basename.capitalize.gsub(/[\s\-_]([a-zA-Z0-9]+)/) { " #{$1.capitalize}" }
|
99
|
+
end
|
100
|
+
|
101
|
+
def build_security(node)
|
102
|
+
available_before_actions = node.controller.__before_actions_for(node.action.to_sym)
|
103
|
+
|
104
|
+
node.auth.filter_map do |auth_entry|
|
105
|
+
if available_before_actions.any? { |action_entry| action_entry[:name] == auth_entry[:method].to_sym }
|
106
|
+
auth_name = auth_entry[:name].gsub(/[^A-Za-z0-9\-._]/, "")
|
107
|
+
@used_security_schemes << auth_entry.merge(name: auth_name)
|
108
|
+
|
109
|
+
{ auth_name => [] }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def build_tags(node)
|
115
|
+
controller_name = node.controller.name.sub(/Controller$/, "")
|
116
|
+
namespace_i = controller_name.rindex("::")
|
117
|
+
|
118
|
+
if namespace_i
|
119
|
+
module_name, class_name = controller_name[0...namespace_i], controller_name[namespace_i + 2..]
|
120
|
+
else
|
121
|
+
module_name, class_name = "", controller_name
|
122
|
+
end
|
123
|
+
|
124
|
+
tag = if module_name =~ /::(V\d+)/
|
125
|
+
"#{$1.downcase}/#{class_name}"
|
126
|
+
else
|
127
|
+
class_name
|
128
|
+
end
|
129
|
+
|
130
|
+
if (custom_tag_resolver = Rage.config.openapi.tag_resolver)
|
131
|
+
tag = custom_tag_resolver.call(node.controller, node.action.to_sym, tag)
|
132
|
+
end
|
133
|
+
|
134
|
+
Array(tag).tap do |node_tags|
|
135
|
+
@used_tags += node_tags
|
136
|
+
end
|
137
|
+
end
|
138
|
+
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,24 @@
|
|
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
|
+
def initialize(controller, action, parents)
|
9
|
+
@controller = controller
|
10
|
+
@action = action
|
11
|
+
@parents = parents
|
12
|
+
|
13
|
+
@responses = {}
|
14
|
+
@parameters = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def root
|
18
|
+
@parents[0].root
|
19
|
+
end
|
20
|
+
|
21
|
+
def auth
|
22
|
+
@parents.flat_map(&:auth)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::OpenAPI::Nodes::Parent
|
4
|
+
attr_reader :root, :controller
|
5
|
+
attr_accessor :deprecated, :private, :auth
|
6
|
+
|
7
|
+
def initialize(root, controller)
|
8
|
+
@root = root
|
9
|
+
@controller = controller
|
10
|
+
|
11
|
+
@auth = []
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,49 @@
|
|
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
|
+
def parent_nodes
|
32
|
+
@parent_nodes_cache.values
|
33
|
+
end
|
34
|
+
|
35
|
+
def new_method_node(controller, action, parent_nodes)
|
36
|
+
node = Rage::OpenAPI::Nodes::Method.new(controller, action, parent_nodes)
|
37
|
+
@leaves << node
|
38
|
+
|
39
|
+
node
|
40
|
+
end
|
41
|
+
|
42
|
+
def new_parent_node(controller)
|
43
|
+
@parent_nodes_cache[controller] ||= begin
|
44
|
+
node = Rage::OpenAPI::Nodes::Parent.new(self, controller)
|
45
|
+
yield(node)
|
46
|
+
node
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|