rails-openapi-gen 0.0.2 → 0.0.3
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 +40 -0
- data/CLAUDE.md +17 -5
- data/README.md +25 -0
- data/lib/rails-openapi-gen/ast_nodes/array_node.rb +101 -0
- data/lib/rails-openapi-gen/ast_nodes/base_node.rb +139 -0
- data/lib/rails-openapi-gen/ast_nodes/comment_data.rb +180 -0
- data/lib/rails-openapi-gen/ast_nodes/node_factory.rb +206 -0
- data/lib/rails-openapi-gen/ast_nodes/object_node.rb +129 -0
- data/lib/rails-openapi-gen/ast_nodes/partial_node.rb +111 -0
- data/lib/rails-openapi-gen/ast_nodes/property_node.rb +74 -0
- data/lib/rails-openapi-gen/ast_nodes.rb +129 -0
- data/lib/rails-openapi-gen/configuration.rb +154 -22
- data/lib/rails-openapi-gen/debug_helpers.rb +185 -0
- data/lib/rails-openapi-gen/engine.rb +1 -1
- data/lib/rails-openapi-gen/generators/yaml_generator.rb +242 -27
- data/lib/rails-openapi-gen/generators.rb +5 -0
- data/lib/rails-openapi-gen/importer.rb +164 -145
- data/lib/rails-openapi-gen/parsers/comment_parser.rb +1 -1
- data/lib/rails-openapi-gen/parsers/comment_parsers/attribute_parser.rb +7 -7
- data/lib/rails-openapi-gen/parsers/comment_parsers/base_attribute_parser.rb +5 -9
- data/lib/rails-openapi-gen/parsers/comment_parsers/body_parser.rb +6 -6
- data/lib/rails-openapi-gen/parsers/comment_parsers/conditional_parser.rb +1 -1
- data/lib/rails-openapi-gen/parsers/comment_parsers/operation_parser.rb +5 -5
- data/lib/rails-openapi-gen/parsers/comment_parsers/param_parser.rb +6 -6
- data/lib/rails-openapi-gen/parsers/comment_parsers/query_parser.rb +6 -6
- data/lib/rails-openapi-gen/parsers/controller_parser.rb +64 -20
- data/lib/rails-openapi-gen/parsers/jbuilder/ast_parser.rb +914 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/array_call_detector.rb +103 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/base_detector.rb +107 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/cache_call_detector.rb +112 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/json_call_detector.rb +91 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/key_format_detector.rb +27 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/null_handling_detector.rb +27 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/object_manipulation_detector.rb +27 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/partial_call_detector.rb +125 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors.rb +95 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/jbuilder_parser.rb +39 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/operation_comment_parser.rb +26 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/processors/array_processor.rb +266 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/processors/base_processor.rb +235 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/processors/composite_processor.rb +97 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/processors/object_processor.rb +176 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/processors/partial_processor.rb +69 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/processors/property_processor.rb +68 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/processors.rb +10 -0
- data/lib/rails-openapi-gen/parsers/jbuilder/property_comment_parser.rb +26 -0
- data/lib/rails-openapi-gen/parsers/jbuilder.rb +10 -0
- data/lib/rails-openapi-gen/parsers/routes_parser.rb +83 -9
- data/lib/rails-openapi-gen/parsers/template_processors/jbuilder_template_processor.rb +125 -131
- data/lib/rails-openapi-gen/parsers/template_processors/response_template_processor.rb +8 -12
- data/lib/rails-openapi-gen/parsers/template_processors.rb +6 -0
- data/lib/rails-openapi-gen/parsers.rb +9 -0
- data/lib/rails-openapi-gen/processors/ast_to_schema_processor.rb +226 -0
- data/lib/rails-openapi-gen/processors/base_processor.rb +124 -0
- data/lib/rails-openapi-gen/processors/component_schema_processor.rb +35 -0
- data/lib/rails-openapi-gen/processors/openapi_schema_processor.rb +218 -0
- data/lib/rails-openapi-gen/processors.rb +7 -0
- data/lib/rails-openapi-gen/railtie.rb +1 -1
- data/lib/rails-openapi-gen/tasks/openapi.rake +4 -4
- data/lib/rails-openapi-gen/version.rb +1 -1
- data/lib/rails-openapi-gen.rb +169 -196
- data/lib/tasks/openapi_import.rake +35 -36
- data/rails-openapi-gen.gemspec +6 -5
- metadata +62 -23
- data/lib/rails-openapi-gen/parsers/jbuilder_parser.rb +0 -529
data/lib/rails-openapi-gen.rb
CHANGED
@@ -1,26 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require "rails-openapi-gen/importer"
|
3
|
+
require 'rails-openapi-gen/version'
|
4
|
+
require 'rails-openapi-gen/configuration'
|
5
|
+
require 'parser/current'
|
6
|
+
require 'pp'
|
8
7
|
|
9
|
-
#
|
8
|
+
# Zeitwerk autoloading setup
|
9
|
+
module RailsOpenapiGen
|
10
|
+
autoload :AstNodes, "rails-openapi-gen/ast_nodes"
|
11
|
+
autoload :Parsers, "rails-openapi-gen/parsers"
|
12
|
+
autoload :Processors, "rails-openapi-gen/processors"
|
13
|
+
autoload :Generators, "rails-openapi-gen/generators"
|
14
|
+
autoload :Importer, "rails-openapi-gen/importer"
|
15
|
+
end
|
10
16
|
|
11
|
-
#
|
17
|
+
# Direct requires for core components that don't follow autoload patterns
|
18
|
+
require 'rails-openapi-gen/generators/yaml_generator'
|
19
|
+
|
20
|
+
# Rails integration is handled by Engine
|
12
21
|
if defined?(Rails::Engine)
|
13
|
-
require
|
14
|
-
|
15
|
-
# Only load parser-dependent components if parser gem is available
|
16
|
-
begin
|
17
|
-
require "parser/current"
|
18
|
-
require "rails-openapi-gen/parsers/routes_parser"
|
19
|
-
require "rails-openapi-gen/parsers/controller_parser"
|
20
|
-
require "rails-openapi-gen/parsers/jbuilder_parser"
|
21
|
-
rescue LoadError
|
22
|
-
# parser gem not available, skip these components
|
23
|
-
end
|
22
|
+
require 'rails-openapi-gen/engine'
|
24
23
|
end
|
25
24
|
|
26
25
|
module RailsOpenapiGen
|
@@ -47,221 +46,195 @@ module RailsOpenapiGen
|
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
49
|
+
class Checker
|
50
|
+
# Checks for missing OpenAPI comments and uncommitted changes
|
51
|
+
# @return [void]
|
52
|
+
def run
|
53
|
+
puts "🔍 Checking for missing comments and uncommitted changes..."
|
54
|
+
|
55
|
+
# Run OpenAPI generation to check for missing comments
|
56
|
+
system('bin/rails openapi:generate') || system('bundle exec rails openapi:generate')
|
57
|
+
|
58
|
+
# Check for uncommitted changes in openapi directory
|
59
|
+
if system('git diff --quiet docs/api/ 2>/dev/null')
|
60
|
+
puts "✅ All checks passed!"
|
61
|
+
else
|
62
|
+
puts "❌ Found uncommitted changes in OpenAPI files"
|
63
|
+
puts `git diff docs/api/`
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
50
69
|
class Generator
|
51
70
|
# Runs the OpenAPI generation process
|
52
71
|
# @return [void]
|
53
72
|
def run
|
54
73
|
# Load configuration
|
55
74
|
RailsOpenapiGen.configuration.load_from_file
|
56
|
-
|
75
|
+
|
57
76
|
routes = Parsers::RoutesParser.new.parse
|
58
|
-
|
59
|
-
|
77
|
+
|
78
|
+
# Debug: Show filtering configuration
|
79
|
+
config = RailsOpenapiGen.configuration
|
80
|
+
|
81
|
+
# Apply filtering with debug output
|
82
|
+
filtered_routes = routes.select do |route|
|
83
|
+
included = config.route_included?(route[:path])
|
84
|
+
included
|
85
|
+
end
|
86
|
+
|
60
87
|
schemas = {}
|
88
|
+
all_components = {}
|
61
89
|
|
62
90
|
filtered_routes.each do |route|
|
63
91
|
controller_info = Parsers::ControllerParser.new(route).parse
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
}
|
92
|
+
|
93
|
+
next unless controller_info[:jbuilder_path]
|
94
|
+
|
95
|
+
jbuilder_parser = Parsers::Jbuilder::JbuilderParser.new(controller_info[:jbuilder_path])
|
96
|
+
ast_node = jbuilder_parser.parse
|
97
|
+
|
98
|
+
# Collect components from this parser
|
99
|
+
if ENV['RAILS_OPENAPI_DEBUG']
|
100
|
+
puts "🔍 DEBUG: jbuilder_parser has ast_parser: #{jbuilder_parser.respond_to?(:ast_parser)}"
|
101
|
+
puts "🔍 DEBUG: ast_parser is nil: #{jbuilder_parser.ast_parser.nil?}" if jbuilder_parser.respond_to?(:ast_parser)
|
102
|
+
if jbuilder_parser.respond_to?(:ast_parser) && jbuilder_parser.ast_parser && jbuilder_parser.ast_parser.respond_to?(:partial_components)
|
103
|
+
puts "🔍 DEBUG: partial_components count: #{jbuilder_parser.ast_parser.partial_components.size}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
if jbuilder_parser.respond_to?(:ast_parser) &&
|
108
|
+
jbuilder_parser.ast_parser &&
|
109
|
+
jbuilder_parser.ast_parser.respond_to?(:partial_components) &&
|
110
|
+
jbuilder_parser.ast_parser.partial_components.any?
|
111
|
+
puts "📦 Merging #{jbuilder_parser.ast_parser.partial_components.size} components" if ENV['RAILS_OPENAPI_DEBUG']
|
112
|
+
all_components.merge!(jbuilder_parser.ast_parser.partial_components)
|
73
113
|
end
|
114
|
+
|
115
|
+
schema = Processors::AstToSchemaProcessor.new.process_to_schema(ast_node)
|
116
|
+
schemas[route] = {
|
117
|
+
schema: schema,
|
118
|
+
parameters: controller_info[:parameters] || {},
|
119
|
+
operation: {} # Operation data is not available in AST approach
|
120
|
+
}
|
121
|
+
rescue StandardError => e
|
122
|
+
puts "❌ ERROR processing route #{route[:method]} #{route[:path]}: #{e.class} - #{e.message}" if ENV['RAILS_OPENAPI_DEBUG']
|
123
|
+
puts "❌ ERROR at: #{e.backtrace.first(3).join("\n")}" if ENV['RAILS_OPENAPI_DEBUG']
|
124
|
+
raise
|
74
125
|
end
|
75
126
|
|
76
|
-
Generators::YamlGenerator.new(schemas).generate
|
77
|
-
puts
|
127
|
+
Generators::YamlGenerator.new(schemas, components: all_components).generate
|
128
|
+
puts '✅ OpenAPI specification generated successfully!'
|
78
129
|
end
|
79
130
|
|
80
131
|
private
|
81
132
|
|
82
|
-
# Builds schema from
|
83
|
-
# @param
|
84
|
-
# @return [Hash] OpenAPI schema
|
85
|
-
def build_schema(
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
schema = { "type" => "object", "properties" => {}, "required" => [] }
|
92
|
-
|
93
|
-
ast.each do |node|
|
94
|
-
property = node[:property]
|
95
|
-
comment_data = node[:comment_data] || {}
|
96
|
-
|
97
|
-
# Skip array root items as they're handled separately
|
98
|
-
next if node[:is_array_root]
|
99
|
-
|
100
|
-
property_schema = build_property_schema(node)
|
101
|
-
|
102
|
-
schema["properties"][property] = property_schema
|
103
|
-
# Don't mark conditional properties as required, even if they have required:true
|
104
|
-
if comment_data[:required] == "true" && !node[:is_conditional]
|
105
|
-
schema["required"] << property
|
106
|
-
end
|
107
|
-
end
|
133
|
+
# Builds OpenAPI schema from AST properties array
|
134
|
+
# @param properties [Array<Hash>] Array of property hashes with property name and comment_data
|
135
|
+
# @return [Hash] OpenAPI schema object
|
136
|
+
def build_schema(properties)
|
137
|
+
schema = {
|
138
|
+
"type" => "object",
|
139
|
+
"properties" => {},
|
140
|
+
"required" => []
|
141
|
+
}
|
108
142
|
|
109
|
-
|
110
|
-
|
143
|
+
properties.each do |prop|
|
144
|
+
property_name = prop[:property] || prop["property"]
|
145
|
+
comment_data = prop[:comment_data] || prop["comment_data"]
|
146
|
+
is_conditional = prop[:is_conditional] || prop["is_conditional"]
|
147
|
+
|
148
|
+
next unless property_name
|
111
149
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
# Find the array root node to check for array_item_properties
|
121
|
-
array_root_node = ast.find { |node| node[:is_array_root] }
|
122
|
-
|
123
|
-
if array_root_node && array_root_node[:array_item_properties]
|
124
|
-
# Use properties from the parsed partial
|
125
|
-
array_root_node[:array_item_properties].each do |node|
|
126
|
-
property = node[:property]
|
127
|
-
comment_data = node[:comment_data] || {}
|
128
|
-
|
129
|
-
property_schema = build_property_schema(node)
|
130
|
-
item_properties[property] = property_schema
|
131
|
-
if comment_data[:required] == "true" && !node[:is_conditional]
|
132
|
-
required_fields << property
|
150
|
+
if comment_data
|
151
|
+
property_schema = build_property_schema(prop)
|
152
|
+
schema["properties"][property_name] = property_schema
|
153
|
+
|
154
|
+
# Add to required unless explicitly marked as not required OR is conditional
|
155
|
+
required = comment_data[:required] || comment_data["required"]
|
156
|
+
unless required == false || required == "false" || is_conditional
|
157
|
+
schema["required"] << property_name
|
133
158
|
end
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
property_schema = build_property_schema(node)
|
144
|
-
item_properties[property] = property_schema
|
145
|
-
if comment_data[:required] == "true" && !node[:is_conditional]
|
146
|
-
required_fields << property
|
159
|
+
else
|
160
|
+
# Handle missing comments
|
161
|
+
schema["properties"][property_name] = {
|
162
|
+
"type" => "string",
|
163
|
+
"description" => "TODO: MISSING COMMENT"
|
164
|
+
}
|
165
|
+
# Don't add conditional properties to required even if they have missing comments
|
166
|
+
unless is_conditional
|
167
|
+
schema["required"] << property_name
|
147
168
|
end
|
148
169
|
end
|
149
170
|
end
|
150
171
|
|
151
|
-
|
152
|
-
"type" => "array",
|
153
|
-
"items" => {
|
154
|
-
"type" => "object",
|
155
|
-
"properties" => item_properties,
|
156
|
-
"required" => required_fields
|
157
|
-
}
|
158
|
-
}
|
172
|
+
schema
|
159
173
|
end
|
160
174
|
|
161
|
-
# Builds property schema
|
162
|
-
# @param
|
163
|
-
# @return [Hash]
|
164
|
-
def build_property_schema(
|
165
|
-
comment_data =
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
property_schema["properties"] = nested_schema["properties"]
|
176
|
-
property_schema["required"] = nested_schema["required"] if nested_schema["required"] && !nested_schema["required"].empty?
|
177
|
-
end
|
178
|
-
elsif node[:is_array]
|
179
|
-
property_schema["type"] = "array"
|
180
|
-
|
181
|
-
if node[:array_item_properties] && !node[:array_item_properties].empty?
|
182
|
-
# Build items schema from array iteration block
|
183
|
-
items_schema = build_nested_object_schema(node[:array_item_properties])
|
184
|
-
items_def = {
|
185
|
-
"type" => "object",
|
186
|
-
"properties" => items_schema["properties"]
|
187
|
-
}
|
188
|
-
items_def["required"] = items_schema["required"] if items_schema["required"] && !items_schema["required"].empty?
|
189
|
-
property_schema["items"] = items_def
|
190
|
-
elsif comment_data[:items]
|
191
|
-
# Use specified items type from comment
|
192
|
-
property_schema["items"] = { "type" => comment_data[:items] }
|
193
|
-
else
|
194
|
-
# Default to object items
|
195
|
-
property_schema["items"] = { "type" => "object" }
|
196
|
-
end
|
197
|
-
elsif comment_data[:type] && comment_data[:type] != "TODO: MISSING COMMENT"
|
198
|
-
property_schema["type"] = comment_data[:type]
|
199
|
-
|
200
|
-
# Handle array types
|
201
|
-
if comment_data[:type] == "array"
|
202
|
-
if comment_data[:items]
|
203
|
-
# Use specified items type
|
204
|
-
property_schema["items"] = { "type" => comment_data[:items] }
|
205
|
-
else
|
206
|
-
# Default to string items if no items type is specified
|
207
|
-
property_schema["items"] = { "type" => "string" }
|
208
|
-
end
|
209
|
-
end
|
210
|
-
else
|
211
|
-
# Only show TODO message if no @openapi comment exists at all
|
212
|
-
property_schema["type"] = "string"
|
213
|
-
if comment_data.nil? || comment_data.empty?
|
214
|
-
property_schema["description"] = "TODO: MISSING COMMENT - Add @openapi comment"
|
215
|
-
end
|
175
|
+
# Builds property schema for a single property
|
176
|
+
# @param prop [Hash] Property hash with comment_data and nested properties
|
177
|
+
# @return [Hash] Property schema
|
178
|
+
def build_property_schema(prop)
|
179
|
+
comment_data = prop[:comment_data] || prop["comment_data"]
|
180
|
+
property_type = comment_data[:type] || comment_data["type"] || "string"
|
181
|
+
|
182
|
+
property_schema = {
|
183
|
+
"type" => property_type
|
184
|
+
}
|
185
|
+
|
186
|
+
# Add description if present
|
187
|
+
if comment_data[:description] || comment_data["description"]
|
188
|
+
property_schema["description"] = comment_data[:description] || comment_data["description"]
|
216
189
|
end
|
217
190
|
|
218
|
-
# Add
|
219
|
-
|
220
|
-
|
191
|
+
# Add enum if present
|
192
|
+
if comment_data[:enum] || comment_data["enum"]
|
193
|
+
property_schema["enum"] = comment_data[:enum] || comment_data["enum"]
|
194
|
+
end
|
221
195
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
nested_properties.each do |node|
|
232
|
-
property = node[:property]
|
233
|
-
comment_data = node[:comment_data] || {}
|
234
|
-
|
235
|
-
property_schema = build_property_schema(node)
|
236
|
-
schema["properties"][property] = property_schema
|
237
|
-
if comment_data[:required] == "true" && !node[:is_conditional]
|
238
|
-
schema["required"] << property
|
196
|
+
# Handle nested object properties
|
197
|
+
if property_type == "object" && (prop[:nested_properties] || prop["nested_properties"])
|
198
|
+
nested_properties = prop[:nested_properties] || prop["nested_properties"]
|
199
|
+
nested_schema = build_schema(nested_properties)
|
200
|
+
property_schema["properties"] = nested_schema["properties"]
|
201
|
+
|
202
|
+
# Only add required array if there are non-conditional required properties
|
203
|
+
if nested_schema["required"] && !nested_schema["required"].empty?
|
204
|
+
property_schema["required"] = nested_schema["required"]
|
239
205
|
end
|
240
206
|
end
|
241
|
-
|
242
|
-
|
207
|
+
|
208
|
+
property_schema
|
243
209
|
end
|
244
|
-
end
|
245
210
|
|
246
|
-
|
247
|
-
#
|
248
|
-
# @return [
|
249
|
-
def
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
puts "❌ Missing @openapi comments found!"
|
255
|
-
exit 1
|
211
|
+
# Builds array schema from properties containing array root
|
212
|
+
# @param properties [Array<Hash>] Array of property hashes
|
213
|
+
# @return [Hash] Array schema object
|
214
|
+
def build_array_schema(properties)
|
215
|
+
array_root = properties.find { |p| p[:is_array_root] || p["is_array_root"] }
|
216
|
+
|
217
|
+
unless array_root
|
218
|
+
raise ArgumentError, "No array root property found in properties"
|
256
219
|
end
|
257
220
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
221
|
+
schema = {
|
222
|
+
"type" => "array"
|
223
|
+
}
|
224
|
+
|
225
|
+
# Build items schema from array_item_properties
|
226
|
+
array_item_properties = array_root[:array_item_properties] || array_root["array_item_properties"]
|
227
|
+
if array_item_properties && !array_item_properties.empty?
|
228
|
+
items_schema = build_schema(array_item_properties)
|
229
|
+
schema["items"] = items_schema
|
230
|
+
else
|
231
|
+
schema["items"] = { "type" => "object" }
|
262
232
|
end
|
263
233
|
|
264
|
-
|
234
|
+
schema
|
265
235
|
end
|
236
|
+
|
237
|
+
# NOTE: Old hash-based processing methods have been removed
|
238
|
+
# The system now uses AST-based processing with AstToSchemaProcessor
|
266
239
|
end
|
267
|
-
end
|
240
|
+
end
|
@@ -4,59 +4,58 @@ namespace :openapi do
|
|
4
4
|
desc "Import OpenAPI specification and add @openapi comments to Jbuilder files"
|
5
5
|
task :import, [:openapi_file] => :environment do |_task, args|
|
6
6
|
openapi_file = args[:openapi_file]
|
7
|
-
|
7
|
+
|
8
8
|
# Set debug mode if verbose flag is passed
|
9
9
|
ENV['DEBUG_OPENAPI_IMPORT'] = '1' if ENV['VERBOSE'] == 'true'
|
10
|
-
|
10
|
+
|
11
11
|
RailsOpenapiGen.import(openapi_file)
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
desc "Diagnose import issues by comparing OpenAPI paths with Rails routes"
|
15
15
|
task :diagnose_import, [:openapi_file] => :environment do |_task, args|
|
16
|
-
openapi_file = args[:openapi_file] || File.join(RailsOpenapiGen.configuration.output_directory,
|
17
|
-
|
18
|
-
|
16
|
+
openapi_file = args[:openapi_file] || File.join(RailsOpenapiGen.configuration.output_directory,
|
17
|
+
RailsOpenapiGen.configuration.output_filename)
|
18
|
+
|
19
19
|
unless File.exist?(openapi_file)
|
20
20
|
puts "❌ OpenAPI file not found: #{openapi_file}"
|
21
21
|
exit 1
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
puts "🔍 Diagnosing OpenAPI import for: #{openapi_file}"
|
25
25
|
puts ""
|
26
|
-
|
26
|
+
|
27
27
|
# Load OpenAPI spec
|
28
28
|
openapi_spec = YAML.load_file(openapi_file)
|
29
29
|
openapi_paths = openapi_spec['paths'] || {}
|
30
|
-
|
30
|
+
|
31
31
|
# Get Rails routes
|
32
32
|
routes_parser = RailsOpenapiGen::Parsers::RoutesParser.new
|
33
33
|
rails_routes = routes_parser.parse
|
34
|
-
|
35
|
-
#
|
36
|
-
|
37
|
-
|
34
|
+
|
35
|
+
# NOTE: routes_by_path grouping removed as it's not used in current implementation
|
36
|
+
|
38
37
|
puts "📊 Summary:"
|
39
38
|
puts " OpenAPI paths: #{openapi_paths.size}"
|
40
39
|
puts " Rails routes: #{rails_routes.size}"
|
41
40
|
puts ""
|
42
|
-
|
41
|
+
|
43
42
|
# Check each OpenAPI path
|
44
43
|
unmatched_paths = []
|
45
44
|
matched_paths = []
|
46
|
-
|
45
|
+
|
47
46
|
openapi_paths.each do |path, methods|
|
48
|
-
methods.each do |method,
|
47
|
+
methods.each do |method, _operation|
|
49
48
|
next if method == 'parameters'
|
50
|
-
|
49
|
+
|
51
50
|
# Convert OpenAPI path to Rails format
|
52
51
|
rails_path = path.gsub(/\{(\w+)\}/, ':\1')
|
53
|
-
|
52
|
+
|
54
53
|
# Try to find matching route
|
55
54
|
matching_routes = rails_routes.select do |route|
|
56
|
-
route[:method] == method.upcase &&
|
57
|
-
|
55
|
+
route[:method] == method.upcase &&
|
56
|
+
route[:path].gsub(%r{/$}, '') == rails_path.gsub(%r{/$}, '')
|
58
57
|
end
|
59
|
-
|
58
|
+
|
60
59
|
if matching_routes.empty?
|
61
60
|
unmatched_paths << { path: path, method: method.upcase, rails_path: rails_path }
|
62
61
|
else
|
@@ -64,7 +63,7 @@ namespace :openapi do
|
|
64
63
|
end
|
65
64
|
end
|
66
65
|
end
|
67
|
-
|
66
|
+
|
68
67
|
# Report results
|
69
68
|
if matched_paths.any?
|
70
69
|
puts "✅ Matched paths (#{matched_paths.size}):"
|
@@ -76,35 +75,35 @@ namespace :openapi do
|
|
76
75
|
end
|
77
76
|
puts ""
|
78
77
|
end
|
79
|
-
|
78
|
+
|
80
79
|
if unmatched_paths.any?
|
81
80
|
puts "❌ Unmatched OpenAPI paths (#{unmatched_paths.size}):"
|
82
81
|
unmatched_paths.each do |unmatched|
|
83
82
|
puts " #{unmatched[:method]} #{unmatched[:path]}"
|
84
|
-
|
83
|
+
|
85
84
|
# Suggest similar routes
|
86
85
|
similar_routes = rails_routes.select do |route|
|
87
|
-
route[:method] == unmatched[:method] &&
|
88
|
-
|
86
|
+
route[:method] == unmatched[:method] &&
|
87
|
+
route[:path].include?(unmatched[:rails_path].split('/').reject(&:empty?).first.to_s)
|
89
88
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
89
|
+
|
90
|
+
next unless similar_routes.any?
|
91
|
+
|
92
|
+
puts " Did you mean one of these?"
|
93
|
+
similar_routes.take(3).each do |route|
|
94
|
+
puts " - #{route[:path]}"
|
96
95
|
end
|
97
96
|
end
|
98
97
|
puts ""
|
99
98
|
end
|
100
|
-
|
99
|
+
|
101
100
|
# Check for Jbuilder templates
|
102
101
|
puts "📄 Checking Jbuilder templates:"
|
103
102
|
matched_paths.each do |match|
|
104
103
|
route = match[:route]
|
105
104
|
controller_parser = RailsOpenapiGen::Parsers::ControllerParser.new(route)
|
106
105
|
controller_info = controller_parser.parse
|
107
|
-
|
106
|
+
|
108
107
|
if controller_info[:jbuilder_path]
|
109
108
|
if File.exist?(controller_info[:jbuilder_path])
|
110
109
|
puts " ✅ #{match[:method]} #{match[:path]} → #{controller_info[:jbuilder_path]}"
|
@@ -115,7 +114,7 @@ namespace :openapi do
|
|
115
114
|
puts " ⚠️ #{match[:method]} #{match[:path]} → No Jbuilder template found"
|
116
115
|
end
|
117
116
|
end
|
118
|
-
|
117
|
+
|
119
118
|
puts ""
|
120
119
|
puts "💡 Tips:"
|
121
120
|
puts " - Ensure your OpenAPI paths match your Rails routes exactly"
|
@@ -123,4 +122,4 @@ namespace :openapi do
|
|
123
122
|
puts " - Create Jbuilder templates for API endpoints that need them"
|
124
123
|
puts " - Run 'DEBUG_OPENAPI_IMPORT=1 rake openapi:import[#{openapi_file}]' for detailed import logs"
|
125
124
|
end
|
126
|
-
end
|
125
|
+
end
|
data/rails-openapi-gen.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "rails-openapi-gen"
|
5
|
-
spec.version = "0.0.
|
5
|
+
spec.version = "0.0.3"
|
6
6
|
spec.authors = ["myzkey"]
|
7
7
|
spec.email = ["myzkey.dev@example.com"]
|
8
8
|
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = "Generates OpenAPI specs from Rails apps by parsing routes, controllers, and view templates with @openapi comment annotations"
|
11
11
|
spec.homepage = "https://github.com/myzkey/rails-openapi-gen"
|
12
12
|
spec.license = "MIT"
|
13
|
-
spec.required_ruby_version = ">=
|
13
|
+
spec.required_ruby_version = ">= 3.0.0"
|
14
14
|
|
15
15
|
spec.metadata["homepage_uri"] = spec.homepage
|
16
16
|
spec.metadata["source_code_uri"] = spec.homepage
|
@@ -20,11 +20,12 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
+
spec.add_dependency "parser", "~> 3.1.0"
|
23
24
|
spec.add_dependency "rails", ">= 6.0"
|
24
|
-
spec.add_dependency "parser", "~> 3.2"
|
25
25
|
spec.add_dependency "yaml", "~> 0.2"
|
26
26
|
|
27
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
27
28
|
spec.add_development_dependency "rspec", "~> 3.12"
|
28
29
|
spec.add_development_dependency "rubocop", "~> 1.50"
|
29
|
-
spec.
|
30
|
-
end
|
30
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
31
|
+
end
|