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
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_processor'
|
4
|
+
require_relative 'array_processor'
|
5
|
+
require_relative 'object_processor'
|
6
|
+
require_relative 'property_processor'
|
7
|
+
require_relative 'partial_processor'
|
8
|
+
require_relative '../call_detectors'
|
9
|
+
|
10
|
+
module RailsOpenapiGen::Parsers::Jbuilder::Processors
|
11
|
+
class CompositeProcessor < BaseProcessor
|
12
|
+
# Alias for shorter reference to call detectors
|
13
|
+
CallDetectors = RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
|
14
|
+
# Initializes composite processor with all sub-processors
|
15
|
+
# @param file_path [String] Path to current file
|
16
|
+
# @param property_parser [PropertyCommentParser] Parser for property comments
|
17
|
+
def initialize(file_path, property_parser)
|
18
|
+
super(file_path, property_parser)
|
19
|
+
|
20
|
+
# Initialize sub-processors
|
21
|
+
@array_processor = ArrayProcessor.new(file_path, property_parser)
|
22
|
+
@object_processor = ObjectProcessor.new(file_path, property_parser)
|
23
|
+
@property_processor = PropertyProcessor.new(file_path, property_parser)
|
24
|
+
@partial_processor = PartialProcessor.new(file_path, property_parser)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Processes method call nodes by delegating to appropriate processors
|
28
|
+
# @param node [Parser::AST::Node] Method call node
|
29
|
+
# @return [void]
|
30
|
+
def on_send(node)
|
31
|
+
receiver, method_name, = node.children
|
32
|
+
|
33
|
+
# Skip Jbuilder helper methods - they are not JSON properties
|
34
|
+
if CallDetectors::CacheCallDetector.cache_call?(receiver, method_name) ||
|
35
|
+
CallDetectors::CacheCallDetector.cache_if_call?(receiver, method_name) ||
|
36
|
+
CallDetectors::KeyFormatDetector.key_format?(receiver, method_name) ||
|
37
|
+
CallDetectors::NullHandlingDetector.null_handling?(receiver, method_name) ||
|
38
|
+
CallDetectors::ObjectManipulationDetector.object_manipulation?(receiver, method_name)
|
39
|
+
super
|
40
|
+
elsif CallDetectors::ArrayCallDetector.array_call?(receiver, method_name)
|
41
|
+
@array_processor.on_send(node)
|
42
|
+
merge_processor_results(@array_processor)
|
43
|
+
elsif CallDetectors::PartialCallDetector.partial_call?(receiver, method_name)
|
44
|
+
@partial_processor.on_send(node)
|
45
|
+
merge_processor_results(@partial_processor)
|
46
|
+
elsif CallDetectors::JsonCallDetector.json_property?(receiver, method_name)
|
47
|
+
@property_processor.on_send(node)
|
48
|
+
merge_processor_results(@property_processor)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Processes block nodes by delegating to appropriate processors
|
53
|
+
# @param node [Parser::AST::Node] Block node
|
54
|
+
# @return [void]
|
55
|
+
def on_block(node)
|
56
|
+
send_node, args_node, body = node.children
|
57
|
+
receiver, method_name, = send_node.children
|
58
|
+
|
59
|
+
if CallDetectors::CacheCallDetector.cache_call?(receiver, method_name) ||
|
60
|
+
CallDetectors::CacheCallDetector.cache_if_call?(receiver, method_name)
|
61
|
+
# This is json.cache! or json.cache_if! block - just process the block contents
|
62
|
+
process(body) if body
|
63
|
+
elsif CallDetectors::JsonCallDetector.json_property?(receiver, method_name) && method_name != :array!
|
64
|
+
# Check if this is an array iteration block (has block arguments)
|
65
|
+
if args_node && args_node.type == :args && args_node.children.any?
|
66
|
+
# This is an array iteration block like json.tags @tags do |tag|
|
67
|
+
@array_processor.on_block(node)
|
68
|
+
merge_processor_results(@array_processor)
|
69
|
+
else
|
70
|
+
# This is a nested object block like json.profile do
|
71
|
+
@object_processor.on_block(node)
|
72
|
+
merge_processor_results(@object_processor)
|
73
|
+
end
|
74
|
+
elsif CallDetectors::ArrayCallDetector.array_call?(receiver, method_name)
|
75
|
+
# This is json.array! block
|
76
|
+
@array_processor.on_block(node)
|
77
|
+
merge_processor_results(@array_processor)
|
78
|
+
else
|
79
|
+
super
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Merges results from a sub-processor into this processor
|
86
|
+
# @param processor [BaseProcessor] Sub-processor to merge results from
|
87
|
+
# @return [void]
|
88
|
+
def merge_processor_results(processor)
|
89
|
+
# Use getter methods to ensure arrays exist
|
90
|
+
properties.concat(processor.properties)
|
91
|
+
partials.concat(processor.partials)
|
92
|
+
|
93
|
+
# Clear the sub-processor's results by calling private clear methods
|
94
|
+
processor.send(:clear_results)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_processor'
|
4
|
+
require_relative '../call_detectors'
|
5
|
+
|
6
|
+
module RailsOpenapiGen::Parsers::Jbuilder::Processors
|
7
|
+
class ObjectProcessor < BaseProcessor
|
8
|
+
# Alias for shorter reference to call detectors
|
9
|
+
CallDetectors = RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
|
10
|
+
# Lazy load CompositeProcessor to avoid circular dependency
|
11
|
+
def self.composite_processor_class
|
12
|
+
RailsOpenapiGen::Parsers::Jbuilder::Processors::CompositeProcessor
|
13
|
+
end
|
14
|
+
|
15
|
+
# Processes block nodes for nested object blocks
|
16
|
+
# @param node [Parser::AST::Node] Block node
|
17
|
+
# @return [void]
|
18
|
+
def on_block(node)
|
19
|
+
send_node, args_node, = node.children
|
20
|
+
receiver, method_name, = send_node.children
|
21
|
+
|
22
|
+
if CallDetectors::JsonCallDetector.json_property?(receiver, method_name) && method_name != :array!
|
23
|
+
# Check if this is a nested object block (no block arguments)
|
24
|
+
if !args_node || args_node.type != :args || args_node.children.empty?
|
25
|
+
# This is a nested object block like json.profile do
|
26
|
+
process_nested_object_block(node, method_name.to_s)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Processes nested object blocks (e.g., json.profile do)
|
38
|
+
# @param node [Parser::AST::Node] Block node
|
39
|
+
# @param property_name [String] Object property name
|
40
|
+
# @return [void]
|
41
|
+
def process_nested_object_block(node, property_name)
|
42
|
+
comment_data = find_comment_for_node(node)
|
43
|
+
|
44
|
+
# Save current context
|
45
|
+
previous_nested_objects = @nested_objects.dup
|
46
|
+
previous_properties = properties.dup
|
47
|
+
previous_partials = partials.dup
|
48
|
+
|
49
|
+
# Create a temporary properties array for this nested object
|
50
|
+
@properties = []
|
51
|
+
@partials = []
|
52
|
+
push_block(:object)
|
53
|
+
|
54
|
+
# Process the block contents using CompositeProcessor
|
55
|
+
_, _args, body = node.children
|
56
|
+
if body
|
57
|
+
# Create a CompositeProcessor to handle all types of calls within the block
|
58
|
+
composite_processor = self.class.composite_processor_class.new(@file_path, @property_parser)
|
59
|
+
composite_processor.process(body)
|
60
|
+
|
61
|
+
# Merge results from the composite processor
|
62
|
+
@properties.concat(composite_processor.properties)
|
63
|
+
@partials.concat(composite_processor.partials)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Collect nested properties from direct block processing
|
67
|
+
nested_properties = properties.dup
|
68
|
+
|
69
|
+
# Process any partials found in this block
|
70
|
+
partials.each do |partial_path|
|
71
|
+
if File.exist?(partial_path)
|
72
|
+
partial_properties = parse_partial_for_nested_object(partial_path)
|
73
|
+
nested_properties.concat(partial_properties)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Check if this object block contains only a json.array! call
|
78
|
+
# In that case, we should treat this as a direct array instead of an object
|
79
|
+
if ENV['RAILS_OPENAPI_DEBUG'] && nested_properties.size == 1
|
80
|
+
prop = nested_properties.first
|
81
|
+
if prop.respond_to?(:property_name)
|
82
|
+
is_root = prop.respond_to?(:is_root_array) ? prop.is_root_array : false
|
83
|
+
puts "🔍 DEBUG: Checking if object contains only array - property: #{prop.property_name}, is_array_root: #{is_root}, class: #{prop.class.name}"
|
84
|
+
else
|
85
|
+
puts "🔍 DEBUG: Checking if object contains only array - property: #{prop[:property_name] || prop[:property]}, is_array_root: #{prop[:is_array_root]}, node_type: #{prop[:node_type]}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
has_only_array_root = nested_properties.size == 1 && is_array_root_property(nested_properties.first)
|
89
|
+
|
90
|
+
# Restore context but keep partials for higher level processing
|
91
|
+
@properties = previous_properties
|
92
|
+
@partials = previous_partials
|
93
|
+
@nested_objects = previous_nested_objects
|
94
|
+
pop_block
|
95
|
+
|
96
|
+
if has_only_array_root
|
97
|
+
# This is a json.property do + json.array! pattern
|
98
|
+
# Treat the property as a direct array instead of an object with items
|
99
|
+
array_root_node = nested_properties.first
|
100
|
+
|
101
|
+
# Create comment data
|
102
|
+
comment_obj = if comment_data
|
103
|
+
RailsOpenapiGen::AstNodes::CommentData.new(
|
104
|
+
type: comment_data[:type] || 'array',
|
105
|
+
items: comment_data[:items] || { type: 'object' }
|
106
|
+
)
|
107
|
+
else
|
108
|
+
RailsOpenapiGen::AstNodes::CommentData.new(type: 'array', items: { type: 'object' })
|
109
|
+
end
|
110
|
+
|
111
|
+
# Create array property node
|
112
|
+
array_item_properties = get_array_item_properties(array_root_node)
|
113
|
+
property_node = RailsOpenapiGen::AstNodes::PropertyNodeFactory.create_array(
|
114
|
+
property: property_name,
|
115
|
+
comment_data: comment_obj,
|
116
|
+
array_item_properties: array_item_properties
|
117
|
+
)
|
118
|
+
else
|
119
|
+
# Store nested object info
|
120
|
+
@nested_objects[property_name] = nested_properties
|
121
|
+
|
122
|
+
# Create comment data
|
123
|
+
comment_obj = if comment_data
|
124
|
+
RailsOpenapiGen::AstNodes::CommentData.new(
|
125
|
+
type: comment_data[:type] || 'object'
|
126
|
+
)
|
127
|
+
else
|
128
|
+
RailsOpenapiGen::AstNodes::CommentData.new(type: 'object')
|
129
|
+
end
|
130
|
+
|
131
|
+
# Add the parent property as a regular object
|
132
|
+
property_node = RailsOpenapiGen::AstNodes::PropertyNodeFactory.create_object(
|
133
|
+
property: property_name,
|
134
|
+
comment_data: comment_obj,
|
135
|
+
nested_properties: nested_properties
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
add_property(property_node)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Helper methods for structured AST support
|
143
|
+
|
144
|
+
# Checks if a property is an array root property
|
145
|
+
# @param property [PropertyNode, Hash] Property to check
|
146
|
+
# @return [Boolean] True if it's an array root property
|
147
|
+
def is_array_root_property(property)
|
148
|
+
if property.is_a?(Hash)
|
149
|
+
property_name = property[:property_name] || property[:property]
|
150
|
+
(property[:is_array_root] || property[:node_type] == 'array') &&
|
151
|
+
(property_name == 'items' || property[:is_array_root])
|
152
|
+
elsif property.respond_to?(:is_root_array)
|
153
|
+
# Structured AST node
|
154
|
+
property.is_root_array ||
|
155
|
+
(property.respond_to?(:property_name) && property.property_name == 'items')
|
156
|
+
else
|
157
|
+
property.is_a?(RailsOpenapiGen::AstNodes::ArrayRootNode) ||
|
158
|
+
(property.is_a?(RailsOpenapiGen::AstNodes::ArrayPropertyNode) && property.property == 'items')
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Gets array item properties from an array root node
|
163
|
+
# @param array_node [PropertyNode, Hash] Array root node
|
164
|
+
# @return [Array] Array of item properties
|
165
|
+
def get_array_item_properties(array_node)
|
166
|
+
if array_node.is_a?(Hash)
|
167
|
+
array_node[:array_item_properties] || []
|
168
|
+
elsif array_node.respond_to?(:children)
|
169
|
+
# For ArrayNode, the children are the item properties
|
170
|
+
array_node.children || []
|
171
|
+
else
|
172
|
+
array_node.array_item_properties || []
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_processor'
|
4
|
+
require_relative '../call_detectors'
|
5
|
+
|
6
|
+
module RailsOpenapiGen::Parsers::Jbuilder::Processors
|
7
|
+
class PartialProcessor < BaseProcessor
|
8
|
+
# Alias for shorter reference to call detectors
|
9
|
+
CallDetectors = RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
|
10
|
+
# Processes method call nodes for partial render calls
|
11
|
+
# @param node [Parser::AST::Node] Method call node
|
12
|
+
# @return [void]
|
13
|
+
def on_send(node)
|
14
|
+
receiver, method_name, *args = node.children
|
15
|
+
|
16
|
+
process_partial(args) if RailsOpenapiGen::Parsers::Jbuilder::CallDetectors::PartialCallDetector.partial_call?(
|
17
|
+
receiver, method_name
|
18
|
+
)
|
19
|
+
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Processes partial render calls to track dependencies
|
26
|
+
# @param args [Array] Partial call arguments
|
27
|
+
# @return [void]
|
28
|
+
def process_partial(args)
|
29
|
+
return if args.empty?
|
30
|
+
|
31
|
+
partial_name = extract_partial_name(args)
|
32
|
+
return unless partial_name
|
33
|
+
|
34
|
+
puts "🔍 DEBUG: Found partial: #{partial_name}" if ENV['RAILS_OPENAPI_DEBUG']
|
35
|
+
partial_path = resolve_partial_path(partial_name)
|
36
|
+
puts "🔍 DEBUG: Resolved partial path: #{partial_path}" if ENV['RAILS_OPENAPI_DEBUG']
|
37
|
+
puts "🔍 DEBUG: Partial exists: #{File.exist?(partial_path)}" if ENV['RAILS_OPENAPI_DEBUG'] && partial_path
|
38
|
+
@partials << partial_path if partial_path
|
39
|
+
end
|
40
|
+
|
41
|
+
# Extracts partial name from arguments (handles both string and hash syntax)
|
42
|
+
# @param args [Array] Partial call arguments
|
43
|
+
# @return [String, nil] Partial name or nil
|
44
|
+
def extract_partial_name(args)
|
45
|
+
first_arg = args.first
|
46
|
+
|
47
|
+
# Handle simple string case: json.partial! 'path/to/partial'
|
48
|
+
if first_arg.type == :str
|
49
|
+
return first_arg.children.first
|
50
|
+
end
|
51
|
+
|
52
|
+
# Handle hash case: json.partial! partial: 'path/to/partial', locals: {...}
|
53
|
+
if first_arg.type == :hash
|
54
|
+
first_arg.children.each do |pair|
|
55
|
+
next unless pair&.type == :pair
|
56
|
+
|
57
|
+
key, value = pair.children
|
58
|
+
next unless key && value
|
59
|
+
|
60
|
+
if key.type == :sym && key.children.first == :partial && value.type == :str
|
61
|
+
return value.children.first
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_processor'
|
4
|
+
require_relative '../call_detectors'
|
5
|
+
|
6
|
+
module RailsOpenapiGen::Parsers::Jbuilder::Processors
|
7
|
+
class PropertyProcessor < BaseProcessor
|
8
|
+
# Alias for shorter reference to call detectors
|
9
|
+
CallDetectors = RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
|
10
|
+
# Processes method call nodes for simple property assignments
|
11
|
+
# @param node [Parser::AST::Node] Method call node
|
12
|
+
# @return [void]
|
13
|
+
def on_send(node)
|
14
|
+
receiver, method_name, *args = node.children
|
15
|
+
|
16
|
+
if CallDetectors::JsonCallDetector.json_property?(receiver, method_name)
|
17
|
+
process_json_property(node, method_name.to_s, args)
|
18
|
+
else
|
19
|
+
# For non-JSON property calls, don't process anything to avoid creating
|
20
|
+
# properties for variable access calls
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Processes a simple JSON property assignment
|
28
|
+
# @param node [Parser::AST::Node] Property node
|
29
|
+
# @param property_name [String] Name of the property
|
30
|
+
# @param args [Array] Method arguments
|
31
|
+
# @return [void]
|
32
|
+
def process_json_property(node, property_name, _args)
|
33
|
+
comment_data = find_comment_for_node(node)
|
34
|
+
|
35
|
+
# Process simple property regardless of context
|
36
|
+
# Note: In the future, different processing could be added based on context
|
37
|
+
process_simple_property(node, property_name, comment_data)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Processes a simple property assignment
|
41
|
+
# @param node [Parser::AST::Node] Property node
|
42
|
+
# @param property_name [String] Name of the property
|
43
|
+
# @param comment_data [Hash, nil] Parsed comment data
|
44
|
+
# @return [void]
|
45
|
+
def process_simple_property(_node, property_name, comment_data)
|
46
|
+
# Create comment data object
|
47
|
+
comment_obj = if comment_data && !comment_data.empty?
|
48
|
+
RailsOpenapiGen::AstNodes::CommentData.new(
|
49
|
+
type: comment_data[:type],
|
50
|
+
description: comment_data[:description],
|
51
|
+
required: comment_data[:required],
|
52
|
+
enum: comment_data[:enum],
|
53
|
+
field_name: comment_data[:field_name]
|
54
|
+
)
|
55
|
+
else
|
56
|
+
RailsOpenapiGen::AstNodes::CommentData.new(type: 'TODO: MISSING COMMENT')
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create simple property node
|
60
|
+
property_node = RailsOpenapiGen::AstNodes::PropertyNodeFactory.create_simple(
|
61
|
+
property: property_name,
|
62
|
+
comment_data: comment_obj
|
63
|
+
)
|
64
|
+
|
65
|
+
add_property(property_node)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsOpenapiGen::Parsers::Jbuilder::Processors
|
4
|
+
autoload :BaseProcessor, "rails-openapi-gen/parsers/jbuilder/processors/base_processor"
|
5
|
+
autoload :CompositeProcessor, "rails-openapi-gen/parsers/jbuilder/processors/composite_processor"
|
6
|
+
autoload :ArrayProcessor, "rails-openapi-gen/parsers/jbuilder/processors/array_processor"
|
7
|
+
autoload :ObjectProcessor, "rails-openapi-gen/parsers/jbuilder/processors/object_processor"
|
8
|
+
autoload :PropertyProcessor, "rails-openapi-gen/parsers/jbuilder/processors/property_processor"
|
9
|
+
autoload :PartialProcessor, "rails-openapi-gen/parsers/jbuilder/processors/partial_processor"
|
10
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../comment_parser'
|
4
|
+
|
5
|
+
module RailsOpenapiGen::Parsers::Jbuilder
|
6
|
+
class PropertyCommentParser
|
7
|
+
# Initializes property comment parser
|
8
|
+
# @param comments [Array] Array of comment objects
|
9
|
+
def initialize(comments)
|
10
|
+
@comments = comments
|
11
|
+
@comment_parser = RailsOpenapiGen::Parsers::CommentParser.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# Finds property comment for a specific line
|
15
|
+
# @param line_number [Integer] Line number to find comment for
|
16
|
+
# @return [Hash, nil] Parsed comment data or nil
|
17
|
+
def find_property_comment_for_line(line_number)
|
18
|
+
@comments.reverse.find do |comment|
|
19
|
+
comment_line = comment.location.line
|
20
|
+
comment_line == line_number - 1 || comment_line == line_number
|
21
|
+
end&.then do |comment|
|
22
|
+
@comment_parser.parse(comment.text)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsOpenapiGen::Parsers::Jbuilder
|
4
|
+
autoload :JbuilderParser, "rails-openapi-gen/parsers/jbuilder/jbuilder_parser"
|
5
|
+
autoload :AstParser, "rails-openapi-gen/parsers/jbuilder/ast_parser"
|
6
|
+
autoload :CallDetectors, "rails-openapi-gen/parsers/jbuilder/call_detectors"
|
7
|
+
autoload :Processors, "rails-openapi-gen/parsers/jbuilder/processors"
|
8
|
+
autoload :OperationCommentParser, "rails-openapi-gen/parsers/jbuilder/operation_comment_parser"
|
9
|
+
autoload :PropertyCommentParser, "rails-openapi-gen/parsers/jbuilder/property_comment_parser"
|
10
|
+
end
|
@@ -1,33 +1,107 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_support/inflector'
|
4
|
+
|
3
5
|
module RailsOpenapiGen
|
4
6
|
module Parsers
|
5
7
|
class RoutesParser
|
8
|
+
# Initialize with optional file existence checker for testability
|
9
|
+
# @param file_checker [#call] Callable that checks if file exists (defaults to File.exist?)
|
10
|
+
def initialize(file_checker: File.method(:exist?))
|
11
|
+
@file_checker = file_checker
|
12
|
+
end
|
13
|
+
|
6
14
|
# Parses Rails application routes to extract route information
|
7
15
|
# @return [Array<Hash>] Array of route hashes with method, path, controller, action, and name
|
8
16
|
def parse
|
9
17
|
routes = []
|
10
|
-
|
18
|
+
|
11
19
|
Rails.application.routes.routes.each do |route|
|
12
20
|
next unless route.defaults[:controller] && route.defaults[:action]
|
13
21
|
next if route.respond_to?(:internal?) ? route.internal? : route.instance_variable_get(:@internal)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
22
|
+
|
23
|
+
# Skip Rails internal and asset routes
|
24
|
+
controller_name = route.defaults[:controller]
|
25
|
+
next if controller_name.to_s.start_with?('rails/')
|
26
|
+
next if controller_name.to_s == 'assets'
|
27
|
+
|
28
|
+
# Extract HTTP method from route.verb (which can be a Regexp like /^GET$/)
|
29
|
+
raw_method = route.verb.is_a?(Array) ? route.verb.first : route.verb
|
30
|
+
method = if raw_method.is_a?(Regexp)
|
31
|
+
raw_method.source.gsub(/[\^$()?\-:mix]/, '')
|
32
|
+
else
|
33
|
+
raw_method.to_s
|
34
|
+
end
|
35
|
+
# Remove format suffix patterns more robustly
|
36
|
+
path = route.path.spec.to_s
|
37
|
+
.gsub(/\(\.:format\)$/, "") # Standard format pattern
|
38
|
+
.gsub(/\(\.\*format\)$/, "") # Wildcard format pattern
|
39
|
+
.gsub(/\(\.[\w|*]*\)$/, "") # Complex format patterns like (.json|.xml|.csv)
|
40
|
+
.gsub(/\(\.[^)]*\)$/, "")
|
41
|
+
controller = infer_controller_from_route(route)
|
18
42
|
action = route.defaults[:action]
|
19
|
-
|
43
|
+
|
20
44
|
routes << {
|
21
|
-
|
45
|
+
verb: method, # Test expects 'verb'
|
46
|
+
method: method, # Keep for backward compatibility
|
22
47
|
path: path,
|
23
48
|
controller: controller,
|
24
49
|
action: action,
|
25
50
|
name: route.name
|
26
51
|
}
|
27
52
|
end
|
28
|
-
|
53
|
+
|
29
54
|
routes
|
30
55
|
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Infers the correct controller name from route information
|
60
|
+
# Uses route name pattern to identify nested resources
|
61
|
+
# @param route [ActionDispatch::Journey::Route] Rails route object
|
62
|
+
# @return [String] Controller name
|
63
|
+
def infer_controller_from_route(route)
|
64
|
+
default_controller = route.defaults[:controller]
|
65
|
+
route_name = route.name
|
66
|
+
|
67
|
+
# If route name indicates a nested resource, use it to infer the controller
|
68
|
+
if route_name && route_name.include?('_')
|
69
|
+
# Parse route name to extract controller path
|
70
|
+
# Example: "api_user_orders" -> "api/users/orders"
|
71
|
+
parts = route_name.split('_')
|
72
|
+
|
73
|
+
# Remove action suffix if present (index, show, create, etc.)
|
74
|
+
action = route.defaults[:action]
|
75
|
+
if action && parts.last == action
|
76
|
+
parts.pop
|
77
|
+
end
|
78
|
+
|
79
|
+
# Convert route name parts to controller path
|
80
|
+
if parts.length > 1
|
81
|
+
# Check if this looks like a nested resource
|
82
|
+
# api_user_orders -> api/users/orders
|
83
|
+
# api_post_comments -> api/posts/comments
|
84
|
+
potential_nested = parts.map.with_index do |part, index|
|
85
|
+
if index == 0 || index == parts.length - 1
|
86
|
+
part # Keep namespace and final resource as-is (e.g., "api", "orders")
|
87
|
+
else
|
88
|
+
# Skip pluralization for version numbers (v1, v2, etc.)
|
89
|
+
part.match?(/^v\d+$/) ? part : part.pluralize
|
90
|
+
end
|
91
|
+
end.join('/')
|
92
|
+
|
93
|
+
# Check if the nested controller file exists
|
94
|
+
nested_controller_path = Rails.root.join("app", "controllers", "#{potential_nested}_controller.rb")
|
95
|
+
|
96
|
+
if @file_checker.call(nested_controller_path.to_s)
|
97
|
+
return potential_nested
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Fall back to default controller name
|
103
|
+
default_controller
|
104
|
+
end
|
31
105
|
end
|
32
106
|
end
|
33
|
-
end
|
107
|
+
end
|