rapitapir 0.1.2 → 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 (109) 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. metadata +74 -2
@@ -0,0 +1,497 @@
1
+ # Using RapiTapir in Real Rails Applications
2
+
3
+ This guide demonstrates how to integrate RapiTapir into real-world Rails 8+ applications for type-safe, documented APIs.
4
+
5
+ ## šŸš€ Quick Start
6
+
7
+ ### 1. Add RapiTapir to your Gemfile
8
+
9
+ ```ruby
10
+ gem 'rapitapir', '~> 1.0'
11
+ ```
12
+
13
+ ### 2. Update your ApplicationController
14
+
15
+ ```ruby
16
+ class ApplicationController < RapiTapir::Server::Rails::ControllerBase
17
+ rapitapir do
18
+ development_defaults! if Rails.env.development?
19
+
20
+ # Global error responses
21
+ error_out(json_body(error: T.string), 401)
22
+ error_out(json_body(error: T.string), 403)
23
+ error_out(json_body(error: T.string), 404)
24
+ error_out(json_body(error: T.string, errors: T.array(T.string).optional), 422)
25
+ end
26
+ end
27
+ ```
28
+
29
+ ### 3. Create API Controllers
30
+
31
+ ```ruby
32
+ class Api::V1::UsersController < ApplicationController
33
+ rapitapir do
34
+ user_type = T.hash(
35
+ id: T.integer,
36
+ name: T.string,
37
+ email: T.string,
38
+ created_at: T.string
39
+ )
40
+
41
+ GET('/api/v1/users')
42
+ .in(query(:page, T.integer.default(1)))
43
+ .out(json_body(users: T.array(user_type)))
44
+ .summary("List users")
45
+ .tag("Users")
46
+
47
+ POST('/api/v1/users')
48
+ .in(json_body(name: T.string, email: T.string))
49
+ .out(json_body(user: user_type), 201)
50
+ .summary("Create user")
51
+ .tag("Users")
52
+ end
53
+
54
+ def list_users
55
+ users = User.page(inputs[:page])
56
+ { users: users.map(&method(:serialize_user)) }
57
+ end
58
+
59
+ def create_user
60
+ user = User.create!(inputs.slice(:name, :email))
61
+ render json: { user: serialize_user(user) }, status: 201
62
+ end
63
+
64
+ private
65
+
66
+ def serialize_user(user)
67
+ {
68
+ id: user.id,
69
+ name: user.name,
70
+ email: user.email,
71
+ created_at: user.created_at.iso8601
72
+ }
73
+ end
74
+ end
75
+ ```
76
+
77
+ ### 4. Update Routes
78
+
79
+ ```ruby
80
+ Rails.application.routes.draw do
81
+ namespace :api do
82
+ namespace :v1 do
83
+ rapitapir_routes_for 'Api::V1::UsersController'
84
+ end
85
+ end
86
+
87
+ # Documentation (development only)
88
+ if Rails.env.development?
89
+ get '/docs', to: 'documentation#swagger_ui'
90
+ get '/openapi.json', to: 'documentation#openapi_spec'
91
+ end
92
+ end
93
+ ```
94
+
95
+ Use the new `ControllerBase` class for the cleanest syntax:
96
+
97
+ ```ruby
98
+ # app/controllers/users_controller.rb
99
+ require 'rapitapir/server/rails/controller_base'
100
+
101
+ class UsersController < RapiTapir::Server::Rails::ControllerBase
102
+ rapitapir do
103
+ info(title: 'Users API', version: '1.0.0')
104
+ # development_defaults! # Coming soon
105
+ end
106
+
107
+ USER_SCHEMA = T.hash({
108
+ "id" => T.integer,
109
+ "name" => T.string,
110
+ "email" => T.email
111
+ })
112
+
113
+ api_resource '/users', schema: USER_SCHEMA do
114
+ crud do
115
+ index { User.all.map(&:attributes) }
116
+ show { |inputs| User.find(inputs[:id]).attributes }
117
+ create { |inputs| User.create!(inputs[:body]).attributes }
118
+ end
119
+ end
120
+ end
121
+ ```
122
+
123
+ ### 3. Auto-Generate Routes
124
+
125
+ Add to your `config/routes.rb`:
126
+
127
+ ```ruby
128
+ Rails.application.routes.draw do
129
+ # Option 1: Auto-generate routes for specific controller
130
+ rapitapir_routes_for UsersController
131
+
132
+ # Option 2: Auto-discover all RapiTapir controllers
133
+ rapitapir_auto_routes
134
+ end
135
+ ```
136
+
137
+ ### 4. Access Documentation
138
+
139
+ Start your Rails server and visit:
140
+ - Interactive Documentation: `http://localhost:3000/docs`
141
+ - OpenAPI Specification: `http://localhost:3000/openapi.json`
142
+
143
+ ## šŸ—ļø Core Features
144
+
145
+ ### Clean Base Class Syntax
146
+
147
+ The new `ControllerBase` provides inheritance-based setup:
148
+
149
+ ```ruby
150
+ class ApiController < RapiTapir::Server::Rails::ControllerBase
151
+ rapitapir do
152
+ info(title: 'My API', version: '1.0.0')
153
+ end
154
+
155
+ # T shortcut automatically available
156
+ # Enhanced HTTP verb DSL automatically available
157
+ # Auto action generation
158
+ end
159
+ ```
160
+
161
+ ### Enhanced HTTP Verb DSL
162
+
163
+ Use the same fluent DSL as Sinatra:
164
+
165
+ ```ruby
166
+ endpoint(
167
+ GET('/books/:id')
168
+ .summary('Get book by ID')
169
+ .path_param(:id, T.integer(minimum: 1))
170
+ .ok(BOOK_SCHEMA)
171
+ .not_found(T.hash({ "error" => T.string }))
172
+ .build
173
+ ) do |inputs|
174
+ book = Book.find(inputs[:id])
175
+ book ? book.attributes : (render json: {error: 'Not found'}, status: 404)
176
+ end
177
+ ```
178
+
179
+ ### RESTful Resource Builder
180
+
181
+ Create complete CRUD APIs with minimal code:
182
+
183
+ ```ruby
184
+ api_resource '/books', schema: BOOK_SCHEMA do
185
+ crud do
186
+ index do
187
+ books = Book.all
188
+ books = books.where(published: true) if params[:published] == 'true'
189
+ books.limit(params[:limit] || 50).map(&:attributes)
190
+ end
191
+
192
+ show { |inputs| Book.find(inputs[:id]).attributes }
193
+
194
+ create do |inputs|
195
+ book = Book.create!(inputs[:body])
196
+ response.status = 201
197
+ book.attributes
198
+ end
199
+
200
+ update { |inputs| Book.update!(inputs[:id], inputs[:body]).attributes }
201
+ destroy { |inputs| Book.destroy(inputs[:id]); head :no_content }
202
+ end
203
+
204
+ # Add custom endpoints
205
+ custom :get, 'featured' do
206
+ Book.where(featured: true).map(&:attributes)
207
+ end
208
+ end
209
+ ```
210
+
211
+ ### Automatic Route Generation
212
+
213
+ Three options for route generation:
214
+
215
+ #### Option 1: Per-Controller Generation
216
+ ```ruby
217
+ # config/routes.rb
218
+ Rails.application.routes.draw do
219
+ rapitapir_routes_for UsersController
220
+ rapitapir_routes_for BooksController
221
+ end
222
+ ```
223
+
224
+ #### Option 2: Auto-Discovery
225
+ ```ruby
226
+ # config/routes.rb
227
+ Rails.application.routes.draw do
228
+ rapitapir_auto_routes # Finds all RapiTapir controllers
229
+ end
230
+ ```
231
+
232
+ #### Option 3: Manual Routes (Still Supported)
233
+ ```ruby
234
+ # config/routes.rb
235
+ Rails.application.routes.draw do
236
+ resources :users, only: [:index, :show, :create, :update, :destroy]
237
+ end
238
+ ```
239
+
240
+ ## šŸ“‹ Migration from Legacy Approach
241
+
242
+ ### Before (Verbose)
243
+
244
+ ```ruby
245
+ class UsersController < ApplicationController
246
+ include RapiTapir::Server::Rails::Controller
247
+
248
+ rapitapir_endpoint :index, RapiTapir.get('/users')
249
+ .summary('List all users')
250
+ .out(RapiTapir::Core::Output.new(
251
+ kind: :json, type: { users: Array }
252
+ )) do |_inputs|
253
+ { users: @users.values }
254
+ end
255
+
256
+ def index
257
+ process_rapitapir_endpoint
258
+ end
259
+ end
260
+ ```
261
+
262
+ ### After (Clean)
263
+
264
+ ```ruby
265
+ class UsersController < RapiTapir::Server::Rails::ControllerBase
266
+ rapitapir do
267
+ info(title: 'Users API', version: '1.0.0')
268
+ end
269
+
270
+ USER_SCHEMA = T.hash({ "id" => T.integer, "name" => T.string })
271
+
272
+ api_resource '/users', schema: USER_SCHEMA do
273
+ crud do
274
+ index { User.all.map(&:attributes) }
275
+ end
276
+ end
277
+ end
278
+ ```
279
+
280
+ ## šŸ”§ Advanced Features
281
+
282
+ ### Custom Endpoints with Full Type Safety
283
+
284
+ ```ruby
285
+ endpoint(
286
+ POST('/users/bulk')
287
+ .summary('Create multiple users')
288
+ .json_body(T.array(USER_CREATE_SCHEMA))
289
+ .created(T.array(USER_SCHEMA))
290
+ .bad_request(ERROR_SCHEMA)
291
+ .build
292
+ ) do |inputs|
293
+ users = inputs[:body].map { |user_data| User.create!(user_data) }
294
+ response.status = 201
295
+ users.map(&:attributes)
296
+ end
297
+ ```
298
+
299
+ ### Search Endpoints
300
+
301
+ ```ruby
302
+ endpoint(
303
+ GET('/users/search')
304
+ .summary('Search users')
305
+ .query(:q, T.string(min_length: 1))
306
+ .query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)))
307
+ .ok(T.array(USER_SCHEMA))
308
+ .build
309
+ ) do |inputs|
310
+ results = User.where("name ILIKE ?", "%#{inputs[:q]}%")
311
+ results = results.limit(inputs[:limit]) if inputs[:limit]
312
+ results.map(&:attributes)
313
+ end
314
+ ```
315
+
316
+ ### Error Handling
317
+
318
+ ```ruby
319
+ show do |inputs|
320
+ user = User.find_by(id: inputs[:id])
321
+
322
+ if user.nil?
323
+ render json: { error: 'User not found' }, status: :not_found
324
+ return
325
+ end
326
+
327
+ user.attributes
328
+ end
329
+ ```
330
+
331
+ ## šŸ“– Complete Example
332
+
333
+ Here's a full-featured Rails controller using all the enhanced features:
334
+
335
+ ```ruby
336
+ # app/controllers/books_controller.rb
337
+ require 'rapitapir/server/rails/controller_base'
338
+
339
+ class BooksController < RapiTapir::Server::Rails::ControllerBase
340
+ rapitapir do
341
+ info(
342
+ title: 'Books API',
343
+ description: 'A comprehensive book management API',
344
+ version: '1.0.0'
345
+ )
346
+ end
347
+
348
+ BOOK_SCHEMA = T.hash({
349
+ "id" => T.integer,
350
+ "title" => T.string(min_length: 1, max_length: 255),
351
+ "author" => T.string(min_length: 1),
352
+ "published" => T.boolean,
353
+ "isbn" => T.optional(T.string),
354
+ "pages" => T.optional(T.integer(minimum: 1)),
355
+ "created_at" => T.datetime,
356
+ "updated_at" => T.datetime
357
+ })
358
+
359
+ BOOK_CREATE_SCHEMA = T.hash({
360
+ "title" => T.string(min_length: 1, max_length: 255),
361
+ "author" => T.string(min_length: 1),
362
+ "published" => T.optional(T.boolean),
363
+ "isbn" => T.optional(T.string),
364
+ "pages" => T.optional(T.integer(minimum: 1))
365
+ })
366
+
367
+ # CRUD operations with enhanced resource builder
368
+ api_resource '/books', schema: BOOK_SCHEMA do
369
+ crud do
370
+ index do
371
+ books = Book.includes(:author)
372
+ books = books.where(published: true) if params[:published] == 'true'
373
+ books = books.where('title ILIKE ?', "%#{params[:search]}%") if params[:search]
374
+
375
+ limit = params[:limit]&.to_i || 20
376
+ offset = params[:offset]&.to_i || 0
377
+
378
+ books.offset(offset).limit(limit).map(&:attributes)
379
+ end
380
+
381
+ show do |inputs|
382
+ book = Book.find_by(id: inputs[:id])
383
+ book ? book.attributes : (render json: {error: 'Book not found'}, status: 404)
384
+ end
385
+
386
+ create do |inputs|
387
+ book = Book.create!(inputs[:body])
388
+ response.status = 201
389
+ book.attributes
390
+ end
391
+
392
+ update do |inputs|
393
+ book = Book.find_by(id: inputs[:id])
394
+
395
+ if book.nil?
396
+ render json: { error: 'Book not found' }, status: 404
397
+ return
398
+ end
399
+
400
+ book.update!(inputs[:body])
401
+ book.attributes
402
+ end
403
+
404
+ destroy do |inputs|
405
+ book = Book.find_by(id: inputs[:id])
406
+
407
+ if book.nil?
408
+ render json: { error: 'Book not found' }, status: 404
409
+ return
410
+ end
411
+
412
+ book.destroy!
413
+ head :no_content
414
+ end
415
+ end
416
+
417
+ # Custom endpoints
418
+ custom :get, 'featured' do
419
+ Book.where(featured: true).map(&:attributes)
420
+ end
421
+ end
422
+
423
+ # Additional custom endpoints
424
+ endpoint(
425
+ GET('/books/search')
426
+ .summary('Advanced book search')
427
+ .query(:q, T.string(min_length: 1), description: 'Search query')
428
+ .query(:author, T.optional(T.string), description: 'Filter by author')
429
+ .query(:published, T.optional(T.boolean), description: 'Filter by published status')
430
+ .query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)), description: 'Results limit')
431
+ .ok(T.array(BOOK_SCHEMA))
432
+ .build
433
+ ) do |inputs|
434
+ query = inputs[:q]
435
+ books = Book.where('title ILIKE ? OR description ILIKE ?', "%#{query}%", "%#{query}%")
436
+ books = books.joins(:author).where('authors.name ILIKE ?', "%#{inputs[:author]}%") if inputs[:author]
437
+ books = books.where(published: inputs[:published]) if inputs.key?(:published)
438
+ books = books.limit(inputs[:limit] || 20)
439
+
440
+ books.map(&:attributes)
441
+ end
442
+ end
443
+ ```
444
+
445
+ ## šŸ›£ļø Routes Configuration
446
+
447
+ ```ruby
448
+ # config/routes.rb
449
+ Rails.application.routes.draw do
450
+ # Auto-generate all RapiTapir routes
451
+ rapitapir_auto_routes
452
+
453
+ # Or generate for specific controllers
454
+ # rapitapir_routes_for BooksController
455
+ # rapitapir_routes_for UsersController
456
+ end
457
+ ```
458
+
459
+ ## šŸŽÆ Benefits
460
+
461
+ ### Compared to Legacy Rails Integration
462
+
463
+ | Feature | Legacy Approach | Enhanced Approach |
464
+ |---------|----------------|-------------------|
465
+ | **Base Class** | Manual include | Clean inheritance |
466
+ | **HTTP Verbs** | Verbose `RapiTapir.get()` | Clean `GET()` |
467
+ | **Type Shortcuts** | `RapiTapir::Types.string` | `T.string` |
468
+ | **Action Generation** | Manual `def action; process_rapitapir_endpoint; end` | Automatic |
469
+ | **Route Generation** | Manual Rails routes | Auto-generated |
470
+ | **CRUD Operations** | Individual endpoint definitions | `api_resource` with `crud` block |
471
+ | **Configuration** | Scattered setup | Single `rapitapir` block |
472
+
473
+ ### Compared to Sinatra
474
+
475
+ | Feature | Sinatra | Enhanced Rails | Status |
476
+ |---------|---------|----------------|---------|
477
+ | **Clean Inheritance** | āœ… `< SinatraRapiTapir` | āœ… `< ControllerBase` | **Achieved** |
478
+ | **HTTP Verb DSL** | āœ… `GET()`, `POST()` | āœ… `GET()`, `POST()` | **Achieved** |
479
+ | **Resource Builder** | āœ… `api_resource` | āœ… `api_resource` | **Achieved** |
480
+ | **T Shortcuts** | āœ… `T.string` | āœ… `T.string` | **Achieved** |
481
+ | **Auto Routes** | āœ… Automatic | āœ… `rapitapir_auto_routes` | **Achieved** |
482
+ | **Documentation** | āœ… `/docs` | 🚧 Coming soon | **In Progress** |
483
+
484
+ ## 🚧 Coming Soon
485
+
486
+ - **Development Defaults**: Auto CORS, health checks, documentation
487
+ - **Built-in Documentation**: `/docs` endpoint for Rails apps
488
+ - **Authentication Helpers**: Bearer token, OAuth2 integration
489
+ - **Observability**: Metrics and tracing integration
490
+ - **Generator**: Rails generator for RapiTapir controllers
491
+
492
+ ## šŸ“š See Also
493
+
494
+ - [Enhanced Users Controller Example](enhanced_users_controller.rb)
495
+ - [Legacy Users Controller Example](users_controller.rb) (for comparison)
496
+ - [Sinatra Integration Guide](../docs/SINATRA_EXTENSION.md)
497
+ - [Type System Documentation](../docs/types.md)
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Comprehensive Rails Integration Validation
5
+
6
+ puts "šŸŽÆ RapiTapir Rails Integration - Comprehensive Test"
7
+ puts "=" * 55
8
+
9
+ success_count = 0
10
+ total_tests = 7
11
+
12
+ def test(description)
13
+ print "\n#{description}... "
14
+ begin
15
+ result = yield
16
+ puts "āœ…"
17
+ return true
18
+ rescue => e
19
+ puts "āŒ #{e.message}"
20
+ return false
21
+ end
22
+ end
23
+
24
+ # Test 1: Basic loading
25
+ success_count += 1 if test("Loading Rails integration") do
26
+ require_relative 'hello_world_app'
27
+ true
28
+ end
29
+
30
+ # Test 2: Controller inheritance
31
+ success_count += 1 if test("Controller inheritance working") do
32
+ HelloWorldController < RapiTapir::Server::Rails::ControllerBase
33
+ end
34
+
35
+ # Test 3: T shortcuts
36
+ success_count += 1 if test("T shortcuts available") do
37
+ type = HelloWorldController::T.string
38
+ type.is_a?(RapiTapir::Types::String)
39
+ end
40
+
41
+ # Test 4: HTTP verbs
42
+ success_count += 1 if test("HTTP verb DSL working") do
43
+ builder = HelloWorldController.GET('/test')
44
+ builder.is_a?(RapiTapir::DSL::FluentEndpointBuilder)
45
+ end
46
+
47
+ # Test 5: Endpoints registered
48
+ success_count += 1 if test("Endpoints properly registered") do
49
+ endpoints = HelloWorldController.rapitapir_endpoints
50
+ endpoints.is_a?(Hash) && endpoints.count == 4
51
+ end
52
+
53
+ # Test 6: Rails routes generated
54
+ success_count += 1 if test("Rails routes generated") do
55
+ routes = HelloWorldRailsApp.routes.routes
56
+ rapitapir_routes = routes.select { |r| r.defaults[:controller] == 'hello_world' }
57
+ rapitapir_routes.count == 4
58
+ end
59
+
60
+ # Test 7: Controller can be instantiated
61
+ success_count += 1 if test("Controller instantiation") do
62
+ controller = HelloWorldController.new
63
+ controller.respond_to?(:process_rapitapir_endpoint)
64
+ end
65
+
66
+ puts "\n" + "=" * 55
67
+ puts "šŸŽ‰ Test Results: #{success_count}/#{total_tests} tests passed"
68
+
69
+ if success_count == total_tests
70
+ puts "\nāœ… RAILS INTEGRATION FULLY WORKING!"
71
+ puts "\nšŸš€ Key Features Implemented:"
72
+ puts " • Enhanced controller base class"
73
+ puts " • Sinatra-like syntax for Rails"
74
+ puts " • Automatic action derivation"
75
+ puts " • Route generation"
76
+ puts " • Type shortcuts (T.string, etc.)"
77
+ puts " • HTTP verb DSL"
78
+ puts "\nšŸ“‹ Available Endpoints:"
79
+ HelloWorldController.rapitapir_endpoints.each do |action, config|
80
+ endpoint = config[:endpoint]
81
+ puts " #{endpoint.method.upcase} #{endpoint.path} => #{action}"
82
+ end
83
+
84
+ puts "\nšŸŽÆ Developer Experience Gap: ELIMINATED āœ…"
85
+ puts "šŸ“Š Feature Parity with Sinatra: ACHIEVED āœ…"
86
+ puts "\n🌟 Rails developers now have the same elegant syntax!"
87
+ else
88
+ puts "\nāŒ Some tests failed. Rails integration needs debugging."
89
+ end
90
+
91
+ puts "\n" + "=" * 55
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example Rails routes configuration for RapiTapir controllers
4
+ # Place this in your config/routes.rb file
5
+
6
+ Rails.application.routes.draw do
7
+ # Option 1: Auto-discover and register all RapiTapir controllers (Recommended)
8
+ # This will automatically find any controller that inherits from ControllerBase
9
+ # or includes the Rails::Controller module
10
+ rapitapir_auto_routes
11
+
12
+ # Option 2: Register specific controllers manually
13
+ # rapitapir_routes_for EnhancedUsersController
14
+ # rapitapir_routes_for BooksController
15
+ # rapitapir_routes_for OrdersController
16
+
17
+ # Option 3: Traditional Rails routes (still works if you prefer manual control)
18
+ # resources :enhanced_users, only: [:index, :show, :create, :update, :destroy] do
19
+ # collection do
20
+ # get :active
21
+ # post :bulk
22
+ # get :search
23
+ # end
24
+ # end
25
+
26
+ # Example of mixed approach - some auto, some manual
27
+ # rapitapir_routes_for EnhancedUsersController
28
+ #
29
+ # resources :admin_users, controller: 'admin/users' do
30
+ # # Manual routes for admin section
31
+ # end
32
+
33
+ # The rapitapir_auto_routes method will generate routes like:
34
+ #
35
+ # For EnhancedUsersController with api_resource '/users':
36
+ # GET /users enhanced_users#index
37
+ # GET /users/:id enhanced_users#show
38
+ # POST /users enhanced_users#create
39
+ # PUT /users/:id enhanced_users#update
40
+ # DELETE /users/:id enhanced_users#destroy
41
+ # GET /users/active enhanced_users#active
42
+ # POST /users/bulk enhanced_users#bulk
43
+ # GET /users/search enhanced_users#search
44
+
45
+ # For documentation and OpenAPI endpoints (coming soon):
46
+ # GET /docs rapitapir#docs
47
+ # GET /openapi.json rapitapir#openapi
48
+ end
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Debug Rails controller methods
5
+
6
+ puts "šŸ” Debugging Rails Controller Methods..."
7
+ puts "=" * 45
8
+
9
+ begin
10
+ # Load the controller
11
+ require_relative 'hello_world_app'
12
+
13
+ puts "\n1ļøāƒ£ Checking controller class hierarchy..."
14
+ puts "HelloWorldController ancestors:"
15
+ HelloWorldController.ancestors[0..10].each_with_index do |ancestor, i|
16
+ puts " #{i}: #{ancestor}"
17
+ end
18
+
19
+ puts "\n2ļøāƒ£ Checking if controller includes required modules..."
20
+ includes_controller = HelloWorldController.included_modules.include?(RapiTapir::Server::Rails::Controller)
21
+ puts "Includes RapiTapir::Server::Rails::Controller: #{includes_controller}"
22
+
23
+ includes_input_processor = HelloWorldController.included_modules.include?(RapiTapir::Server::Rails::InputProcessor)
24
+ puts "Includes InputProcessor: #{includes_input_processor}"
25
+
26
+ includes_response_handler = HelloWorldController.included_modules.include?(RapiTapir::Server::Rails::ResponseHandler)
27
+ puts "Includes ResponseHandler: #{includes_response_handler}"
28
+
29
+ puts "\n3ļøāƒ£ Checking available instance methods..."
30
+ controller = HelloWorldController.new
31
+ has_process_method = controller.respond_to?(:process_rapitapir_endpoint, true)
32
+ puts "Has process_rapitapir_endpoint method: #{has_process_method}"
33
+
34
+ has_extract_inputs = controller.respond_to?(:extract_rails_inputs, true)
35
+ puts "Has extract_rails_inputs method: #{has_extract_inputs}"
36
+
37
+ has_render_response = controller.respond_to?(:render_rapitapir_response, true)
38
+ puts "Has render_rapitapir_response method: #{has_render_response}"
39
+
40
+ puts "\n4ļøāƒ£ Checking registered endpoints..."
41
+ endpoints = HelloWorldController.rapitapir_endpoints
42
+ puts "Registered endpoints: #{endpoints.keys}"
43
+
44
+ endpoints.each do |action, config|
45
+ puts " #{action}: #{config[:endpoint].method} #{config[:endpoint].path}"
46
+ end
47
+
48
+ puts "\n5ļøāƒ£ Checking Rails controller methods..."
49
+ has_render = controller.respond_to?(:render)
50
+ puts "Has render method: #{has_render}"
51
+
52
+ has_request = controller.respond_to?(:request)
53
+ puts "Has request method: #{has_request}"
54
+
55
+ has_params = controller.respond_to?(:params)
56
+ puts "Has params method: #{has_params}"
57
+
58
+ puts "\nāœ… Debug complete!"
59
+
60
+ rescue => e
61
+ puts "āŒ Error: #{e.message}"
62
+ puts e.backtrace[0..5].join("\n")
63
+ end