rage-rb 1.10.0 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -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 +7 -5
- 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 +6 -1
- 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,21 @@
|
|
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
|
+
|
13
|
+
## [1.10.1] - 2024-09-17
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
|
17
|
+
- Patch AR pool even if `Rake` is defined (#105).
|
18
|
+
|
3
19
|
## [1.10.0] - 2024-09-16
|
4
20
|
|
5
21
|
### Changed
|
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
|
@@ -94,6 +96,6 @@ if defined?(ActiveRecord) && !Rage.config.internal.rails_mode && (database_url |
|
|
94
96
|
end
|
95
97
|
|
96
98
|
# patch `ActiveRecord::ConnectionPool`
|
97
|
-
if defined?(ActiveRecord) &&
|
99
|
+
if defined?(ActiveRecord) && Rage.config.internal.patch_ar_pool?
|
98
100
|
Rage.patch_active_record_connection_pool
|
99
101
|
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
|