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,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
|
@@ -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
|