rapitapir 0.1.1 → 2.0.0

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -7
  3. data/.rubocop_todo.yml +83 -0
  4. data/README.md +1319 -235
  5. data/RUBY_WEEKLY_LAUNCH_POST.md +219 -0
  6. data/docs/RAILS_INTEGRATION_IMPLEMENTATION.md +209 -0
  7. data/docs/SINATRA_EXTENSION.md +399 -348
  8. data/docs/STRICT_VALIDATION.md +229 -0
  9. data/docs/VALIDATION_IMPROVEMENTS.md +218 -0
  10. data/docs/ai-integration-plan.md +112 -0
  11. data/docs/auto-derivation.md +505 -92
  12. data/docs/endpoint-definition.md +536 -129
  13. data/docs/n8n-integration.md +212 -0
  14. data/docs/observability.md +810 -500
  15. data/docs/using-mcp.md +93 -0
  16. data/examples/ai/knowledge_base_rag.rb +83 -0
  17. data/examples/ai/user_management_mcp.rb +92 -0
  18. data/examples/ai/user_validation_llm.rb +187 -0
  19. data/examples/rails/RAILS_8_GUIDE.md +165 -0
  20. data/examples/rails/RAILS_LOADING_FIX.rb +35 -0
  21. data/examples/rails/README.md +497 -0
  22. data/examples/rails/comprehensive_test.rb +91 -0
  23. data/examples/rails/config/routes.rb +48 -0
  24. data/examples/rails/debug_controller.rb +63 -0
  25. data/examples/rails/detailed_test.rb +46 -0
  26. data/examples/rails/enhanced_users_controller.rb +278 -0
  27. data/examples/rails/final_server_test.rb +50 -0
  28. data/examples/rails/hello_world_app.rb +116 -0
  29. data/examples/rails/hello_world_controller.rb +186 -0
  30. data/examples/rails/hello_world_routes.rb +28 -0
  31. data/examples/rails/rails8_minimal_demo.rb +132 -0
  32. data/examples/rails/rails8_simple_demo.rb +140 -0
  33. data/examples/rails/rails8_working_demo.rb +255 -0
  34. data/examples/rails/real_world_blog_api.rb +510 -0
  35. data/examples/rails/server_test.rb +46 -0
  36. data/examples/rails/test_direct_processing.rb +41 -0
  37. data/examples/rails/test_hello_world.rb +80 -0
  38. data/examples/rails/test_rails_integration.rb +54 -0
  39. data/examples/rails/traditional_app/Gemfile +37 -0
  40. data/examples/rails/traditional_app/README.md +265 -0
  41. data/examples/rails/traditional_app/app/controllers/api/v1/posts_controller.rb +254 -0
  42. data/examples/rails/traditional_app/app/controllers/api/v1/users_controller.rb +220 -0
  43. data/examples/rails/traditional_app/app/controllers/application_controller.rb +86 -0
  44. data/examples/rails/traditional_app/app/controllers/application_controller_simplified.rb +87 -0
  45. data/examples/rails/traditional_app/app/controllers/documentation_controller.rb +149 -0
  46. data/examples/rails/traditional_app/app/controllers/health_controller.rb +42 -0
  47. data/examples/rails/traditional_app/config/routes.rb +25 -0
  48. data/examples/rails/traditional_app/config/routes_best_practice.rb +25 -0
  49. data/examples/rails/traditional_app/config/routes_simplified.rb +36 -0
  50. data/examples/rails/traditional_app_runnable.rb +406 -0
  51. data/examples/rails/users_controller.rb +4 -1
  52. data/examples/serverless/Gemfile +43 -0
  53. data/examples/serverless/QUICKSTART.md +331 -0
  54. data/examples/serverless/README.md +520 -0
  55. data/examples/serverless/aws_lambda_example.rb +307 -0
  56. data/examples/serverless/aws_sam_template.yaml +215 -0
  57. data/examples/serverless/azure_functions_example.rb +407 -0
  58. data/examples/serverless/deploy.rb +204 -0
  59. data/examples/serverless/gcp_cloud_functions_example.rb +367 -0
  60. data/examples/serverless/gcp_function.yaml +23 -0
  61. data/examples/serverless/host.json +24 -0
  62. data/examples/serverless/package.json +32 -0
  63. data/examples/serverless/spec/aws_lambda_spec.rb +196 -0
  64. data/examples/serverless/spec/spec_helper.rb +89 -0
  65. data/examples/serverless/vercel.json +31 -0
  66. data/examples/serverless/vercel_example.rb +404 -0
  67. data/examples/strict_validation_examples.rb +104 -0
  68. data/examples/validation_error_examples.rb +173 -0
  69. data/lib/rapitapir/ai/llm_instruction.rb +456 -0
  70. data/lib/rapitapir/ai/mcp.rb +134 -0
  71. data/lib/rapitapir/ai/rag.rb +287 -0
  72. data/lib/rapitapir/ai/rag_middleware.rb +147 -0
  73. data/lib/rapitapir/auth/oauth2.rb +43 -57
  74. data/lib/rapitapir/cli/command.rb +362 -2
  75. data/lib/rapitapir/cli/mcp_export.rb +18 -0
  76. data/lib/rapitapir/cli/validator.rb +2 -6
  77. data/lib/rapitapir/core/endpoint.rb +59 -6
  78. data/lib/rapitapir/core/enhanced_endpoint.rb +2 -6
  79. data/lib/rapitapir/dsl/fluent_endpoint_builder.rb +53 -0
  80. data/lib/rapitapir/endpoint_registry.rb +47 -0
  81. data/lib/rapitapir/observability/health_check.rb +4 -4
  82. data/lib/rapitapir/observability/logging.rb +10 -10
  83. data/lib/rapitapir/schema.rb +2 -2
  84. data/lib/rapitapir/server/rack_adapter.rb +1 -3
  85. data/lib/rapitapir/server/rails/configuration.rb +77 -0
  86. data/lib/rapitapir/server/rails/controller_base.rb +185 -0
  87. data/lib/rapitapir/server/rails/documentation_helpers.rb +76 -0
  88. data/lib/rapitapir/server/rails/resource_builder.rb +181 -0
  89. data/lib/rapitapir/server/rails/routes.rb +114 -0
  90. data/lib/rapitapir/server/rails_adapter.rb +10 -3
  91. data/lib/rapitapir/server/rails_adapter_class.rb +1 -3
  92. data/lib/rapitapir/server/rails_controller.rb +1 -3
  93. data/lib/rapitapir/server/rails_integration.rb +67 -0
  94. data/lib/rapitapir/server/rails_response_handler.rb +16 -3
  95. data/lib/rapitapir/server/sinatra_adapter.rb +29 -5
  96. data/lib/rapitapir/server/sinatra_integration.rb +4 -4
  97. data/lib/rapitapir/sinatra/extension.rb +2 -2
  98. data/lib/rapitapir/sinatra/oauth2_helpers.rb +34 -40
  99. data/lib/rapitapir/types/array.rb +4 -0
  100. data/lib/rapitapir/types/auto_derivation.rb +4 -18
  101. data/lib/rapitapir/types/datetime.rb +1 -3
  102. data/lib/rapitapir/types/float.rb +2 -6
  103. data/lib/rapitapir/types/hash.rb +40 -2
  104. data/lib/rapitapir/types/integer.rb +4 -12
  105. data/lib/rapitapir/types/object.rb +6 -2
  106. data/lib/rapitapir/types.rb +6 -2
  107. data/lib/rapitapir/version.rb +1 -1
  108. data/lib/rapitapir.rb +5 -3
  109. data/rapitapir.gemspec +7 -5
  110. metadata +116 -16
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Api::V1::UsersController < ApplicationController
4
+ rapitapir do
5
+ # User type definitions
6
+ user_type = T.hash(
7
+ id: T.integer,
8
+ email: T.string,
9
+ name: T.string,
10
+ bio: T.string.optional,
11
+ avatar_url: T.string.optional,
12
+ created_at: T.string,
13
+ updated_at: T.string,
14
+ posts_count: T.integer
15
+ )
16
+
17
+ create_user_type = T.hash(
18
+ email: T.string,
19
+ name: T.string,
20
+ bio: T.string.optional,
21
+ password: T.string
22
+ )
23
+
24
+ update_user_type = T.hash(
25
+ email: T.string.optional,
26
+ name: T.string.optional,
27
+ bio: T.string.optional
28
+ )
29
+
30
+ # List users with search and pagination
31
+ GET('/api/v1/users')
32
+ .in(query(:search, T.string.optional))
33
+ .in(query(:page, T.integer.default(1)))
34
+ .in(query(:per_page, T.integer.default(20)))
35
+ .in(query(:sort, T.enum(['name', 'email', 'created_at']).default('created_at')))
36
+ .in(query(:order, T.enum(['asc', 'desc']).default('desc')))
37
+ .out(json_body(
38
+ users: T.array(user_type),
39
+ pagination: T.hash(
40
+ page: T.integer,
41
+ per_page: T.integer,
42
+ total: T.integer,
43
+ total_pages: T.integer,
44
+ has_next: T.boolean,
45
+ has_prev: T.boolean
46
+ )
47
+ ))
48
+ .summary("List users")
49
+ .description("Get a paginated list of users with optional search")
50
+ .tag("Users")
51
+
52
+ # Get user by ID
53
+ GET('/api/v1/users/:id')
54
+ .in(path(:id, T.integer))
55
+ .out(json_body(user: user_type))
56
+ .summary("Get user")
57
+ .description("Get a specific user by ID")
58
+ .tag("Users")
59
+
60
+ # Create new user
61
+ POST('/api/v1/users')
62
+ .in(json_body(create_user_type))
63
+ .out(json_body(user: user_type), 201)
64
+ .summary("Create user")
65
+ .description("Create a new user account")
66
+ .tag("Users")
67
+
68
+ # Update user
69
+ PUT('/api/v1/users/:id')
70
+ .in(path(:id, T.integer))
71
+ .in(json_body(update_user_type))
72
+ .out(json_body(user: user_type))
73
+ .summary("Update user")
74
+ .description("Update an existing user")
75
+ .tag("Users")
76
+
77
+ # Delete user
78
+ DELETE('/api/v1/users/:id')
79
+ .in(path(:id, T.integer))
80
+ .out(json_body(message: T.string))
81
+ .summary("Delete user")
82
+ .description("Delete a user account")
83
+ .tag("Users")
84
+
85
+ # Get user's posts
86
+ GET('/api/v1/users/:id/posts')
87
+ .in(path(:id, T.integer))
88
+ .in(query(:published, T.boolean.optional))
89
+ .in(query(:page, T.integer.default(1)))
90
+ .out(json_body(
91
+ posts: T.array(T.hash(
92
+ id: T.integer,
93
+ title: T.string,
94
+ excerpt: T.string,
95
+ published: T.boolean,
96
+ created_at: T.string
97
+ )),
98
+ pagination: T.hash(
99
+ page: T.integer,
100
+ total: T.integer,
101
+ total_pages: T.integer
102
+ )
103
+ ))
104
+ .summary("Get user posts")
105
+ .description("Get all posts by a specific user")
106
+ .tag("Users")
107
+ end
108
+
109
+ def list_users
110
+ users_scope = User.includes(:posts)
111
+
112
+ # Apply search filter
113
+ if inputs[:search].present?
114
+ search_term = "%#{inputs[:search]}%"
115
+ users_scope = users_scope.where(
116
+ "name ILIKE ? OR email ILIKE ?", search_term, search_term
117
+ )
118
+ end
119
+
120
+ # Apply sorting
121
+ order_clause = "#{inputs[:sort]} #{inputs[:order]}"
122
+ users_scope = users_scope.order(order_clause)
123
+
124
+ # Pagination
125
+ page = inputs[:page]
126
+ per_page = [inputs[:per_page], 100].min # Cap at 100
127
+
128
+ users = users_scope.page(page).per(per_page)
129
+
130
+ {
131
+ users: users.map { |user| serialize_user(user) },
132
+ pagination: pagination_metadata(users_scope, page, per_page)
133
+ }
134
+ end
135
+
136
+ def get_user
137
+ user = User.includes(:posts).find_by(id: inputs[:id])
138
+ return render_error("User not found", 404) unless user
139
+
140
+ { user: serialize_user(user) }
141
+ end
142
+
143
+ def create_user
144
+ user = User.new(user_create_params)
145
+
146
+ if user.save
147
+ render json: { user: serialize_user(user) }, status: 201
148
+ else
149
+ render_error("Validation failed", 422, errors: user.errors.full_messages)
150
+ end
151
+ end
152
+
153
+ def update_user
154
+ user = User.find_by(id: inputs[:id])
155
+ return render_error("User not found", 404) unless user
156
+
157
+ if user.update(user_update_params)
158
+ { user: serialize_user(user) }
159
+ else
160
+ render_error("Validation failed", 422, errors: user.errors.full_messages)
161
+ end
162
+ end
163
+
164
+ def delete_user
165
+ user = User.find_by(id: inputs[:id])
166
+ return render_error("User not found", 404) unless user
167
+
168
+ user.destroy
169
+ { message: "User deleted successfully" }
170
+ end
171
+
172
+ def get_user_posts
173
+ user = User.find_by(id: inputs[:id])
174
+ return render_error("User not found", 404) unless user
175
+
176
+ posts_scope = user.posts
177
+ posts_scope = posts_scope.where(published: inputs[:published]) if inputs.key?(:published)
178
+
179
+ page = inputs[:page]
180
+ posts = posts_scope.page(page).per(10)
181
+
182
+ {
183
+ posts: posts.map { |post| serialize_post_summary(post) },
184
+ pagination: pagination_metadata(posts_scope, page, 10)
185
+ }
186
+ end
187
+
188
+ private
189
+
190
+ def user_create_params
191
+ inputs.slice(:email, :name, :bio, :password)
192
+ end
193
+
194
+ def user_update_params
195
+ inputs.slice(:email, :name, :bio).compact
196
+ end
197
+
198
+ def serialize_user(user)
199
+ {
200
+ id: user.id,
201
+ email: user.email,
202
+ name: user.name,
203
+ bio: user.bio,
204
+ avatar_url: user.avatar_url,
205
+ created_at: user.created_at.iso8601,
206
+ updated_at: user.updated_at.iso8601,
207
+ posts_count: user.posts.count
208
+ }
209
+ end
210
+
211
+ def serialize_post_summary(post)
212
+ {
213
+ id: post.id,
214
+ title: post.title,
215
+ excerpt: post.content.truncate(200),
216
+ published: post.published,
217
+ created_at: post.created_at.iso8601
218
+ }
219
+ end
220
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationController < RapiTapir::Server::Rails::ControllerBase
4
+ # Global configuration for all controllers
5
+ rapitapir do
6
+ # Enable development features (automatic docs, etc.)
7
+ development_defaults! if Rails.env.development?
8
+
9
+ # Global error handling
10
+ error_out(json_body(error: T.string, details: T.string.optional), 500)
11
+ error_out(json_body(error: T.string), 401)
12
+ error_out(json_body(error: T.string), 403)
13
+ error_out(json_body(error: T.string), 404)
14
+ error_out(json_body(error: T.string, errors: T.array(T.string).optional), 422)
15
+
16
+ # Health check endpoint - no separate controller needed!
17
+ GET('/health')
18
+ .out(json_body(
19
+ status: T.string,
20
+ timestamp: T.string,
21
+ version: T.string,
22
+ environment: T.string,
23
+ database: T.string,
24
+ services: T.hash(redis: T.string)
25
+ ))
26
+ .summary("Health check")
27
+ .description("Check API and service health")
28
+ .tag("System")
29
+ end
30
+
31
+ def health_check
32
+ {
33
+ status: 'ok',
34
+ timestamp: Time.current.iso8601,
35
+ version: '1.0.0',
36
+ environment: Rails.env,
37
+ database: database_status,
38
+ services: {
39
+ redis: redis_status
40
+ }
41
+ }
42
+ end
43
+
44
+ protected
45
+
46
+ # Helper method for standardized error responses
47
+ def render_error(message, status, details: nil, errors: nil)
48
+ payload = { error: message }
49
+ payload[:details] = details if details
50
+ payload[:errors] = errors if errors
51
+
52
+ render json: payload, status: status
53
+ end
54
+
55
+ # Helper for pagination metadata
56
+ def pagination_metadata(collection, page, per_page)
57
+ total = collection.respond_to?(:count) ? collection.count : collection.size
58
+ {
59
+ page: page,
60
+ per_page: per_page,
61
+ total: total,
62
+ total_pages: (total.to_f / per_page).ceil,
63
+ has_next: page < (total.to_f / per_page).ceil,
64
+ has_prev: page > 1
65
+ }
66
+ end
67
+
68
+ private
69
+
70
+ def database_status
71
+ ActiveRecord::Base.connection.execute('SELECT 1')
72
+ 'connected'
73
+ rescue => e
74
+ Rails.logger.error "Database check failed: #{e.message}"
75
+ 'disconnected'
76
+ end
77
+
78
+ def redis_status
79
+ # Example Redis check - uncomment if using Redis
80
+ # Redis.current.ping == 'PONG' ? 'connected' : 'disconnected'
81
+ 'not_configured'
82
+ rescue => e
83
+ Rails.logger.error "Redis check failed: #{e.message}"
84
+ 'disconnected'
85
+ end
86
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationController < RapiTapir::Server::Rails::ControllerBase
4
+ include RapiTapir::Server::Rails::DocumentationHelpers
5
+
6
+ rapitapir do
7
+ # Enable development features (automatic docs, etc.)
8
+ development_defaults! if Rails.env.development?
9
+
10
+ # Global error handling
11
+ error_out(json_body(error: T.string, details: T.string.optional), 500)
12
+ error_out(json_body(error: T.string), 401)
13
+ error_out(json_body(error: T.string), 403)
14
+ error_out(json_body(error: T.string), 404)
15
+ error_out(json_body(error: T.string, errors: T.array(T.string).optional), 422)
16
+
17
+ # Health check endpoint - no separate controller needed!
18
+ GET('/health')
19
+ .out(json_body(
20
+ status: T.string,
21
+ timestamp: T.string,
22
+ version: T.string,
23
+ environment: T.string,
24
+ database: T.string,
25
+ services: T.hash(redis: T.string)
26
+ ))
27
+ .summary("Health check")
28
+ .description("Check API and service health")
29
+ .tag("System")
30
+ end
31
+
32
+ def health_check
33
+ {
34
+ status: 'ok',
35
+ timestamp: Time.current.iso8601,
36
+ version: '1.0.0',
37
+ environment: Rails.env,
38
+ database: database_status,
39
+ services: {
40
+ redis: redis_status
41
+ }
42
+ }
43
+ end
44
+
45
+ protected
46
+
47
+ # Helper method for standardized error responses
48
+ def render_error(message, status, details: nil, errors: nil)
49
+ payload = { error: message }
50
+ payload[:details] = details if details
51
+ payload[:errors] = errors if errors
52
+
53
+ render json: payload, status: status
54
+ end
55
+
56
+ # Helper for pagination metadata
57
+ def pagination_metadata(collection, page, per_page)
58
+ total = collection.respond_to?(:count) ? collection.count : collection.size
59
+ {
60
+ page: page,
61
+ per_page: per_page,
62
+ total: total,
63
+ total_pages: (total.to_f / per_page).ceil,
64
+ has_next: page < (total.to_f / per_page).ceil,
65
+ has_prev: page > 1
66
+ }
67
+ end
68
+
69
+ private
70
+
71
+ def database_status
72
+ ActiveRecord::Base.connection.execute('SELECT 1')
73
+ 'connected'
74
+ rescue => e
75
+ Rails.logger.error "Database check failed: #{e.message}"
76
+ 'disconnected'
77
+ end
78
+
79
+ def redis_status
80
+ # Example Redis check - uncomment if using Redis
81
+ # Redis.current.ping == 'PONG' ? 'connected' : 'disconnected'
82
+ 'not_configured'
83
+ rescue => e
84
+ Rails.logger.error "Redis check failed: #{e.message}"
85
+ 'disconnected'
86
+ end
87
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DocumentationController < ApplicationController
4
+ skip_before_action :verify_authenticity_token, if: -> { request.format.json? }
5
+
6
+ def swagger_ui
7
+ render html: swagger_ui_html.html_safe
8
+ end
9
+
10
+ def openapi_spec
11
+ render json: generate_openapi_spec
12
+ end
13
+
14
+ def redoc
15
+ render html: redoc_html.html_safe
16
+ end
17
+
18
+ private
19
+
20
+ def swagger_ui_html
21
+ <<~HTML
22
+ <!DOCTYPE html>
23
+ <html>
24
+ <head>
25
+ <title>#{Rails.application.class.name.split('::').first} API Documentation</title>
26
+ <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui.css" />
27
+ <style>
28
+ html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }
29
+ *, *:before, *:after { box-sizing: inherit; }
30
+ body { margin:0; background: #fafafa; }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <div id="swagger-ui"></div>
35
+ <script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-bundle.js"></script>
36
+ <script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-standalone-preset.js"></script>
37
+ <script>
38
+ window.onload = function() {
39
+ const ui = SwaggerUIBundle({
40
+ url: '/openapi.json',
41
+ dom_id: '#swagger-ui',
42
+ deepLinking: true,
43
+ presets: [
44
+ SwaggerUIBundle.presets.apis,
45
+ SwaggerUIStandalonePreset
46
+ ],
47
+ plugins: [
48
+ SwaggerUIBundle.plugins.DownloadUrl
49
+ ],
50
+ layout: "StandaloneLayout"
51
+ });
52
+ };
53
+ </script>
54
+ </body>
55
+ </html>
56
+ HTML
57
+ end
58
+
59
+ def redoc_html
60
+ <<~HTML
61
+ <!DOCTYPE html>
62
+ <html>
63
+ <head>
64
+ <title>#{Rails.application.class.name.split('::').first} API Documentation</title>
65
+ <meta charset="utf-8"/>
66
+ <meta name="viewport" content="width=device-width, initial-scale=1">
67
+ <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
68
+ <style>
69
+ body { margin: 0; padding: 0; }
70
+ </style>
71
+ </head>
72
+ <body>
73
+ <redoc spec-url='/openapi.json'></redoc>
74
+ <script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0/bundles/redoc.standalone.js"></script>
75
+ </body>
76
+ </html>
77
+ HTML
78
+ end
79
+
80
+ def generate_openapi_spec
81
+ {
82
+ openapi: "3.0.3",
83
+ info: {
84
+ title: "#{Rails.application.class.name.split('::').first} API",
85
+ version: "1.0.0",
86
+ description: "API documentation generated by RapiTapir",
87
+ contact: {
88
+ name: "API Support",
89
+ email: "support@example.com"
90
+ }
91
+ },
92
+ servers: [
93
+ {
94
+ url: "#{request.protocol}#{request.host_with_port}",
95
+ description: "#{Rails.env.capitalize} server"
96
+ }
97
+ ],
98
+ paths: generate_paths,
99
+ components: {
100
+ securitySchemes: {
101
+ bearerAuth: {
102
+ type: "http",
103
+ scheme: "bearer",
104
+ bearerFormat: "JWT"
105
+ }
106
+ }
107
+ }
108
+ }
109
+ end
110
+
111
+ def generate_paths
112
+ paths = {}
113
+
114
+ # Get all RapiTapir controllers
115
+ rapitapir_controllers = [
116
+ Api::V1::UsersController,
117
+ Api::V1::PostsController
118
+ ]
119
+
120
+ rapitapir_controllers.each do |controller|
121
+ next unless controller.respond_to?(:endpoints)
122
+
123
+ controller.endpoints.each do |endpoint|
124
+ path_key = endpoint.path.gsub(/:(\w+)/, '{\1}')
125
+ method = endpoint.method.downcase
126
+
127
+ paths[path_key] ||= {}
128
+ paths[path_key][method] = {
129
+ summary: endpoint.summary || "#{method.upcase} #{path_key}",
130
+ description: endpoint.description || "",
131
+ tags: endpoint.tags || [controller.name.demodulize.gsub('Controller', '')],
132
+ operationId: "#{method}_#{path_key.gsub(/[\/{}]/, '_').gsub(/_+/, '_').gsub(/^_|_$/, '')}",
133
+ responses: {
134
+ "200" => {
135
+ description: "Success"
136
+ }
137
+ }
138
+ }
139
+
140
+ # Add authentication if required
141
+ if endpoint.inputs.any? { |input| input.name == :authorization }
142
+ paths[path_key][method][:security] = [{ bearerAuth: [] }]
143
+ end
144
+ end
145
+ end
146
+
147
+ paths
148
+ end
149
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HealthController < ApplicationController
4
+ skip_before_action :verify_authenticity_token
5
+
6
+ def check
7
+ render json: {
8
+ status: 'ok',
9
+ timestamp: Time.current.iso8601,
10
+ version: '1.0.0',
11
+ environment: Rails.env,
12
+ database: database_status,
13
+ services: services_status
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ def database_status
20
+ ActiveRecord::Base.connection.execute('SELECT 1')
21
+ 'connected'
22
+ rescue => e
23
+ Rails.logger.error "Database check failed: #{e.message}"
24
+ 'disconnected'
25
+ end
26
+
27
+ def services_status
28
+ {
29
+ redis: redis_status,
30
+ # Add other service checks here
31
+ }
32
+ end
33
+
34
+ def redis_status
35
+ # Example Redis check - uncomment if using Redis
36
+ # Redis.current.ping == 'PONG' ? 'connected' : 'disconnected'
37
+ 'not_configured'
38
+ rescue => e
39
+ Rails.logger.error "Redis check failed: #{e.message}"
40
+ 'disconnected'
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ # Health check (from ApplicationController)
5
+ rapitapir_routes_for ApplicationController
6
+
7
+ # API v1 routes - RapiTapir auto-generates routes from endpoint definitions
8
+ namespace :api do
9
+ namespace :v1 do
10
+ rapitapir_routes_for 'Api::V1::UsersController'
11
+ rapitapir_routes_for 'Api::V1::PostsController'
12
+ end
13
+ end
14
+
15
+ # Documentation routes (automatically added by development_defaults! in development)
16
+ # Available at:
17
+ # - GET /docs -> Swagger UI
18
+ # - GET /openapi.json -> OpenAPI 3.0 specification
19
+ # - GET /redoc -> ReDoc alternative UI (if enabled)
20
+
21
+ # Catch-all for unmatched routes
22
+ match '*path', to: proc { |env|
23
+ [404, { 'Content-Type' => 'application/json' }, [{ error: 'Route not found' }.to_json]]
24
+ }, via: :all
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Simplified Rails application using RapiTapir best practices
4
+ # No separate health or documentation controllers needed!
5
+
6
+ Rails.application.routes.draw do
7
+ # Health check and docs are handled by RapiTapir automatically
8
+ rapitapir_routes_for ApplicationController
9
+
10
+ # API endpoints
11
+ namespace :api do
12
+ namespace :v1 do
13
+ rapitapir_routes_for 'Api::V1::UsersController'
14
+ rapitapir_routes_for 'Api::V1::PostsController'
15
+ end
16
+ end
17
+
18
+ # RapiTapir provides documentation routes automatically in development
19
+ if Rails.env.development?
20
+ # These are automatically added by development_defaults!
21
+ # /docs -> Swagger UI
22
+ # /openapi.json -> OpenAPI spec
23
+ # /redoc -> ReDoc alternative UI
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ # Use RapiTapir's built-in route generation for all controllers
5
+ rapitapir_routes_for ApplicationController # This includes /health
6
+
7
+ # API v1 routes
8
+ namespace :api do
9
+ namespace :v1 do
10
+ rapitapir_routes_for 'Api::V1::UsersController'
11
+ rapitapir_routes_for 'Api::V1::PostsController'
12
+ end
13
+ end
14
+
15
+ # Documentation routes using RapiTapir's built-in helpers
16
+ if Rails.env.development?
17
+ # These use the DocumentationHelpers module that's already included
18
+ get '/docs', to: proc { |env|
19
+ request = ActionDispatch::Request.new(env)
20
+ html = ApplicationController.new.send(:generate_swagger_ui_html)
21
+ [200, { 'Content-Type' => 'text/html' }, [html]]
22
+ }
23
+
24
+ get '/openapi.json', to: proc { |env|
25
+ request = ActionDispatch::Request.new(env)
26
+ controllers = [ApplicationController, Api::V1::UsersController, Api::V1::PostsController]
27
+ spec = ApplicationController.new.send(:generate_openapi_spec_for_controllers, controllers)
28
+ [200, { 'Content-Type' => 'application/json' }, [JSON.pretty_generate(spec)]]
29
+ }
30
+ end
31
+
32
+ # Catch-all for unmatched routes
33
+ match '*path', to: proc { |env|
34
+ [404, { 'Content-Type' => 'application/json' }, [{ error: 'Route not found' }.to_json]]
35
+ }, via: :all
36
+ end