oas_rage 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 105b36f0df7a0ce3a829ba070e83b30cf6d7bfc512ef45950c11bf62eb8f0760
4
+ data.tar.gz: 3c01928efdc49854f1b3bc5e65f7404a21a2e4ab8b169cabfada0ba45ca6e5bf
5
+ SHA512:
6
+ metadata.gz: 88a773990c11d3d748b206c2bd9a188d3962606253d465a8c92ce912e450a79ddbabb729901acc19b5784b45b67558718f1490fd798bd8bdb4401138600482a1
7
+ data.tar.gz: eac5dc00f10a0c19a7b799d10c310ccddb2ca4a232429a2f6f041765662011c9773fbf06ab93fb5d5fb4c87efe24ff4c71bab327edeb0dd2866db3bb2ce0c24b
@@ -0,0 +1,64 @@
1
+ {
2
+ "release-type": "ruby",
3
+ "changelog-sections": [
4
+ {
5
+ "type": "feat",
6
+ "section": "Features"
7
+ },
8
+ {
9
+ "type": "feature",
10
+ "section": "Features"
11
+ },
12
+ {
13
+ "type": "fix",
14
+ "section": "Bug Fixes"
15
+ },
16
+ {
17
+ "type": "perf",
18
+ "section": "Performance Improvements"
19
+ },
20
+ {
21
+ "type": "revert",
22
+ "section": "Reverts"
23
+ },
24
+ {
25
+ "type": "docs",
26
+ "section": "Documentation"
27
+ },
28
+ {
29
+ "type": "style",
30
+ "section": "Styles",
31
+ "hidden": true
32
+ },
33
+ {
34
+ "type": "chore",
35
+ "section": "Miscellaneous Chores",
36
+ "hidden": true
37
+ },
38
+ {
39
+ "type": "refactor",
40
+ "section": "Code Refactoring"
41
+ },
42
+ {
43
+ "type": "test",
44
+ "section": "Tests"
45
+ },
46
+ {
47
+ "type": "build",
48
+ "section": "Build System",
49
+ "hidden": true
50
+ },
51
+ {
52
+ "type": "ci",
53
+ "section": "Continuous Integration",
54
+ "hidden": true
55
+ }
56
+ ],
57
+ "packages": {
58
+ ".": {
59
+ "release-type": "ruby",
60
+ "package-name": "oas_rage",
61
+ "version-file": "lib/oas_rage/version.rb"
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.0"
3
+ }
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 3.1
5
+ NewCops: disable
6
+ SuggestExtensions: false
7
+
8
+ Style/Documentation:
9
+ Enabled: false
10
+
11
+ Metrics/MethodLength:
12
+ Max: 15
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,12 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2025-06-10 00:03:15 UTC using RuboCop version 1.76.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
11
+ Metrics/AbcSize:
12
+ Max: 19
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## [0.2.0](https://github.com/a-chacon/oas_rage/compare/oas_rage-v0.1.0...oas_rage/v0.2.0) (2025-06-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * first release, minimal features ([406f64f](https://github.com/a-chacon/oas_rage/commit/406f64f634288beb2f99cf466de09de02f607597))
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ ![Gem Version](https://img.shields.io/gem/v/oas_rage?color=E9573F)
2
+ ![GitHub License](https://img.shields.io/github/license/a-chacon/oas_rage?color=blue)
3
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/a-chacon/oas_rage/.github%2Fworkflows%2Frubyonrails.yml)
4
+ ![Gem Total Downloads](https://img.shields.io/gem/dt/oas_rage)
5
+ ![Static Badge](https://img.shields.io/badge/Rails-%3E%3D7.0.0-%23E9573F)
6
+ ![Static Badge](https://img.shields.io/badge/Ruby-%3E%3D3.1.0-%23E9573F)
7
+
8
+ # 📃Open API Specification For Rage
9
+
10
+ OasRage is a tool for generating **automatic interactive documentation for your Rage APIs**. It generates an **OAS 3.1** document and displays it using **[RapiDoc](https://rapidocweb.com)**.
11
+
12
+ Built for the high-performance [Rage](https://github.com/rage-rb/rage) framework, OasRage leverages Rage's compatibility with Rails and its modern Ruby features, including fiber scheduling for non-blocking I/O. It relies on the [OasCore](https://github.com/a-chacon/oas_core) gem for seamless OpenAPI integration.
13
+
14
+ ![Screenshot](https://a-chacon.com/assets/images/oas_rage_ui.png)
15
+
16
+ ## Documentation
17
+
18
+ For details on how to install, configure, and use OasRage, please refer to the [OasCore MDBook](http://a-chacon.com/oas_core).
19
+
20
+ ## Contributing
21
+
22
+ Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star⭐! Thanks again!
23
+
24
+ If you plan a big feature, first open an issue to discuss it before any development.
25
+
26
+ 1. Fork the Project
27
+ 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
28
+ 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
29
+ 4. Push to the Branch (`git push origin feature/AmazingFeature`)
30
+ 5. Open a Pull Request
31
+
32
+ ## License
33
+
34
+ The gem is available as open source under the terms of the [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text).
35
+
36
+ ## Star History
37
+
38
+ [![Star History Chart](https://api.star-history.com/svg?repos=a-chacon/oas_rage&type=Date)](https://www.star-history.com/#a-chacon/oas_rails&Date)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'minitest/test_task'
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasRage
4
+ class Configuration < OasCore::Configuration
5
+ attr_accessor :autodiscover_request_body, :autodiscover_responses, :ignored_actions, :rapidoc_theme, :layout
6
+ attr_reader :route_extractor, :include_mode
7
+
8
+ def initialize
9
+ super
10
+ @route_extractor = RouteExtractor
11
+ @include_mode = :all
12
+ @autodiscover_request_body = true
13
+ @autodiscover_responses = true
14
+ @ignored_actions = []
15
+ @rapidoc_theme = :rails
16
+ @layout = nil
17
+
18
+ # TODO: implement
19
+ # autodiscover_request_body
20
+ # autodiscover_responses
21
+ end
22
+
23
+ def include_mode=(value)
24
+ valid_modes = %i[all with_tags explicit]
25
+ raise ArgumentError, "include_mode must be one of #{valid_modes}" unless valid_modes.include?(value)
26
+
27
+ @include_mode = value
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasRage
4
+ class OasRouteBuilder
5
+ def self.build_from_rage_route(rage_route)
6
+ new(rage_route).build
7
+ end
8
+
9
+ def initialize(rage_route)
10
+ @rage_route = rage_route
11
+ end
12
+
13
+ def build
14
+ OasCore::OasRoute.new(
15
+ controller_class: controller_class,
16
+ controller_action: controller_action,
17
+ controller: controller,
18
+ method_name: method,
19
+ verb: verb,
20
+ path: path,
21
+ docstring: docstring,
22
+ source_string: source_string,
23
+ tags: tags
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def controller_class
30
+ controller_name = @rage_route[:meta][:raw_handler].split('#').first
31
+ "#{Utils.camelize(controller_name)}Controller"
32
+ end
33
+
34
+ def controller_action
35
+ "#{controller_class}##{@rage_route[:meta][:raw_handler].split('#').last}"
36
+ end
37
+
38
+ def controller
39
+ @rage_route[:meta][:raw_handler].split('#').first
40
+ end
41
+
42
+ def method
43
+ @rage_route[:meta][:raw_handler].split('#').last
44
+ end
45
+
46
+ def verb
47
+ @rage_route[:method]
48
+ end
49
+
50
+ def path
51
+ @rage_route[:path].to_s.gsub('(.:format)', '').gsub(/:\w+/) { |match| "{#{match[1..]}}" }
52
+ end
53
+
54
+ def source_string
55
+ Utils.constantize(controller_class).instance_method(method).source
56
+ end
57
+
58
+ def docstring
59
+ comment_lines = Utils.constantize(controller_class).instance_method(method).comment.lines
60
+ processed_lines = comment_lines.map { |line| line.sub(/^# /, '') }
61
+ ::YARD::Docstring.parser.parse(processed_lines.join).to_docstring
62
+ end
63
+
64
+ def tags
65
+ method_comment = Utils.constantize(controller_class).instance_method(method).comment
66
+ class_comment = Utils.constantize(controller_class).instance_method(method).class_comment
67
+
68
+ method_tags = parse_tags(method_comment)
69
+ class_tags = parse_tags(class_comment)
70
+
71
+ method_tags + class_tags
72
+ end
73
+
74
+ def parse_tags(comment)
75
+ lines = comment.lines.map { |line| line.sub(/^# /, '') }
76
+ ::YARD::Docstring.parser.parse(lines.join).tags
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasRage
4
+ class RouteExtractor
5
+ def initialize
6
+ @host_routes = nil
7
+ @host_paths = nil
8
+ end
9
+
10
+ def host_routes_by_path(path)
11
+ host_routes.select { |r| r.path == path }
12
+ end
13
+
14
+ def host_routes
15
+ @host_routes ||= extract_host_routes
16
+ end
17
+
18
+ # Clear instance variable @host_routes
19
+ #
20
+ # This method clears the instance variable @host_routes
21
+ # to force a re-extraction of the routes.
22
+ def clear_cache
23
+ @host_routes = nil
24
+ @host_paths = nil
25
+ end
26
+
27
+ def host_paths
28
+ @host_paths ||= host_routes.map(&:path).uniq.sort
29
+ end
30
+
31
+ private
32
+
33
+ def extract_host_routes
34
+ routes = valid_routes.map { |r| OasRouteBuilder.build_from_rage_route(r) }
35
+ routes.select! { |route| route.tags.any? } if OasRage.config.include_mode == :with_tags
36
+ if OasRage.config.include_mode == :explicit
37
+ routes.select! do |route|
38
+ route.tags.any? do |t|
39
+ t.tag_name == 'oas_include'
40
+ end
41
+ end
42
+ end
43
+ routes
44
+ end
45
+
46
+ def valid_routes
47
+ Rage.__router.routes.select do |route|
48
+ valid_api_route?(route)
49
+ end
50
+ end
51
+
52
+ def valid_api_route?(route)
53
+ return false unless valid_route_implementation?(route)
54
+ # return false unless route.path.spec.to_s.start_with?(OasRage.config.api_path)
55
+ return false if ignore_custom_actions(route)
56
+
57
+ true
58
+ end
59
+
60
+ # Checks if a route has a valid implementation.
61
+ #
62
+ # This method verifies that both the controller and the action specified
63
+ # in the route exist. It checks if the controller class is defined and
64
+ # if the action method is implemented within that controller.
65
+ #
66
+ # @param route [ActionDispatch::Journey::Route] The route to check.
67
+ # @return [Boolean] true if both the controller and action exist, false otherwise.
68
+ def valid_route_implementation?(route)
69
+ raw_handler = route[:meta][:raw_handler]
70
+ return false unless raw_handler.is_a?(String) && raw_handler.include?('#')
71
+
72
+ controller_name, action_name = raw_handler.split('#')
73
+ controller_name = "#{Utils.camelize(controller_name)}Controller"
74
+
75
+ controller_class = Utils.safe_constantize(controller_name)
76
+ return false unless controller_class
77
+
78
+ controller_class.instance_methods.include?(action_name.to_sym)
79
+ end
80
+
81
+ # Ignore user-specified paths in initializer configuration.
82
+ # Sanitize api_path by removing the "/" if it starts with that, and adding "/" if it ends without that.
83
+ # Support controller name only to ignore all controller actions.
84
+ # Support ignoring "controller#action"
85
+ # Ignoring "controller#action" AND "api_path/controller#action"
86
+ def ignore_custom_actions(route)
87
+ api_path = "#{OasRage.config.api_path.sub(%r{\A/}, '')}/".sub(%r{/+$}, '/')
88
+ ignored_actions = OasRage.config.ignored_actions.flat_map do |custom_route|
89
+ if custom_route.start_with?(api_path)
90
+ [custom_route]
91
+ else
92
+ ["#{api_path}#{custom_route}", custom_route]
93
+ end
94
+ end
95
+
96
+ raw_handler = route[:meta][:raw_handler]
97
+ return false unless raw_handler.is_a?(String) && raw_handler.include?('#')
98
+
99
+ controller_action = raw_handler
100
+ controller_only = raw_handler.split('#').first
101
+
102
+ ignored_actions.include?(controller_action) || ignored_actions.include?(controller_only)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasRage
4
+ module Utils
5
+ def self.camelize(str)
6
+ str.split('_').map(&:capitalize).join
7
+ end
8
+
9
+ def self.safe_constantize(str)
10
+ Object.const_get(str)
11
+ rescue NameError
12
+ nil
13
+ end
14
+
15
+ def self.constantize(str)
16
+ Object.const_get(str)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasRage
4
+ VERSION = '0.2.0'
5
+ end