rails-openapi-gen 0.0.1

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 (27) hide show
  1. checksums.yaml +7 -0
  2. data/CLAUDE.md +160 -0
  3. data/README.md +164 -0
  4. data/lib/rails_openapi_gen/configuration.rb +157 -0
  5. data/lib/rails_openapi_gen/engine.rb +11 -0
  6. data/lib/rails_openapi_gen/generators/yaml_generator.rb +302 -0
  7. data/lib/rails_openapi_gen/importer.rb +647 -0
  8. data/lib/rails_openapi_gen/parsers/comment_parser.rb +40 -0
  9. data/lib/rails_openapi_gen/parsers/comment_parsers/attribute_parser.rb +57 -0
  10. data/lib/rails_openapi_gen/parsers/comment_parsers/base_attribute_parser.rb +42 -0
  11. data/lib/rails_openapi_gen/parsers/comment_parsers/body_parser.rb +62 -0
  12. data/lib/rails_openapi_gen/parsers/comment_parsers/conditional_parser.rb +13 -0
  13. data/lib/rails_openapi_gen/parsers/comment_parsers/operation_parser.rb +50 -0
  14. data/lib/rails_openapi_gen/parsers/comment_parsers/param_parser.rb +62 -0
  15. data/lib/rails_openapi_gen/parsers/comment_parsers/query_parser.rb +62 -0
  16. data/lib/rails_openapi_gen/parsers/controller_parser.rb +153 -0
  17. data/lib/rails_openapi_gen/parsers/jbuilder_parser.rb +529 -0
  18. data/lib/rails_openapi_gen/parsers/routes_parser.rb +33 -0
  19. data/lib/rails_openapi_gen/parsers/template_processors/jbuilder_template_processor.rb +147 -0
  20. data/lib/rails_openapi_gen/parsers/template_processors/response_template_processor.rb +17 -0
  21. data/lib/rails_openapi_gen/railtie.rb +11 -0
  22. data/lib/rails_openapi_gen/tasks/openapi.rake +30 -0
  23. data/lib/rails_openapi_gen/version.rb +5 -0
  24. data/lib/rails_openapi_gen.rb +267 -0
  25. data/lib/tasks/openapi_import.rake +126 -0
  26. data/rails-openapi-gen.gemspec +30 -0
  27. metadata +155 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8628f98cf4d903315e964dbcc45d339778e13ede98bbc0ed87ae26e36ab870be
4
+ data.tar.gz: c90f264b7bf1345670496fbc96a03f4440177830464af007b271d0af87008ac9
5
+ SHA512:
6
+ metadata.gz: b2065cbdb52235914fb1609011b1cc3a434c6ab11d82e74009cf5668ece9c317b55cc5d21a5a368a3587388ce9df0993abfb28941621180292706a67073804b0
7
+ data.tar.gz: 6736fb52b3ca6fd58715ea46fdba2f77ddaab7c40c3182c2a973ae4fd53dc00a145e90d66f91fa98b4f8f3e53d2b34c536d5cfe681af8cebb65f910fe5e4120f
data/CLAUDE.md ADDED
@@ -0,0 +1,160 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ Rails OpenAPI Gen is a Ruby gem that automatically generates OpenAPI 3.0 specifications from Rails applications. It uses AST parsing to analyze routes, controllers, and Jbuilder templates, relying on `# @openapi` comments as the source of truth for type information.
8
+
9
+ ## Development Commands
10
+
11
+ **Testing:**
12
+ ```bash
13
+ rspec # Run all tests
14
+ rspec spec/specific_spec.rb # Run specific test file
15
+ ```
16
+
17
+ **Linting:**
18
+ ```bash
19
+ rubocop # Run linter
20
+ rubocop -a # Auto-fix linter issues
21
+ ```
22
+
23
+ **OpenAPI Generation (for testing):**
24
+ ```bash
25
+ # These commands require a Rails app environment
26
+ bin/rails openapi:generate # Generate OpenAPI spec
27
+ bin/rails openapi:check # Check for missing comments and uncommitted changes
28
+ ```
29
+
30
+ **Gem Development:**
31
+ ```bash
32
+ bundle install # Install dependencies
33
+ rake -T # List available rake tasks
34
+ ```
35
+
36
+ ## Architecture
37
+
38
+ ### Core Components
39
+
40
+ **Main Module** (`lib/rails_openapi_gen.rb`):
41
+ - `RailsOpenapiGen.generate` - Entry point for spec generation
42
+ - `RailsOpenapiGen.check` - Entry point for validation
43
+ - `Generator` class - Orchestrates parsing and generation
44
+ - `Checker` class - Validates missing comments and git status
45
+
46
+ **Parsers** (`lib/rails_openapi_gen/parsers/`):
47
+ - `RoutesParser` - Extracts routes from Rails application
48
+ - `ControllerParser` - Finds controller actions and locates Jbuilder templates
49
+ - `JbuilderParser` - Uses AST to parse Jbuilder templates and extract JSON structure
50
+ - `CommentParser` - Parses `@openapi` comment annotations with regex
51
+
52
+ **Generators** (`lib/rails_openapi_gen/generators/`):
53
+ - `YamlGenerator` - Creates split YAML files with $ref support
54
+
55
+ ### Data Flow
56
+
57
+ 1. Parse Rails routes to discover endpoints
58
+ 2. For each route, find corresponding controller action
59
+ 3. Locate associated Jbuilder template
60
+ 4. Parse Jbuilder template using AST to extract JSON structure
61
+ 5. Extract `@openapi` comments for type information
62
+ 6. Generate split YAML files (main spec + separate paths/schemas)
63
+
64
+ ### Comment Format
65
+
66
+ The gem uses `# @openapi` comments in Jbuilder templates:
67
+
68
+ ```ruby
69
+ # @openapi id:integer required:true description:"User ID"
70
+ json.id @user.id
71
+
72
+ # @openapi status:string enum:[active,inactive] description:"User status"
73
+ json.status @user.status
74
+ ```
75
+
76
+ **Supported attributes:**
77
+ - `type`: Data type (string, integer, boolean, number, array, object)
78
+ - `required`: Whether field is required (true/false)
79
+ - `enum`: Allowed values for the field
80
+ - `description`: Human-readable description
81
+
82
+ ### Conditional Rendering Support
83
+
84
+ For properties that are conditionally rendered (e.g., inside `if` statements), use the `# @openapi conditional:true` comment:
85
+
86
+ ```ruby
87
+ # @openapi conditional:true
88
+ if @user.profile.present?
89
+ # @openapi type:object description:"User profile information"
90
+ json.profile do
91
+ # @openapi bio:string description:"User biography"
92
+ json.bio @user.profile.bio
93
+
94
+ # @openapi verified:boolean required:true description:"Whether verified"
95
+ json.verified @user.profile.verified
96
+ end
97
+ end
98
+ ```
99
+
100
+ **Conditional behavior:**
101
+ - Properties inside conditional blocks are marked as optional in the OpenAPI schema
102
+ - Even if a conditional property has `required:true`, it will not be included in the schema's `required` array
103
+ - Both the parent property (`profile`) and nested properties (`bio`, `verified`) are marked as conditional
104
+ - This ensures the generated OpenAPI spec accurately reflects that these fields may not always be present
105
+
106
+ ### Missing Comment Handling
107
+
108
+ Fields without `@openapi` comments are marked as `"TODO: MISSING COMMENT"` in the generated spec. The `openapi:check` task validates that no missing comments exist and that the OpenAPI directory has no uncommitted changes.
109
+
110
+ ### Output Structure
111
+
112
+ Generated files are organized as:
113
+ ```
114
+ openapi/
115
+ openapi.yaml # Main OpenAPI file with $ref to other files
116
+ paths/
117
+ users.yaml # Path definitions per controller
118
+ components/
119
+ schemas/
120
+ user.yaml # Schema definitions per model
121
+ ```
122
+
123
+ ## Key Files to Understand
124
+
125
+ - `lib/rails_openapi_gen.rb` - Main entry points and orchestration logic
126
+ - `lib/rails_openapi_gen/parsers/comment_parser.rb` - Comment parsing logic with regex patterns
127
+ - `lib/rails_openapi_gen/parsers/jbuilder_parser.rb` - AST parsing for JSON structure extraction
128
+ - `examples/user.json.jbuilder` - Example of properly commented Jbuilder template
129
+
130
+ ## Examples
131
+
132
+ ### rails_gen_comment Example
133
+
134
+ The `examples/rails_gen_comment/` directory contains a complete Rails application demonstrating the **reverse workflow** - importing from an existing OpenAPI specification to generate `@openapi` comments in Jbuilder templates.
135
+
136
+ **Use case:** You have an existing OpenAPI spec and want to automatically annotate your Jbuilder templates with the appropriate `@openapi` comments.
137
+
138
+ **Key features demonstrated:**
139
+ - Import existing OpenAPI spec: `RailsOpenapiGen.import('docs/api/openapi.yaml')`
140
+ - Automatic comment generation in Jbuilder files
141
+ - Support for complex nested objects and arrays
142
+ - Conditional rendering patterns
143
+ - Operation-level documentation
144
+
145
+ **Sample files:**
146
+ - `docs/api/openapi.yaml` - Complete OpenAPI 3.0 specification
147
+ - `app/views/api/users/show.json.jbuilder` - Jbuilder with auto-generated comments
148
+ - `app/views/api/users/_user.json.jbuilder` - Partial template with comments
149
+
150
+ **Running the import:**
151
+ ```bash
152
+ cd examples/rails_gen_comment
153
+ bin/rails runner "RailsOpenapiGen.import('docs/api/openapi.yaml')"
154
+ ```
155
+
156
+ This will analyze the OpenAPI spec and add appropriate `@openapi` comments to your Jbuilder templates, making it easy to maintain consistency between your API documentation and implementation.
157
+
158
+ ## Testing Strategy
159
+
160
+ The gem is designed to work with existing Rails applications. When testing locally, ensure you have a Rails application environment with routes, controllers, and Jbuilder templates that include `@openapi` comments.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # Rails OpenAPI Gen
2
+
3
+ Rails comment-driven OpenAPI specification generator.
4
+
5
+ ## Overview
6
+
7
+ rails-openapi-gen analyzes your Rails application's routes.rb, controllers, and jbuilder templates to automatically generate OpenAPI documentation. It uses AST parsing to extract JSON structure and relies on `# @openapi` comments for accurate type information.
8
+
9
+ ## Note
10
+
11
+ AST analysis alone cannot accurately infer all conditional branches and partial patterns. Type, required status, enum values, and descriptions should be explicitly defined using `# @openapi` comments as the source of truth.
12
+
13
+ ### Limitations
14
+
15
+ The following Jbuilder patterns are not currently supported:
16
+
17
+ - **Shorthand array syntax**: `json.array! @items, :id, :name` - This shorthand notation cannot be annotated with `@openapi` comments. Use the block form instead:
18
+ ```ruby
19
+ json.array! @items do |item|
20
+ # @openapi id:integer required:true description:"Item ID"
21
+ json.id item.id
22
+
23
+ # @openapi name:string required:true description:"Item name"
24
+ json.name item.name
25
+ end
26
+ ```
27
+
28
+ - **Extract shorthand**: `json.extract! @item, :id, :name` - This shorthand notation cannot be annotated with `@openapi` comments. Use explicit property assignments instead:
29
+ ```ruby
30
+ # @openapi id:integer required:true description:"Item ID"
31
+ json.id @item.id
32
+
33
+ # @openapi name:string required:true description:"Item name"
34
+ json.name @item.name
35
+ ```
36
+
37
+ ## Installation
38
+
39
+ Add this line to your application's Gemfile:
40
+
41
+ ```ruby
42
+ gem 'rails-openapi-gen'
43
+ ```
44
+
45
+ And then execute:
46
+
47
+ ```bash
48
+ $ bundle install
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ### Comment Format
54
+
55
+ Add `# @openapi` comments to your jbuilder templates:
56
+
57
+ ```ruby
58
+ # @openapi id:integer required:true description:"User ID"
59
+ json.id @user.id
60
+
61
+ # @openapi status:string enum:[active,inactive] description:"User status"
62
+ json.status @user.status
63
+
64
+ # @openapi email:string required:true description:"User email address"
65
+ json.email @user.email
66
+
67
+ # @openapi created_at:string description:"ISO 8601 timestamp"
68
+ json.created_at @user.created_at.iso8601
69
+ ```
70
+
71
+ ### Generate OpenAPI Specification
72
+
73
+ ```bash
74
+ bin/rails openapi:generate
75
+ ```
76
+
77
+ This creates the following structure:
78
+
79
+ ```
80
+ openapi/
81
+ openapi.yaml # Main OpenAPI file
82
+ paths/
83
+ users.yaml # Path definitions
84
+ posts.yaml
85
+ components/
86
+ schemas/
87
+ user.yaml # Schema definitions
88
+ post.yaml
89
+ ```
90
+
91
+ ### Check for Missing Comments
92
+
93
+ ```bash
94
+ bin/rails openapi:check
95
+ ```
96
+
97
+ This command will:
98
+ - Generate the OpenAPI specification
99
+ - Check for missing `@openapi` comments (marked as "TODO: MISSING COMMENT")
100
+ - Verify no uncommitted changes in the openapi/ directory
101
+ - Exit with code 1 if issues are found
102
+
103
+ ## CI Integration
104
+
105
+ Add to your CI pipeline:
106
+
107
+ ```yaml
108
+ name: OpenAPI Spec Check
109
+ on: [push, pull_request]
110
+
111
+ jobs:
112
+ openapi-check:
113
+ runs-on: ubuntu-latest
114
+ steps:
115
+ - uses: actions/checkout@v3
116
+
117
+ - name: Setup Ruby
118
+ uses: ruby/setup-ruby@v1
119
+ with:
120
+ bundler-cache: true
121
+
122
+ - name: Generate OpenAPI Spec
123
+ run: bin/rails openapi:generate
124
+
125
+ - name: Check for missing comments
126
+ run: |
127
+ if grep -r "TODO: MISSING COMMENT" openapi/; then
128
+ echo "❌ Missing @openapi comments!"
129
+ exit 1
130
+ fi
131
+
132
+ - name: Check for unexpected diffs
133
+ run: git diff --exit-code openapi/
134
+ ```
135
+
136
+ ## Comment Attributes
137
+
138
+ - `type`: Data type (string, integer, boolean, number, array, object)
139
+ - `required`: Whether the field is required (true/false)
140
+ - `enum`: Allowed values for the field
141
+ - `description`: Human-readable description
142
+
143
+ ## Features
144
+
145
+ - ✅ Routes.rb parsing for endpoint discovery
146
+ - ✅ Controller analysis to locate jbuilder templates
147
+ - ✅ AST-based jbuilder parsing with partial support
148
+ - ✅ Comment-driven type annotations
149
+ - ✅ Split YAML generation with $ref support
150
+ - ✅ CI-friendly validation commands
151
+ - 🚧 ActiveRecord model inference (optional, future)
152
+ - 🚧 Serializer support (future)
153
+
154
+ ## Development
155
+
156
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
157
+
158
+ ## Contributing
159
+
160
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rails-openapi-gen/rails-openapi-gen.
161
+
162
+ ## License
163
+
164
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen
4
+ class Configuration
5
+ attr_accessor :openapi_version, :info, :servers, :route_patterns, :output
6
+
7
+ # Initializes configuration with default values
8
+ def initialize
9
+ @openapi_version = "3.0.0"
10
+ @info = {
11
+ title: default_app_name,
12
+ version: "1.0.0",
13
+ description: "API documentation generated by rails-openapi-gen"
14
+ }
15
+ @servers = [
16
+ {
17
+ url: "http://localhost:3000",
18
+ description: "Development server"
19
+ }
20
+ ]
21
+ @route_patterns = {
22
+ include: [/.*/], # Include all routes by default
23
+ exclude: [] # Exclude nothing by default
24
+ }
25
+ @output = {
26
+ directory: "openapi",
27
+ filename: "openapi.yaml",
28
+ split_files: true
29
+ }
30
+ end
31
+
32
+ # TODO: Loads configuration from Ruby file
33
+ # @param file_path [String, nil] Path to configuration file (defaults to config/openapi.rb or config/initializers/openapi.rb)
34
+ # @return [void]
35
+ def load_from_file(file_path = nil)
36
+ # TODO: Implement configuration loading from file
37
+ # This method should load configuration from Ruby files and apply it to the instance
38
+ return
39
+ end
40
+
41
+ # Returns the full path to the output directory
42
+ # @return [String] Full output directory path
43
+ def output_directory
44
+ if @output[:directory].start_with?('/')
45
+ @output[:directory]
46
+ else
47
+ File.join(Rails.root.to_s, @output[:directory])
48
+ end
49
+ end
50
+
51
+ # Returns the output filename
52
+ # @return [String] Output filename
53
+ def output_filename
54
+ @output[:filename]
55
+ end
56
+
57
+ # Checks if output should be split into multiple files
58
+ # @return [Boolean] True if files should be split
59
+ def split_files?
60
+ @output[:split_files]
61
+ end
62
+
63
+ # Updates output configuration
64
+ # @param output_config [Hash] Output configuration hash
65
+ # @return [void]
66
+ def update_output_config(output_config)
67
+ load_output_config(output_config)
68
+ end
69
+
70
+ # Checks if a route path should be included in the OpenAPI spec
71
+ # @param path [String] Route path to check
72
+ # @return [Boolean] True if route should be included
73
+ def route_included?(path)
74
+ # Check if path matches any include pattern
75
+ included = @route_patterns[:include].any? { |pattern| path.match?(pattern) }
76
+ return false unless included
77
+
78
+ # Check if path matches any exclude pattern
79
+ excluded = @route_patterns[:exclude].any? { |pattern| path.match?(pattern) }
80
+ !excluded
81
+ end
82
+
83
+ private
84
+
85
+ # Loads Ruby configuration file
86
+ # @param file_path [String] Path to configuration file
87
+ # @return [void]
88
+ def load_ruby_config(file_path)
89
+ # Load Ruby configuration file
90
+ # The file should call RailsOpenapiGen.configure block
91
+ load file_path
92
+
93
+ # Copy configuration from global singleton to this instance
94
+ global_config = RailsOpenapiGen.configuration
95
+ @openapi_version = global_config.openapi_version
96
+ @info = global_config.info.dup
97
+ @servers = global_config.servers.dup
98
+ @route_patterns = global_config.route_patterns.dup
99
+ @output = global_config.output.dup
100
+ end
101
+
102
+ # Returns default application name
103
+ # @return [String] Default app name
104
+ def default_app_name
105
+ if defined?(Rails) && Rails.application
106
+ Rails.application.class.respond_to?(:module_parent_name) ?
107
+ Rails.application.class.module_parent_name :
108
+ "RailsApp"
109
+ else
110
+ "API"
111
+ end
112
+ end
113
+
114
+ # Converts hash keys to symbols
115
+ # @param hash [Hash] Hash to convert
116
+ # @return [Hash] Hash with symbolized keys
117
+ def symbolize_keys(hash)
118
+ return hash unless hash.is_a?(Hash)
119
+ hash.transform_keys(&:to_sym)
120
+ end
121
+
122
+
123
+ # Loads output configuration settings
124
+ # @param output_config [Hash] Output configuration hash
125
+ # @return [void]
126
+ def load_output_config(output_config)
127
+ output_settings = symbolize_keys(output_config)
128
+
129
+ @output[:directory] = output_settings[:directory] if output_settings[:directory]
130
+ @output[:filename] = output_settings[:filename] if output_settings[:filename]
131
+ @output[:split_files] = output_settings[:split_files] if output_settings.key?(:split_files)
132
+ end
133
+ end
134
+
135
+ class << self
136
+ attr_writer :configuration
137
+
138
+ # Returns the configuration instance
139
+ # @return [Configuration] Configuration instance
140
+ def configuration
141
+ @configuration ||= Configuration.new
142
+ end
143
+
144
+ # Configures the gem via block
145
+ # @yield [Configuration] Configuration instance
146
+ # @return [void]
147
+ def configure
148
+ yield(configuration)
149
+ end
150
+
151
+ # Resets configuration to defaults
152
+ # @return [void]
153
+ def reset_configuration!
154
+ @configuration = Configuration.new
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace RailsOpenapiGen
6
+
7
+ rake_tasks do
8
+ load File.expand_path("tasks/openapi.rake", __dir__)
9
+ end
10
+ end
11
+ end