rails_routes_to_openapi 0.1.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 +7 -0
- data/README.md +83 -0
- data/Rakefile +23 -0
- data/lib/rails_routes_to_openapi/structs/open_api.rb +46 -0
- data/lib/rails_routes_to_openapi/structs/rails_route_info.rb +12 -0
- data/lib/rails_routes_to_openapi/types/index.rb +3 -0
- data/lib/rails_routes_to_openapi/util/file_helpers.rb +46 -0
- data/lib/rails_routes_to_openapi/util/open_api_helpers.rb +76 -0
- data/lib/rails_routes_to_openapi.rb +34 -0
- data/lib/tasks/routes_to_openapi.rake +23 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fded852e3d8520a0ca3e59b04afa46d637185933027c8d960872f29c28c3bc6d
|
4
|
+
data.tar.gz: 8e3e5578b948e57a89a6ae82be630240fa55a7a35efc92a855013297bfa6e244
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f00dfdbe5d465f5069718d5c10f281ce8cdde15afae559101e2739437e3a7b1038f83c56e0b538cdeb18008ea674d2e43e1d6efe904835787a84432e27d662c4
|
7
|
+
data.tar.gz: f86f5e5a6a07647fcb3e7af9315f74595be110e4019791d1591eec865b0434e8de84673780af97e795a67addb60177c966dc56040a9d427e09a6aa5d397f3723
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Converting a Rails Routes Output into a Postman Collection
|
2
|
+
|
3
|
+
In an effort to learn a little bit about Ruby and Rails, I decided a fun project would be to attempt generating a YAML file that aligns with the OpenAPI v3.1.0 specification. This file can then be imported into Postman so you can test Rails routes without manually using the Rails console or the consuming frontend application.
|
4
|
+
|
5
|
+
## Quick Start
|
6
|
+
|
7
|
+
1. Add the gem to your Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'rails_routes_to_openapi'
|
11
|
+
```
|
12
|
+
|
13
|
+
2. Install dependencies:
|
14
|
+
|
15
|
+
```shell
|
16
|
+
bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
3. Run the conversion task:
|
20
|
+
|
21
|
+
```shell
|
22
|
+
bundle exec rake routes_to_openapi:convert
|
23
|
+
```
|
24
|
+
|
25
|
+
This task will internally run `rails routes` on your Rails application and generate an OpenAPI YAML file named similar to `openapi_v3.1_YYYYMMDDHHMMSS.yml` in the project root.
|
26
|
+
|
27
|
+
> [!WARNING] > **Tip:** Ensure you run the task within a Rails project context so that it can properly pull the routes.
|
28
|
+
|
29
|
+
## Workflow Overview
|
30
|
+
|
31
|
+
1. **Routes Processing**
|
32
|
+
The conversion workflow starts by reading the output of `rails routes`, splitting it into lines, and filtering out header information. A helper module parses each line into a structured `RouteInfo` object that captures the HTTP verb, URI pattern, and the associated controller action.
|
33
|
+
|
34
|
+
2. **OpenAPI Generation**
|
35
|
+
The parsed routes are then converted into an OpenAPI compliant hash. The conversion process does the following:
|
36
|
+
|
37
|
+
- It adapts URL parameters (e.g., turning `/a/b/:param/d` into `/a/b/{param}`).
|
38
|
+
- For non-GET verbs, it attaches a default request body schema.
|
39
|
+
- Finally, it merges the above details into an OpenAPI compliant hash which is then dumped into YAML format.
|
40
|
+
|
41
|
+
3. **Output**
|
42
|
+
The generated YAML file contains:
|
43
|
+
- OpenAPI version information.
|
44
|
+
- Descriptive metadata (title, description, version, etc.).
|
45
|
+
- A list of servers.
|
46
|
+
- A paths list that maps each route to its HTTP verb and summary (derived from the controller action).
|
47
|
+
|
48
|
+
## Example OpenAPI YAML Output
|
49
|
+
|
50
|
+
Below is an abbreviated snippet of what the output might look like:
|
51
|
+
|
52
|
+
```yaml
|
53
|
+
openapi: 3.1.0
|
54
|
+
info:
|
55
|
+
title: Rails Routes to OpenAPI
|
56
|
+
description: Generate an OpenAPI v3.1 YAML file for Postman import
|
57
|
+
version: "1.1.0"
|
58
|
+
servers:
|
59
|
+
- url: "https://api.example.com"
|
60
|
+
description: "Base URL for the API"
|
61
|
+
paths:
|
62
|
+
"/":
|
63
|
+
get:
|
64
|
+
summary: "home#show"
|
65
|
+
"/path/to/authorize/native":
|
66
|
+
get:
|
67
|
+
summary: "doorkeeper/authorizations#show"
|
68
|
+
"/path/to/authorize":
|
69
|
+
get:
|
70
|
+
summary: "doorkeeper/authorizations#new"
|
71
|
+
delete:
|
72
|
+
summary: "doorkeeper/authorizations#destroy"
|
73
|
+
requestBody:
|
74
|
+
required: true
|
75
|
+
content:
|
76
|
+
"application/json":
|
77
|
+
schema:
|
78
|
+
type: object
|
79
|
+
properties:
|
80
|
+
id:
|
81
|
+
type: string
|
82
|
+
format: string
|
83
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "rake"
|
2
|
+
require_relative "lib/rails_routes_to_openapi"
|
3
|
+
|
4
|
+
namespace :routes_to_openapi do
|
5
|
+
desc "Convert Rails routes to OpenAPI YAML"
|
6
|
+
task :convert do
|
7
|
+
routes_output = IO.popen("rails routes") { |io| io.read }
|
8
|
+
|
9
|
+
# Check if the output contains expected routes information
|
10
|
+
if routes_output.include?("Usage:") || routes_output.strip.empty?
|
11
|
+
puts "Failed to retrieve routes. Please ensure you are running this task within a Rails project."
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
RailsRoutesToOpenAPI.convert(routes_output)
|
17
|
+
puts 'OpenAPI v3 YAML file generated successfully.'
|
18
|
+
rescue => e
|
19
|
+
puts "Failed to generate OpenAPI YAML file."
|
20
|
+
puts e.message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "dry-struct"
|
2
|
+
require_relative "../types/index"
|
3
|
+
|
4
|
+
module Structs
|
5
|
+
module OpenAPI
|
6
|
+
class Base < Dry::Struct
|
7
|
+
attribute :openapi, Types::String
|
8
|
+
attribute :info, Types::Hash.schema(
|
9
|
+
title: Types::Strict::String,
|
10
|
+
description: Types::Strict::String,
|
11
|
+
version: Types::String
|
12
|
+
)
|
13
|
+
attribute :servers, Types::Array.of(
|
14
|
+
Types::Hash.schema(
|
15
|
+
url: Types::Strict::String,
|
16
|
+
description: Types::Strict::String
|
17
|
+
)
|
18
|
+
)
|
19
|
+
attribute :paths, Types::Strict::Hash
|
20
|
+
end
|
21
|
+
|
22
|
+
class VerbWithRequestBody < Dry::Struct
|
23
|
+
attribute :summary, Types::Strict::String
|
24
|
+
attribute :requestBody, Types::Hash.schema(
|
25
|
+
required: Types::Strict::Bool,
|
26
|
+
content: Types::Hash.schema(
|
27
|
+
"application/json": Types::Hash.schema(
|
28
|
+
schema: Types::Hash.schema(
|
29
|
+
type: Types::Strict::String,
|
30
|
+
properties: Types::Hash.schema(
|
31
|
+
id: Types::Hash.schema(
|
32
|
+
type: Types::Strict::String,
|
33
|
+
format: Types::Strict::String
|
34
|
+
)
|
35
|
+
)
|
36
|
+
)
|
37
|
+
)
|
38
|
+
)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
class Verb < Dry::Struct
|
43
|
+
attribute :summary, Types::Strict::String
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "dry-struct"
|
2
|
+
require_relative "../types/index"
|
3
|
+
|
4
|
+
module Structs
|
5
|
+
class RouteInfo < Dry::Struct
|
6
|
+
attribute :verb, Types::Strict::String
|
7
|
+
attribute :uri_pattern, Types::Strict::String
|
8
|
+
attribute :controller_action, Types::Strict::String
|
9
|
+
|
10
|
+
attr_writer :uri_pattern
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative "../structs/rails_route_info"
|
2
|
+
module Util
|
3
|
+
class FileHelpers
|
4
|
+
class << self
|
5
|
+
# Takes routes output and returns RouteInfo based on each line
|
6
|
+
# for REST verb, uri_pattern and associated controller_action
|
7
|
+
def parse(routes_output)
|
8
|
+
split_file_data = split(routes_output)
|
9
|
+
filtered_file_data = filter_header(split_file_data)
|
10
|
+
create_route_info(filtered_file_data)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns string array for each line
|
14
|
+
def split(file_content)
|
15
|
+
file_content.split(/\n/)
|
16
|
+
rescue
|
17
|
+
raise "Failed to split file content"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Filter out the header line
|
21
|
+
def filter_header(file_content_array)
|
22
|
+
# Assuming the first line is the header
|
23
|
+
file_content_array.drop(1)
|
24
|
+
rescue
|
25
|
+
raise "Failed to filter header"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Accepts string array from file line
|
29
|
+
def create_route_info(file_data)
|
30
|
+
# Use regex to split each line into prefix, verb, uri_pattern, and controller_action
|
31
|
+
file_data.flat_map { |line|
|
32
|
+
next if line.empty?
|
33
|
+
match = line.match(/^\s*(\S*)\s*(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)?\s+(\S+)\s+(.+)\s*$/)
|
34
|
+
if match
|
35
|
+
prefix, verb, uri_pattern, controller_action = match.captures
|
36
|
+
# Split the verb by '|' if it exists, otherwise default to 'GET'
|
37
|
+
verbs = verb&.split('|') || ['GET']
|
38
|
+
verbs.map { |v| Structs::RouteInfo.new(verb: v, uri_pattern: uri_pattern, controller_action: controller_action) }
|
39
|
+
end
|
40
|
+
}.compact
|
41
|
+
rescue
|
42
|
+
raise "Failed to map line to RouteInfo"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "deep_merge"
|
2
|
+
require_relative "../structs/open_api"
|
3
|
+
|
4
|
+
module Util
|
5
|
+
class OpenAPIHelpers
|
6
|
+
class << self
|
7
|
+
# Takes an array of RouteInfo types and generates the OpenAPI v3 spec
|
8
|
+
# to write into YAML and export to a file.
|
9
|
+
def convert(route_info)
|
10
|
+
param_friendly_route_info = convert_params_to_compatible_format(route_info)
|
11
|
+
paths_hash = write_paths_hash(param_friendly_route_info)
|
12
|
+
write_open_api_compliant_hash(paths_hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
def convert_params_to_compatible_format(route_info)
|
16
|
+
route_info.map { |info|
|
17
|
+
# Remove (.:format) from uri_pattern
|
18
|
+
update_string = info.uri_pattern.gsub(/\(\.:format\)/, '')
|
19
|
+
|
20
|
+
# Turn something like /a/b/:param/d to /a/b/{param} for OpenAPI standards
|
21
|
+
update_string = update_string.split("/").map { |uri|
|
22
|
+
uri[0] == ":" ? "{#{uri[1..]}}" : uri
|
23
|
+
}.join("/")
|
24
|
+
|
25
|
+
Structs::RouteInfo.new(verb: info.verb, controller_action: info.controller_action, uri_pattern: update_string)
|
26
|
+
}
|
27
|
+
rescue
|
28
|
+
raise 'Could not convert params from :param to {param} format'
|
29
|
+
end
|
30
|
+
|
31
|
+
# ! To be honest, I feel like all of this should be cleaned up.
|
32
|
+
# Even if it is just that I need a better formatter to make it read easier.
|
33
|
+
# I feel like the following four methods are a bit wtf really.
|
34
|
+
def get_request_body_schema
|
35
|
+
{type: "object", properties: {id: {type: "string", format: "string"}}}
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_verb_with_req_body(info)
|
39
|
+
Structs::OpenAPI::VerbWithRequestBody.new(summary: info.controller_action, requestBody: {required: true, content: {"application/json": {schema: get_request_body_schema}}}).attributes
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_verb(info)
|
43
|
+
Structs::OpenAPI::Verb.new(summary: info.controller_action).attributes
|
44
|
+
end
|
45
|
+
|
46
|
+
def write_paths_hash(route_info)
|
47
|
+
paths_hash = {}
|
48
|
+
route_info.each do |info|
|
49
|
+
# Simplify the GET part - assume all other REST verbs require a request body
|
50
|
+
if info.verb == "GET"
|
51
|
+
paths_hash.deep_merge!({info.uri_pattern => {info.verb.downcase => get_verb(info)}})
|
52
|
+
else
|
53
|
+
paths_hash.deep_merge!({info.uri_pattern => {info.verb.downcase => get_verb_with_req_body(info)}})
|
54
|
+
end
|
55
|
+
end
|
56
|
+
paths_hash
|
57
|
+
rescue
|
58
|
+
raise 'Could not write paths for hash'
|
59
|
+
end
|
60
|
+
|
61
|
+
def write_open_api_compliant_hash(paths_hash)
|
62
|
+
info = {
|
63
|
+
title: "Rails Routes to OpenAPI",
|
64
|
+
description: "Generate an OpenAPI v3.1 YAML file for Postman import",
|
65
|
+
version: "1.1.0",
|
66
|
+
}
|
67
|
+
servers = [{url: "https://api.example.com", description: "Base URL for the API"}]
|
68
|
+
|
69
|
+
# This ends up the final hash to write to disk.
|
70
|
+
Structs::OpenAPI::Base.new(openapi: "3.1.0", info: info, servers: servers, paths: paths_hash).attributes
|
71
|
+
rescue
|
72
|
+
raise 'Could not write OpenAPI compliant hash'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require_relative "rails_routes_to_openapi/util/file_helpers"
|
3
|
+
require_relative "rails_routes_to_openapi/util/open_api_helpers"
|
4
|
+
|
5
|
+
module RailsRoutesToOpenAPI
|
6
|
+
class ::Hash
|
7
|
+
def deep_stringify_keys
|
8
|
+
each_with_object({}) do |(k, v), h|
|
9
|
+
h[k.to_s] = case v
|
10
|
+
when Hash
|
11
|
+
v.deep_stringify_keys
|
12
|
+
when Array
|
13
|
+
v.map { |i| i.is_a?(Hash) ? i.deep_stringify_keys : i }
|
14
|
+
else
|
15
|
+
v
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.convert(routes_output)
|
22
|
+
route_info = Util::FileHelpers.parse(routes_output)
|
23
|
+
yaml_format = Util::OpenAPIHelpers.convert(route_info).to_hash.deep_stringify_keys.to_yaml
|
24
|
+
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
25
|
+
output_file = "openapi_v3.1_#{timestamp}.yml"
|
26
|
+
|
27
|
+
File.open(output_file, "w") { |file| file.write(yaml_format) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Load Rake tasks if Rails is defined
|
32
|
+
if defined?(Rails)
|
33
|
+
Dir[File.join(__dir__, 'tasks/**/*.rake')].each { |ext| load ext }
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "rake"
|
2
|
+
require_relative "../rails_routes_to_openapi"
|
3
|
+
|
4
|
+
namespace :routes_to_openapi do
|
5
|
+
desc "Convert Rails routes to OpenAPI YAML"
|
6
|
+
task :convert do
|
7
|
+
routes_output = IO.popen("rails routes") { |io| io.read }
|
8
|
+
|
9
|
+
# Check if the output contains expected routes information
|
10
|
+
if routes_output.include?("Usage:") || routes_output.strip.empty?
|
11
|
+
puts "Failed to retrieve routes. Please ensure you are running this task within a Rails project."
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
RailsRoutesToOpenAPI.convert(routes_output)
|
17
|
+
puts 'OpenAPI v3 YAML file generated successfully.'
|
18
|
+
rescue => e
|
19
|
+
puts "Failed to generate OpenAPI YAML file."
|
20
|
+
puts e.message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails_routes_to_openapi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Camel Chang
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-02-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-types
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-struct
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yaml
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: deep_merge
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A gem to convert Rails routes to OpenAPI YAML for Postman import
|
70
|
+
email:
|
71
|
+
- a556622821@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- README.md
|
77
|
+
- Rakefile
|
78
|
+
- lib/rails_routes_to_openapi.rb
|
79
|
+
- lib/rails_routes_to_openapi/structs/open_api.rb
|
80
|
+
- lib/rails_routes_to_openapi/structs/rails_route_info.rb
|
81
|
+
- lib/rails_routes_to_openapi/types/index.rb
|
82
|
+
- lib/rails_routes_to_openapi/util/file_helpers.rb
|
83
|
+
- lib/rails_routes_to_openapi/util/open_api_helpers.rb
|
84
|
+
- lib/tasks/routes_to_openapi.rake
|
85
|
+
homepage: https://github.com/camel2243/rails-gen-openapi
|
86
|
+
licenses:
|
87
|
+
- Apache-2.0
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubygems_version: 3.4.10
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Convert Rails routes to OpenAPI YAML
|
108
|
+
test_files: []
|