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
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ast_to_schema_processor"
4
+
5
+ module RailsOpenapiGen::Processors
6
+ # Processor that converts AST nodes to OpenAPI schema format for components
7
+ # Differs from AstToSchemaProcessor by inline expanding component references
8
+ class ComponentSchemaProcessor < AstToSchemaProcessor
9
+ # Visit a property node (override to inline expand component references)
10
+ # @param node [RailsOpenapiGen::AstNodes::PropertyNode] Property node
11
+ # @return [Hash] Property schema
12
+ def visit_property(node)
13
+ # For component generation, we inline expand component references instead of using $ref
14
+ if node.is_component_ref && node.component_name
15
+ puts "🔄 Inline expanding component reference: #{node.component_name}" if ENV['RAILS_OPENAPI_DEBUG']
16
+ return inline_expand_component_reference(node)
17
+ end
18
+
19
+ # Use the parent implementation for regular properties
20
+ super
21
+ end
22
+
23
+ private
24
+
25
+ # Inline expand a component reference for component generation
26
+ # @param node [RailsOpenapiGen::AstNodes::PropertyNode] Component reference property
27
+ # @return [Hash] Inline expanded schema
28
+ def inline_expand_component_reference(_node)
29
+ # Create a basic object schema as placeholder without auto-generated description
30
+ {
31
+ 'type' => 'object'
32
+ }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ast_to_schema_processor"
4
+
5
+ module RailsOpenapiGen::Processors
6
+ # High-level processor for generating complete OpenAPI schemas
7
+ # Orchestrates the conversion from AST to full OpenAPI specification
8
+ class OpenApiSchemaProcessor
9
+ def initialize
10
+ @ast_processor = AstToSchemaProcessor.new
11
+ end
12
+
13
+ # Generate complete OpenAPI response schema from AST
14
+ # @param root_node [RailsOpenapiGen::AstNodes::BaseNode] Root AST node
15
+ # @param operation_info [Hash, nil] Operation-level information
16
+ # @return [Hash] Complete OpenAPI response schema
17
+ def generate_response_schema(root_node, operation_info = nil)
18
+ return default_response_schema unless root_node
19
+
20
+ # Convert AST to schema
21
+ schema = @ast_processor.process_to_schema(root_node)
22
+
23
+ # Build response object
24
+ response = {
25
+ description: operation_info&.dig(:response_description) || 'Successful response',
26
+ content: {
27
+ 'application/json' => {
28
+ schema: schema
29
+ }
30
+ }
31
+ }
32
+
33
+ # Add examples if available
34
+ if operation_info&.dig(:examples)
35
+ response[:content]['application/json'][:examples] = operation_info[:examples]
36
+ end
37
+
38
+ {
39
+ '200' => response
40
+ }
41
+ end
42
+
43
+ # Generate complete OpenAPI operation from AST and operation info
44
+ # @param root_node [RailsOpenapiGen::AstNodes::BaseNode] Root AST node
45
+ # @param operation_info [Hash, nil] Operation-level information
46
+ # @return [Hash] Complete OpenAPI operation
47
+ def generate_operation(root_node, operation_info = nil)
48
+ operation = {}
49
+
50
+ # Add operation-level information
51
+ if operation_info
52
+ operation[:operationId] = operation_info[:operation_id] if operation_info[:operation_id]
53
+ operation[:summary] = operation_info[:summary] if operation_info[:summary]
54
+ operation[:description] = operation_info[:description] if operation_info[:description]
55
+ operation[:tags] = operation_info[:tags] if operation_info[:tags]
56
+ end
57
+
58
+ # Add responses
59
+ operation[:responses] = generate_response_schema(root_node, operation_info)
60
+
61
+ # Add parameters if specified
62
+ if operation_info&.dig(:parameters)
63
+ operation[:parameters] = operation_info[:parameters]
64
+ end
65
+
66
+ # Add request body if specified
67
+ if operation_info&.dig(:request_body)
68
+ operation[:requestBody] = operation_info[:request_body]
69
+ end
70
+
71
+ operation
72
+ end
73
+
74
+ # Generate path item from AST and operation info
75
+ # @param method [String] HTTP method (get, post, etc.)
76
+ # @param root_node [RailsOpenapiGen::AstNodes::BaseNode] Root AST node
77
+ # @param operation_info [Hash, nil] Operation-level information
78
+ # @return [Hash] OpenAPI path item
79
+ def generate_path_item(method, root_node, operation_info = nil)
80
+ {
81
+ method.downcase => generate_operation(root_node, operation_info)
82
+ }
83
+ end
84
+
85
+ # Generate component schema from AST
86
+ # @param root_node [RailsOpenapiGen::AstNodes::BaseNode] Root AST node
87
+ # @param schema_name [String] Name for the schema component
88
+ # @return [Hash] Component schema
89
+ def generate_component_schema(root_node, schema_name)
90
+ schema = @ast_processor.process_to_schema(root_node)
91
+
92
+ {
93
+ schema_name => schema
94
+ }
95
+ end
96
+
97
+ # Validate generated schema
98
+ # @param schema [Hash] OpenAPI schema to validate
99
+ # @return [Array<String>] Array of validation errors (empty if valid)
100
+ def validate_schema(schema)
101
+ errors = []
102
+
103
+ # Basic validation
104
+ unless schema.is_a?(Hash)
105
+ errors << "Schema must be a hash"
106
+ return errors
107
+ end
108
+
109
+ # Check for required fields in responses
110
+ if schema.dig('200', 'content', 'application/json', 'schema')
111
+ schema_obj = schema['200']['content']['application/json']['schema']
112
+ errors.concat(validate_schema_object(schema_obj, 'root'))
113
+ end
114
+
115
+ errors
116
+ end
117
+
118
+ # Extract missing comment information
119
+ # @param root_node [RailsOpenapiGen::AstNodes::BaseNode] Root AST node
120
+ # @return [Array<Hash>] Array of missing comment information
121
+ def extract_missing_comments(root_node)
122
+ missing = []
123
+ extract_missing_comments_recursive(root_node, [], missing)
124
+ missing
125
+ end
126
+
127
+ private
128
+
129
+ # Default response schema for error cases
130
+ # @return [Hash] Default response schema
131
+ def default_response_schema
132
+ {
133
+ '200' => {
134
+ description: 'Successful response',
135
+ content: {
136
+ 'application/json' => {
137
+ schema: {
138
+ type: 'object',
139
+ description: 'TODO: MISSING COMMENT'
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ end
146
+
147
+ # Validate a schema object recursively
148
+ # @param schema [Hash] Schema object to validate
149
+ # @param path [String] Current path for error reporting
150
+ # @return [Array<String>] Validation errors
151
+ def validate_schema_object(schema, path)
152
+ errors = []
153
+
154
+ unless schema.is_a?(Hash)
155
+ errors << "Schema at #{path} must be a hash"
156
+ return errors
157
+ end
158
+
159
+ # Check for missing type
160
+ unless schema[:type] || schema['type']
161
+ errors << "Schema at #{path} is missing type"
162
+ end
163
+
164
+ # Validate properties if it's an object
165
+ type = schema[:type] || schema['type']
166
+ if type == 'object'
167
+ properties = schema[:properties] || schema['properties']
168
+ if properties.is_a?(Hash)
169
+ properties.each do |prop_name, prop_schema|
170
+ errors.concat(validate_schema_object(prop_schema, "#{path}.#{prop_name}"))
171
+ end
172
+ end
173
+ end
174
+
175
+ # Validate array items
176
+ if type == 'array'
177
+ items = schema[:items] || schema['items']
178
+ if items.is_a?(Hash)
179
+ errors.concat(validate_schema_object(items, "#{path}[]"))
180
+ end
181
+ end
182
+
183
+ errors
184
+ end
185
+
186
+ # Recursively extract missing comment information
187
+ # @param node [RailsOpenapiGen::AstNodes::BaseNode] Current node
188
+ # @param path [Array<String>] Current path
189
+ # @param missing [Array<Hash>] Array to collect missing comments
190
+ # @return [void]
191
+ def extract_missing_comments_recursive(node, path, missing)
192
+ return unless node
193
+
194
+ # Check if node has missing comment
195
+ if node.respond_to?(:comment_data) &&
196
+ (!node.comment_data || node.comment_data.description.nil?)
197
+ missing << {
198
+ path: path.join('.'),
199
+ property: node.respond_to?(:property_name) ? node.property_name : 'unknown',
200
+ type: node.class.name.split('::').last
201
+ }
202
+ end
203
+
204
+ # Recurse into child nodes
205
+ if node.respond_to?(:properties)
206
+ node.properties.each do |child|
207
+ child_path = path + [child.respond_to?(:property_name) ? child.property_name : 'unknown']
208
+ extract_missing_comments_recursive(child, child_path, missing)
209
+ end
210
+ elsif node.respond_to?(:items)
211
+ node.items.each_with_index do |child, index|
212
+ child_path = path + ["[#{index}]"]
213
+ extract_missing_comments_recursive(child, child_path, missing)
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen::Processors
4
+ autoload :BaseProcessor, "rails-openapi-gen/processors/base_processor"
5
+ autoload :OpenApiSchemaProcessor, "rails-openapi-gen/processors/openapi_schema_processor"
6
+ autoload :AstToSchemaProcessor, "rails-openapi-gen/processors/ast_to_schema_processor"
7
+ end
@@ -8,4 +8,4 @@ module RailsOpenapiGen
8
8
  load File.expand_path("tasks/openapi.rake", __dir__)
9
9
  end
10
10
  end
11
- end
11
+ end
@@ -16,15 +16,15 @@ namespace :openapi do
16
16
  desc "Import OpenAPI specification and add comments to Jbuilder templates"
17
17
  task :import, [:openapi_file] => :environment do |_task, args|
18
18
  require "rails-openapi-gen"
19
-
19
+
20
20
  openapi_file = args[:openapi_file]
21
-
21
+
22
22
  if openapi_file.nil? || openapi_file.empty?
23
23
  puts "Usage: bin/rails openapi:import[PATH_TO_OPENAPI_FILE]"
24
24
  puts "Example: bin/rails openapi:import[docs/api/openapi.yaml]"
25
25
  exit 1
26
26
  end
27
-
27
+
28
28
  RailsOpenapiGen.import(openapi_file)
29
29
  end
30
- end
30
+ end
@@ -2,4 +2,4 @@
2
2
 
3
3
  module RailsOpenapiGen
4
4
  VERSION = "0.0.1"
5
- end
5
+ end