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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CLAUDE.md +17 -5
  4. data/README.md +25 -0
  5. data/lib/rails-openapi-gen/ast_nodes/array_node.rb +101 -0
  6. data/lib/rails-openapi-gen/ast_nodes/base_node.rb +139 -0
  7. data/lib/rails-openapi-gen/ast_nodes/comment_data.rb +180 -0
  8. data/lib/rails-openapi-gen/ast_nodes/node_factory.rb +206 -0
  9. data/lib/rails-openapi-gen/ast_nodes/object_node.rb +129 -0
  10. data/lib/rails-openapi-gen/ast_nodes/partial_node.rb +111 -0
  11. data/lib/rails-openapi-gen/ast_nodes/property_node.rb +74 -0
  12. data/lib/rails-openapi-gen/ast_nodes.rb +129 -0
  13. data/lib/rails-openapi-gen/configuration.rb +154 -22
  14. data/lib/rails-openapi-gen/debug_helpers.rb +185 -0
  15. data/lib/rails-openapi-gen/engine.rb +1 -1
  16. data/lib/rails-openapi-gen/generators/yaml_generator.rb +242 -27
  17. data/lib/rails-openapi-gen/generators.rb +5 -0
  18. data/lib/rails-openapi-gen/importer.rb +164 -145
  19. data/lib/rails-openapi-gen/parsers/comment_parser.rb +1 -1
  20. data/lib/rails-openapi-gen/parsers/comment_parsers/attribute_parser.rb +7 -7
  21. data/lib/rails-openapi-gen/parsers/comment_parsers/base_attribute_parser.rb +5 -9
  22. data/lib/rails-openapi-gen/parsers/comment_parsers/body_parser.rb +6 -6
  23. data/lib/rails-openapi-gen/parsers/comment_parsers/conditional_parser.rb +1 -1
  24. data/lib/rails-openapi-gen/parsers/comment_parsers/operation_parser.rb +5 -5
  25. data/lib/rails-openapi-gen/parsers/comment_parsers/param_parser.rb +6 -6
  26. data/lib/rails-openapi-gen/parsers/comment_parsers/query_parser.rb +6 -6
  27. data/lib/rails-openapi-gen/parsers/controller_parser.rb +64 -20
  28. data/lib/rails-openapi-gen/parsers/jbuilder/ast_parser.rb +914 -0
  29. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/array_call_detector.rb +103 -0
  30. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/base_detector.rb +107 -0
  31. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/cache_call_detector.rb +112 -0
  32. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/json_call_detector.rb +91 -0
  33. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/key_format_detector.rb +27 -0
  34. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/null_handling_detector.rb +27 -0
  35. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/object_manipulation_detector.rb +27 -0
  36. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/partial_call_detector.rb +125 -0
  37. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors.rb +95 -0
  38. data/lib/rails-openapi-gen/parsers/jbuilder/jbuilder_parser.rb +39 -0
  39. data/lib/rails-openapi-gen/parsers/jbuilder/operation_comment_parser.rb +26 -0
  40. data/lib/rails-openapi-gen/parsers/jbuilder/processors/array_processor.rb +266 -0
  41. data/lib/rails-openapi-gen/parsers/jbuilder/processors/base_processor.rb +235 -0
  42. data/lib/rails-openapi-gen/parsers/jbuilder/processors/composite_processor.rb +97 -0
  43. data/lib/rails-openapi-gen/parsers/jbuilder/processors/object_processor.rb +176 -0
  44. data/lib/rails-openapi-gen/parsers/jbuilder/processors/partial_processor.rb +69 -0
  45. data/lib/rails-openapi-gen/parsers/jbuilder/processors/property_processor.rb +68 -0
  46. data/lib/rails-openapi-gen/parsers/jbuilder/processors.rb +10 -0
  47. data/lib/rails-openapi-gen/parsers/jbuilder/property_comment_parser.rb +26 -0
  48. data/lib/rails-openapi-gen/parsers/jbuilder.rb +10 -0
  49. data/lib/rails-openapi-gen/parsers/routes_parser.rb +83 -9
  50. data/lib/rails-openapi-gen/parsers/template_processors/jbuilder_template_processor.rb +125 -131
  51. data/lib/rails-openapi-gen/parsers/template_processors/response_template_processor.rb +8 -12
  52. data/lib/rails-openapi-gen/parsers/template_processors.rb +6 -0
  53. data/lib/rails-openapi-gen/parsers.rb +9 -0
  54. data/lib/rails-openapi-gen/processors/ast_to_schema_processor.rb +226 -0
  55. data/lib/rails-openapi-gen/processors/base_processor.rb +124 -0
  56. data/lib/rails-openapi-gen/processors/component_schema_processor.rb +35 -0
  57. data/lib/rails-openapi-gen/processors/openapi_schema_processor.rb +218 -0
  58. data/lib/rails-openapi-gen/processors.rb +7 -0
  59. data/lib/rails-openapi-gen/railtie.rb +1 -1
  60. data/lib/rails-openapi-gen/tasks/openapi.rake +4 -4
  61. data/lib/rails-openapi-gen/version.rb +1 -1
  62. data/lib/rails-openapi-gen.rb +169 -196
  63. data/lib/tasks/openapi_import.rake +35 -36
  64. data/rails-openapi-gen.gemspec +6 -5
  65. metadata +62 -23
  66. data/lib/rails-openapi-gen/parsers/jbuilder_parser.rb +0 -529
@@ -1,147 +1,141 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "parser/current"
4
- require_relative 'response_template_processor'
4
+ require_relative '../template_processors'
5
5
 
6
- module RailsOpenapiGen
7
- module Parsers
8
- module TemplateProcessors
9
- class JbuilderTemplateProcessor
10
- include ResponseTemplateProcessor
6
+ module RailsOpenapiGen::Parsers::TemplateProcessors
7
+ class JbuilderTemplateProcessor
8
+ include ResponseTemplateProcessor
11
9
 
12
- def initialize(controller, action)
13
- @controller = controller
14
- @action = action
10
+ def initialize(controller, action)
11
+ @controller = controller
12
+ @action = action
13
+ end
14
+
15
+ def extract_template_path(action_node, route)
16
+ return nil unless action_node
17
+
18
+ processor = JbuilderPathProcessor.new(route[:controller], route[:action])
19
+ processor.process(action_node)
20
+ processor.jbuilder_path
21
+ end
22
+
23
+ def find_default_template(route)
24
+ template_path = Rails.root.join("app", "views", route[:controller], "#{route[:action]}.json.jbuilder")
25
+ File.exist?(template_path) ? template_path.to_s : nil
26
+ end
27
+
28
+ class JbuilderPathProcessor < Parser::AST::Processor
29
+ attr_reader :jbuilder_path
30
+
31
+ def initialize(controller, action)
32
+ super()
33
+ @controller = controller
34
+ @action = action
35
+ @jbuilder_path = nil
36
+ end
37
+
38
+ def on_send(node)
39
+ if render_call?(node) && @jbuilder_path.nil?
40
+ extract_render_target(node)
15
41
  end
42
+ super
43
+ end
16
44
 
17
- def extract_template_path(action_node, route)
18
- return nil unless action_node
45
+ private
19
46
 
20
- processor = JbuilderPathProcessor.new(route[:controller], route[:action])
21
- processor.process(action_node)
22
- processor.jbuilder_path
47
+ def render_call?(node)
48
+ receiver, method_name = node.children[0..1]
49
+ receiver.nil? && method_name == :render
50
+ end
51
+
52
+ def extract_render_target(node)
53
+ args = node.children[2..-1]
54
+
55
+ if args.empty?
56
+ @jbuilder_path = default_jbuilder_path
57
+ elsif args.first.type == :hash
58
+ parse_render_options(args.first)
59
+ elsif args.first.type == :str || args.first.type == :sym
60
+ template = args.first.children.first.to_s
61
+ @jbuilder_path = Rails.root.join("app", "views", @controller, "#{template}.json.jbuilder")
23
62
  end
63
+ end
24
64
 
25
- def find_default_template(route)
26
- template_path = Rails.root.join("app", "views", route[:controller], "#{route[:action]}.json.jbuilder")
27
- File.exist?(template_path) ? template_path.to_s : nil
65
+ def parse_render_options(hash_node)
66
+ render_options = extract_render_hash_options(hash_node)
67
+
68
+ if render_options[:json]
69
+ @jbuilder_path = default_jbuilder_path
70
+ elsif render_options[:template]
71
+ template_path = render_options[:template]
72
+ formats = render_options[:formats] || :json
73
+ handlers = render_options[:handlers] || :jbuilder
74
+
75
+ # Build full template path with format and handler
76
+ full_template_path = build_template_path(template_path, formats, handlers)
77
+ @jbuilder_path = Rails.root.join("app", "views", full_template_path.to_s)
78
+ end
79
+ end
80
+
81
+ def extract_render_hash_options(hash_node)
82
+ options = {}
83
+
84
+ hash_node.children.each do |pair|
85
+ key_node, value_node = pair.children
86
+ next unless key_node.type == :sym
87
+
88
+ key = key_node.children.first
89
+ value = extract_node_value(value_node)
90
+
91
+ options[key] = value
28
92
  end
29
93
 
30
- private
31
-
32
- class JbuilderPathProcessor < Parser::AST::Processor
33
- attr_reader :jbuilder_path
34
-
35
- def initialize(controller, action)
36
- @controller = controller
37
- @action = action
38
- @jbuilder_path = nil
39
- end
40
-
41
- def on_send(node)
42
- if render_call?(node)
43
- extract_render_target(node)
44
- end
45
- super
46
- end
47
-
48
- private
49
-
50
- def render_call?(node)
51
- receiver, method_name = node.children[0..1]
52
- receiver.nil? && method_name == :render
53
- end
54
-
55
- def extract_render_target(node)
56
- args = node.children[2..-1]
57
-
58
- if args.empty?
59
- @jbuilder_path = default_jbuilder_path
60
- elsif args.first.type == :hash
61
- parse_render_options(args.first)
62
- elsif args.first.type == :str || args.first.type == :sym
63
- template = args.first.children.first.to_s
64
- @jbuilder_path = Rails.root.join("app", "views", @controller, "#{template}.json.jbuilder")
65
- end
66
- end
67
-
68
- def parse_render_options(hash_node)
69
- render_options = extract_render_hash_options(hash_node)
70
-
71
- if render_options[:json]
72
- @jbuilder_path = default_jbuilder_path
73
- elsif render_options[:template]
74
- template_path = render_options[:template]
75
- formats = render_options[:formats] || :json
76
- handlers = render_options[:handlers] || :jbuilder
77
-
78
- # Build full template path with format and handler
79
- full_template_path = build_template_path(template_path, formats, handlers)
80
- @jbuilder_path = Rails.root.join("app", "views", "#{full_template_path}")
81
- end
82
- end
83
-
84
- private
85
-
86
- def extract_render_hash_options(hash_node)
87
- options = {}
88
-
89
- hash_node.children.each do |pair|
90
- key_node, value_node = pair.children
91
- next unless key_node.type == :sym
92
-
93
- key = key_node.children.first
94
- value = extract_node_value(value_node)
95
-
96
- options[key] = value
97
- end
98
-
99
- options
100
- end
101
-
102
- def extract_node_value(node)
103
- case node.type
104
- when :str, :sym
105
- node.children.first
106
- when :true
107
- true
108
- when :false
109
- false
110
- else
111
- node.children.first
112
- end
113
- end
114
-
115
- def build_template_path(template, formats, handlers)
116
- # Handle different format specifications
117
- format_str = case formats
118
- when Symbol
119
- formats.to_s
120
- when String
121
- formats
122
- else
123
- "json"
124
- end
125
-
126
- # Handle different handler specifications
127
- handler_str = case handlers
128
- when Symbol
129
- handlers.to_s
130
- when String
131
- handlers
132
- else
133
- "jbuilder"
134
- end
135
-
136
- # Build the path: template.format.handler
137
- "#{template.gsub('/', File::SEPARATOR)}.#{format_str}.#{handler_str}"
138
- end
139
-
140
- def default_jbuilder_path
141
- Rails.root.join("app", "views", @controller, "#{@action}.json.jbuilder")
142
- end
94
+ options
95
+ end
96
+
97
+ def extract_node_value(node)
98
+ case node.type
99
+ when :str, :sym
100
+ node.children.first
101
+ when true
102
+ true
103
+ when false
104
+ false
105
+ else
106
+ # For other node types, try to extract the first child or return nil
107
+ node.children&.first
143
108
  end
144
109
  end
110
+
111
+ def build_template_path(template, formats, handlers)
112
+ # Handle different format specifications
113
+ format_str = case formats
114
+ when Symbol
115
+ formats.to_s
116
+ when String
117
+ formats
118
+ else
119
+ "json"
120
+ end
121
+
122
+ # Handle different handler specifications
123
+ handler_str = case handlers
124
+ when Symbol
125
+ handlers.to_s
126
+ when String
127
+ handlers
128
+ else
129
+ "jbuilder"
130
+ end
131
+
132
+ # Build the path: template.format.handler
133
+ "#{template.gsub('/', File::SEPARATOR)}.#{format_str}.#{handler_str}"
134
+ end
135
+
136
+ def default_jbuilder_path
137
+ Rails.root.join("app", "views", @controller, "#{@action}.json.jbuilder")
138
+ end
145
139
  end
146
140
  end
147
- end
141
+ end
@@ -1,17 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RailsOpenapiGen
4
- module Parsers
5
- module TemplateProcessors
6
- module ResponseTemplateProcessor
7
- def extract_template_path(action_node, route)
8
- raise NotImplementedError, "#{self.class} must implement #extract_template_path"
9
- end
3
+ module RailsOpenapiGen::Parsers::TemplateProcessors
4
+ module ResponseTemplateProcessor
5
+ def extract_template_path(_action_node, _route)
6
+ raise NotImplementedError, "#{self.class} must implement #extract_template_path"
7
+ end
10
8
 
11
- def find_default_template(route)
12
- raise NotImplementedError, "#{self.class} must implement #find_default_template"
13
- end
14
- end
9
+ def find_default_template(_route)
10
+ raise NotImplementedError, "#{self.class} must implement #find_default_template"
15
11
  end
16
12
  end
17
- end
13
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen::Parsers::TemplateProcessors
4
+ autoload :ResponseTemplateProcessor, "rails-openapi-gen/parsers/template_processors/response_template_processor"
5
+ autoload :JbuilderTemplateProcessor, "rails-openapi-gen/parsers/template_processors/jbuilder_template_processor"
6
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen::Parsers
4
+ autoload :CommentParser, "rails-openapi-gen/parsers/comment_parser"
5
+ autoload :ControllerParser, "rails-openapi-gen/parsers/controller_parser"
6
+ autoload :RoutesParser, "rails-openapi-gen/parsers/routes_parser"
7
+ autoload :Jbuilder, "rails-openapi-gen/parsers/jbuilder"
8
+ autoload :TemplateProcessors, "rails-openapi-gen/parsers/template_processors"
9
+ end
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_processor"
4
+
5
+ module RailsOpenapiGen::Processors
6
+ # Processor that converts AST nodes to OpenAPI schema format
7
+ # Implements the visitor pattern to handle different node types
8
+ class AstToSchemaProcessor < BaseProcessor
9
+ def initialize
10
+ super()
11
+ @schema = {}
12
+ @required_properties = []
13
+ end
14
+
15
+ # Process root node and return OpenAPI schema
16
+ # @param root_node [RailsOpenapiGen::AstNodes::BaseNode] Root AST node
17
+ # @return [Hash] OpenAPI schema
18
+ def process_to_schema(root_node)
19
+ @schema = {}
20
+ @required_properties = []
21
+
22
+ puts "🔍 DEBUG: Using NEW AstToSchemaProcessor with node: #{root_node.class.name}" if ENV['RAILS_OPENAPI_DEBUG']
23
+ if root_node.respond_to?(:properties) && ENV['RAILS_OPENAPI_DEBUG']
24
+ puts "🔍 DEBUG: Root node has #{root_node.properties.size} properties"
25
+ root_node.properties.each_with_index do |prop, i|
26
+ puts "🔍 DEBUG: Property #{i}: #{prop.property_name} (#{prop.class.name})"
27
+ end
28
+ end
29
+
30
+ result = process(root_node)
31
+
32
+ # Handle root array case
33
+ if root_node.is_a?(RailsOpenapiGen::AstNodes::ArrayNode) && root_node.root_array?
34
+ # For root arrays, return the result directly since visit_array already creates the array schema
35
+ result || { 'type' => 'array', 'items' => { 'type' => 'object' } }
36
+ else
37
+ result || { 'type' => 'object' }
38
+ end
39
+ end
40
+
41
+ # Visit a property node
42
+ # @param node [RailsOpenapiGen::AstNodes::PropertyNode] Property node
43
+ # @return [Hash] Property schema
44
+ def visit_property(node)
45
+ # If this is a component reference, return a $ref with prefix removed
46
+ if node.is_component_ref && node.component_name
47
+ # Remove component prefix from schema name in $ref
48
+ config = RailsOpenapiGen.configuration
49
+ schema_name_without_prefix = config.remove_component_prefix(node.component_name)
50
+ return { '$ref' => "#/components/schemas/#{schema_name_without_prefix}" }
51
+ end
52
+
53
+ schema = build_basic_schema(node)
54
+
55
+ # Add enum if specified
56
+ if node.comment_data&.has_enum?
57
+ schema['enum'] = node.comment_data.enum
58
+ end
59
+
60
+ # Add format if specified
61
+ if node.comment_data&.has_format?
62
+ schema['format'] = node.comment_data.format
63
+ end
64
+
65
+ # Add example if specified
66
+ if node.comment_data&.has_example?
67
+ schema['example'] = node.comment_data.example
68
+ end
69
+
70
+ schema
71
+ end
72
+
73
+ # Visit an array node
74
+ # @param node [RailsOpenapiGen::AstNodes::ArrayNode] Array node
75
+ # @return [Hash] Array schema
76
+ def visit_array(node)
77
+ schema = {
78
+ 'type' => 'array',
79
+ 'description' => extract_description(node.comment_data)
80
+ }.compact
81
+
82
+ # Process array items
83
+ if node.items.any?
84
+ # Check if all items are properties (from partial processing)
85
+ # If so, combine them into a single object schema
86
+ if node.items.all? { |item| item.is_a?(RailsOpenapiGen::AstNodes::PropertyNode) }
87
+ # Combine all property nodes into a single object schema
88
+ properties = {}
89
+ required = []
90
+
91
+ node.items.each do |property_node|
92
+ prop_schema = process(property_node)
93
+ next unless prop_schema
94
+
95
+ properties[property_node.property_name] = prop_schema
96
+
97
+ # Add to required if property is required
98
+ if required?(property_node)
99
+ required << property_node.property_name
100
+ end
101
+ end
102
+
103
+ object_schema = { 'type' => 'object' }
104
+ object_schema['properties'] = properties if properties.any?
105
+ object_schema['required'] = required if required.any?
106
+
107
+ schema['items'] = object_schema
108
+ else
109
+ # If we have actual item nodes, process them
110
+ item_schemas = process_nodes(node.items)
111
+
112
+ schema['items'] = if item_schemas.length == 1
113
+ item_schemas.first
114
+ elsif item_schemas.length > 1
115
+ # Multiple item types - use oneOf
116
+ { 'oneOf' => item_schemas }
117
+ else
118
+ { 'type' => 'object' }
119
+ end
120
+ end
121
+ else
122
+ # Use comment data for item type
123
+ items_spec = node.comment_data&.array_items
124
+ schema['items'] = items_spec || { 'type' => 'object' }
125
+ end
126
+
127
+ schema
128
+ end
129
+
130
+ # Visit an object node
131
+ # @param node [RailsOpenapiGen::AstNodes::ObjectNode] Object node
132
+ # @return [Hash] Object schema
133
+ def visit_object(node)
134
+ schema = {
135
+ 'type' => 'object',
136
+ 'description' => extract_description(node.comment_data)
137
+ }.compact
138
+
139
+ # Process properties
140
+ if node.properties.any?
141
+ properties = {}
142
+ required = []
143
+
144
+ node.properties.each do |property|
145
+ prop_schema = process(property)
146
+ next unless prop_schema
147
+
148
+ properties[property.property_name] = prop_schema
149
+
150
+ # Add to required if property is required
151
+ if required?(property)
152
+ required << property.property_name
153
+ end
154
+ end
155
+
156
+ schema['properties'] = properties if properties.any?
157
+ schema['required'] = required if required.any?
158
+ end
159
+
160
+ schema
161
+ end
162
+
163
+ # Visit a partial node
164
+ # @param node [RailsOpenapiGen::AstNodes::PartialNode] Partial node
165
+ # @return [Hash] Schema from partial
166
+ def visit_partial(node)
167
+ puts "🔍 DEBUG: Processing partial node: #{node.partial_path}, properties: #{node.properties.size}" if ENV['RAILS_OPENAPI_DEBUG']
168
+
169
+ # Process the properties from the parsed partial
170
+ if node.properties.any?
171
+ # Create an object schema with the partial's properties
172
+ properties = {}
173
+ required = []
174
+
175
+ node.properties.each do |property|
176
+ puts "🔍 DEBUG: Processing partial property: #{property.property_name} (#{property.class.name})" if ENV['RAILS_OPENAPI_DEBUG']
177
+ prop_schema = process(property)
178
+ next unless prop_schema
179
+
180
+ properties[property.property_name] = prop_schema
181
+
182
+ if required?(property)
183
+ required << property.property_name
184
+ end
185
+ end
186
+
187
+ schema = { 'type' => 'object' }
188
+ schema['properties'] = properties if properties.any?
189
+ schema['required'] = required if required.any?
190
+ schema['description'] = extract_description(node.comment_data) if node.comment_data&.description
191
+
192
+ puts "🔍 DEBUG: Partial schema generated: #{schema.keys}" if ENV['RAILS_OPENAPI_DEBUG']
193
+ schema
194
+ else
195
+ puts "🔍 DEBUG: Partial has no properties, using fallback" if ENV['RAILS_OPENAPI_DEBUG']
196
+ # Fallback to basic object schema
197
+ {
198
+ 'type' => 'object',
199
+ 'description' => extract_description(node.comment_data) || "Partial: #{node.partial_path}"
200
+ }.compact
201
+ end
202
+ end
203
+
204
+ # Visit an unknown node type
205
+ # @param node [RailsOpenapiGen::AstNodes::BaseNode] Unknown node
206
+ # @return [Hash] Basic object schema
207
+ def visit_unknown(_node)
208
+ {
209
+ 'type' => 'object',
210
+ 'description' => 'Unknown node type'
211
+ }
212
+ end
213
+
214
+ private
215
+
216
+ # Build basic schema from node
217
+ # @param node [RailsOpenapiGen::AstNodes::BaseNode] Node
218
+ # @return [Hash] Basic schema
219
+ def build_basic_schema(node)
220
+ {
221
+ 'type' => extract_type(node.comment_data),
222
+ 'description' => extract_description(node.comment_data)
223
+ }.compact
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen::Processors
4
+ # Base class for all processors that convert AST nodes to output formats
5
+ # Implements visitor pattern for processing different node types
6
+ class BaseProcessor
7
+ # Process an AST node and return converted output
8
+ # @param node [RailsOpenapiGen::AstNodes::BaseNode] AST node to process
9
+ # @return [Object] Processed output
10
+ def process(node)
11
+ return nil unless node
12
+
13
+ if node.respond_to?(:accept)
14
+ node.accept(self)
15
+ else
16
+ visit(node)
17
+ end
18
+ end
19
+
20
+ # Generic visit method for nodes that don't implement accept
21
+ # @param node [RailsOpenapiGen::AstNodes::BaseNode] Node to visit
22
+ # @return [Object] Processed output
23
+ def visit(node)
24
+ case node
25
+ when RailsOpenapiGen::AstNodes::PropertyNode
26
+ visit_property(node)
27
+ when RailsOpenapiGen::AstNodes::ArrayNode
28
+ visit_array(node)
29
+ when RailsOpenapiGen::AstNodes::ObjectNode
30
+ visit_object(node)
31
+ when RailsOpenapiGen::AstNodes::PartialNode
32
+ visit_partial(node)
33
+ else
34
+ visit_unknown(node)
35
+ end
36
+ end
37
+
38
+ # Visit a property node
39
+ # @param node [RailsOpenapiGen::AstNodes::PropertyNode] Property node
40
+ # @return [Object] Processed output
41
+ def visit_property(_node)
42
+ raise NotImplementedError, "#{self.class} must implement #visit_property"
43
+ end
44
+
45
+ # Visit an array node
46
+ # @param node [RailsOpenapiGen::AstNodes::ArrayNode] Array node
47
+ # @return [Object] Processed output
48
+ def visit_array(_node)
49
+ raise NotImplementedError, "#{self.class} must implement #visit_array"
50
+ end
51
+
52
+ # Visit an object node
53
+ # @param node [RailsOpenapiGen::AstNodes::ObjectNode] Object node
54
+ # @return [Object] Processed output
55
+ def visit_object(_node)
56
+ raise NotImplementedError, "#{self.class} must implement #visit_object"
57
+ end
58
+
59
+ # Visit a partial node
60
+ # @param node [RailsOpenapiGen::AstNodes::PartialNode] Partial node
61
+ # @return [Object] Processed output
62
+ def visit_partial(_node)
63
+ raise NotImplementedError, "#{self.class} must implement #visit_partial"
64
+ end
65
+
66
+ # Visit an unknown node type
67
+ # @param node [RailsOpenapiGen::AstNodes::BaseNode] Unknown node
68
+ # @return [Object] Processed output
69
+ def visit_unknown(_node)
70
+ # Default implementation returns nil for unknown nodes
71
+ nil
72
+ end
73
+
74
+ # Process multiple nodes
75
+ # @param nodes [Array<RailsOpenapiGen::AstNodes::BaseNode>] Nodes to process
76
+ # @return [Array<Object>] Array of processed outputs
77
+ def process_nodes(nodes)
78
+ return [] unless nodes.respond_to?(:map)
79
+
80
+ nodes.map { |node| process(node) }.compact
81
+ end
82
+
83
+ # Check if node should be processed based on conditions
84
+ # @param node [RailsOpenapiGen::AstNodes::BaseNode] Node to check
85
+ # @return [Boolean] True if node should be processed
86
+ def should_process?(node)
87
+ return false unless node
88
+
89
+ # Can be overridden in subclasses for custom filtering
90
+ true
91
+ end
92
+
93
+ protected
94
+
95
+ # Extract description from comment data
96
+ # @param comment_data [RailsOpenapiGen::AstNodes::CommentData, nil] Comment data
97
+ # @return [String, nil] Description
98
+ def extract_description(comment_data)
99
+ comment_data&.description
100
+ end
101
+
102
+ # Extract OpenAPI type from comment data
103
+ # @param comment_data [RailsOpenapiGen::AstNodes::CommentData, nil] Comment data
104
+ # @param default_type [String] Default type if not specified
105
+ # @return [String] OpenAPI type
106
+ def extract_type(comment_data, default_type = 'string')
107
+ comment_data&.openapi_type || default_type
108
+ end
109
+
110
+ # Check if property is required
111
+ # @param node [RailsOpenapiGen::AstNodes::BaseNode] Node to check
112
+ # @return [Boolean] True if property is required
113
+ def required?(node)
114
+ node.respond_to?(:required?) ? node.required? : false
115
+ end
116
+
117
+ # Check if property is conditional
118
+ # @param node [RailsOpenapiGen::AstNodes::BaseNode] Node to check
119
+ # @return [Boolean] True if property is conditional
120
+ def conditional?(node)
121
+ node.respond_to?(:is_conditional) ? node.is_conditional : false
122
+ end
123
+ end
124
+ end