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,510 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Real-world Rails API example demonstrating RapiTapir integration
4
+ # This simulates a blog API with users, posts, and comments
5
+
6
+ require 'bundler/inline'
7
+
8
+ gemfile do
9
+ source 'https://rubygems.org'
10
+ gem 'rails', '~> 7.0'
11
+ gem 'sqlite3'
12
+ gem 'puma'
13
+ end
14
+
15
+ require 'rails/all'
16
+ require_relative '../../lib/rapitapir'
17
+
18
+ # Simulate ActiveRecord models
19
+ class User < ActiveRecord::Base
20
+ has_many :posts, dependent: :destroy
21
+ has_many :comments, dependent: :destroy
22
+
23
+ validates :email, presence: true, uniqueness: true
24
+ validates :name, presence: true
25
+ end
26
+
27
+ class Post < ActiveRecord::Base
28
+ belongs_to :user
29
+ has_many :comments, dependent: :destroy
30
+
31
+ validates :title, presence: true
32
+ validates :content, presence: true
33
+ end
34
+
35
+ class Comment < ActiveRecord::Base
36
+ belongs_to :user
37
+ belongs_to :post
38
+
39
+ validates :content, presence: true
40
+ end
41
+
42
+ # Rails Application Setup
43
+ class BlogApiApp < Rails::Application
44
+ config.api_only = true
45
+ config.eager_load = false
46
+ config.logger = Logger.new(STDOUT)
47
+ config.log_level = :info
48
+
49
+ # Database setup
50
+ config.active_record.database_selector = { reading: :primary }
51
+ config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
52
+ config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
53
+ end
54
+
55
+ Rails.application.initialize!
56
+
57
+ # Database setup
58
+ ActiveRecord::Base.establish_connection(
59
+ adapter: 'sqlite3',
60
+ database: ':memory:'
61
+ )
62
+
63
+ # Create tables
64
+ ActiveRecord::Schema.define do
65
+ create_table :users do |t|
66
+ t.string :name, null: false
67
+ t.string :email, null: false
68
+ t.text :bio
69
+ t.timestamps
70
+ end
71
+
72
+ create_table :posts do |t|
73
+ t.references :user, null: false, foreign_key: true
74
+ t.string :title, null: false
75
+ t.text :content, null: false
76
+ t.boolean :published, default: false
77
+ t.timestamps
78
+ end
79
+
80
+ create_table :comments do |t|
81
+ t.references :user, null: false, foreign_key: true
82
+ t.references :post, null: false, foreign_key: true
83
+ t.text :content, null: false
84
+ t.timestamps
85
+ end
86
+
87
+ add_index :users, :email, unique: true
88
+ end
89
+
90
+ # Seed some data
91
+ user1 = User.create!(name: "Alice Johnson", email: "alice@example.com", bio: "Tech blogger")
92
+ user2 = User.create!(name: "Bob Smith", email: "bob@example.com", bio: "Developer")
93
+
94
+ post1 = Post.create!(
95
+ user: user1,
96
+ title: "Getting Started with RapiTapir",
97
+ content: "RapiTapir makes API development in Ruby a breeze...",
98
+ published: true
99
+ )
100
+
101
+ post2 = Post.create!(
102
+ user: user1,
103
+ title: "Advanced API Patterns",
104
+ content: "Let's explore some advanced patterns...",
105
+ published: false
106
+ )
107
+
108
+ Comment.create!(
109
+ user: user2,
110
+ post: post1,
111
+ content: "Great article! Very helpful."
112
+ )
113
+
114
+ # RapiTapir Controllers
115
+ class UsersController < RapiTapir::Server::Rails::ControllerBase
116
+ rapitapir do
117
+ development_defaults!
118
+
119
+ # User schema for responses
120
+ user_schema = T.hash(
121
+ id: T.integer,
122
+ name: T.string,
123
+ email: T.string,
124
+ bio: T.string.optional,
125
+ created_at: T.string,
126
+ updated_at: T.string
127
+ )
128
+
129
+ # User list with pagination
130
+ GET('/users')
131
+ .in(query(:page, T.integer.default(1)))
132
+ .in(query(:per_page, T.integer.default(10)))
133
+ .out(json_body(
134
+ users: T.array(user_schema),
135
+ pagination: T.hash(
136
+ page: T.integer,
137
+ per_page: T.integer,
138
+ total: T.integer,
139
+ total_pages: T.integer
140
+ )
141
+ ))
142
+ .summary("List all users")
143
+ .description("Get a paginated list of all users")
144
+
145
+ # Get specific user
146
+ GET('/users/:id')
147
+ .in(path(:id, T.integer))
148
+ .out(json_body(user: user_schema))
149
+ .error_out(json_body(error: T.string), 404)
150
+ .summary("Get user by ID")
151
+
152
+ # Create new user
153
+ POST('/users')
154
+ .in(json_body(
155
+ name: T.string,
156
+ email: T.string,
157
+ bio: T.string.optional
158
+ ))
159
+ .out(json_body(user: user_schema), 201)
160
+ .error_out(json_body(errors: T.array(T.string)), 422)
161
+ .summary("Create a new user")
162
+
163
+ # Update user
164
+ PUT('/users/:id')
165
+ .in(path(:id, T.integer))
166
+ .in(json_body(
167
+ name: T.string.optional,
168
+ email: T.string.optional,
169
+ bio: T.string.optional
170
+ ))
171
+ .out(json_body(user: user_schema))
172
+ .error_out(json_body(error: T.string), 404)
173
+ .error_out(json_body(errors: T.array(T.string)), 422)
174
+ .summary("Update user")
175
+
176
+ # Delete user
177
+ DELETE('/users/:id')
178
+ .in(path(:id, T.integer))
179
+ .out(json_body(message: T.string))
180
+ .error_out(json_body(error: T.string), 404)
181
+ .summary("Delete user")
182
+ end
183
+
184
+ def list_users
185
+ page = inputs[:page]
186
+ per_page = [inputs[:per_page], 50].min # Cap at 50
187
+
188
+ users_scope = User.all
189
+ total = users_scope.count
190
+ users = users_scope.offset((page - 1) * per_page).limit(per_page)
191
+
192
+ {
193
+ users: users.map(&method(:serialize_user)),
194
+ pagination: {
195
+ page: page,
196
+ per_page: per_page,
197
+ total: total,
198
+ total_pages: (total.to_f / per_page).ceil
199
+ }
200
+ }
201
+ end
202
+
203
+ def get_user
204
+ user = User.find_by(id: inputs[:id])
205
+ return render_error("User not found", 404) unless user
206
+
207
+ { user: serialize_user(user) }
208
+ end
209
+
210
+ def create_user
211
+ user = User.new(user_params)
212
+
213
+ if user.save
214
+ render json: { user: serialize_user(user) }, status: 201
215
+ else
216
+ render json: { errors: user.errors.full_messages }, status: 422
217
+ end
218
+ end
219
+
220
+ def update_user
221
+ user = User.find_by(id: inputs[:id])
222
+ return render_error("User not found", 404) unless user
223
+
224
+ if user.update(user_params)
225
+ { user: serialize_user(user) }
226
+ else
227
+ render json: { errors: user.errors.full_messages }, status: 422
228
+ end
229
+ end
230
+
231
+ def delete_user
232
+ user = User.find_by(id: inputs[:id])
233
+ return render_error("User not found", 404) unless user
234
+
235
+ user.destroy
236
+ { message: "User deleted successfully" }
237
+ end
238
+
239
+ private
240
+
241
+ def user_params
242
+ inputs.slice(:name, :email, :bio).compact
243
+ end
244
+
245
+ def serialize_user(user)
246
+ {
247
+ id: user.id,
248
+ name: user.name,
249
+ email: user.email,
250
+ bio: user.bio,
251
+ created_at: user.created_at.iso8601,
252
+ updated_at: user.updated_at.iso8601
253
+ }
254
+ end
255
+
256
+ def render_error(message, status)
257
+ render json: { error: message }, status: status
258
+ end
259
+ end
260
+
261
+ class PostsController < RapiTapir::Server::Rails::ControllerBase
262
+ rapitapir do
263
+ development_defaults!
264
+
265
+ # Post schema
266
+ post_schema = T.hash(
267
+ id: T.integer,
268
+ title: T.string,
269
+ content: T.string,
270
+ published: T.boolean,
271
+ user_id: T.integer,
272
+ user: T.hash(
273
+ id: T.integer,
274
+ name: T.string,
275
+ email: T.string
276
+ ),
277
+ created_at: T.string,
278
+ updated_at: T.string
279
+ )
280
+
281
+ # List posts with filtering
282
+ GET('/posts')
283
+ .in(query(:published, T.boolean.optional))
284
+ .in(query(:user_id, T.integer.optional))
285
+ .in(query(:page, T.integer.default(1)))
286
+ .out(json_body(
287
+ posts: T.array(post_schema),
288
+ pagination: T.hash(
289
+ page: T.integer,
290
+ total: T.integer,
291
+ total_pages: T.integer
292
+ )
293
+ ))
294
+ .summary("List posts")
295
+ .description("Get posts with optional filtering by published status and user")
296
+
297
+ # Get specific post
298
+ GET('/posts/:id')
299
+ .in(path(:id, T.integer))
300
+ .out(json_body(post: post_schema))
301
+ .error_out(json_body(error: T.string), 404)
302
+ .summary("Get post by ID")
303
+
304
+ # Create post
305
+ POST('/posts')
306
+ .in(json_body(
307
+ title: T.string,
308
+ content: T.string,
309
+ published: T.boolean.default(false),
310
+ user_id: T.integer
311
+ ))
312
+ .out(json_body(post: post_schema), 201)
313
+ .error_out(json_body(errors: T.array(T.string)), 422)
314
+ .summary("Create a new post")
315
+
316
+ # Update post
317
+ PUT('/posts/:id')
318
+ .in(path(:id, T.integer))
319
+ .in(json_body(
320
+ title: T.string.optional,
321
+ content: T.string.optional,
322
+ published: T.boolean.optional
323
+ ))
324
+ .out(json_body(post: post_schema))
325
+ .error_out(json_body(errors: T.array(T.string)), 422)
326
+ .summary("Update post")
327
+ end
328
+
329
+ def list_posts
330
+ posts_scope = Post.includes(:user)
331
+
332
+ # Apply filters
333
+ posts_scope = posts_scope.where(published: inputs[:published]) if inputs[:published]
334
+ posts_scope = posts_scope.where(user_id: inputs[:user_id]) if inputs[:user_id]
335
+
336
+ # Pagination
337
+ page = inputs[:page]
338
+ per_page = 10
339
+ total = posts_scope.count
340
+ posts = posts_scope.offset((page - 1) * per_page).limit(per_page)
341
+
342
+ {
343
+ posts: posts.map(&method(:serialize_post)),
344
+ pagination: {
345
+ page: page,
346
+ total: total,
347
+ total_pages: (total.to_f / per_page).ceil
348
+ }
349
+ }
350
+ end
351
+
352
+ def get_post
353
+ post = Post.includes(:user).find_by(id: inputs[:id])
354
+ return render_error("Post not found", 404) unless post
355
+
356
+ { post: serialize_post(post) }
357
+ end
358
+
359
+ def create_post
360
+ post = Post.new(post_params)
361
+
362
+ if post.save
363
+ render json: { post: serialize_post(post.reload) }, status: 201
364
+ else
365
+ render json: { errors: post.errors.full_messages }, status: 422
366
+ end
367
+ end
368
+
369
+ def update_post
370
+ post = Post.find_by(id: inputs[:id])
371
+ return render_error("Post not found", 404) unless post
372
+
373
+ if post.update(post_params.compact)
374
+ { post: serialize_post(post.reload) }
375
+ else
376
+ render json: { errors: post.errors.full_messages }, status: 422
377
+ end
378
+ end
379
+
380
+ private
381
+
382
+ def post_params
383
+ inputs.slice(:title, :content, :published, :user_id)
384
+ end
385
+
386
+ def serialize_post(post)
387
+ {
388
+ id: post.id,
389
+ title: post.title,
390
+ content: post.content,
391
+ published: post.published,
392
+ user_id: post.user_id,
393
+ user: {
394
+ id: post.user.id,
395
+ name: post.user.name,
396
+ email: post.user.email
397
+ },
398
+ created_at: post.created_at.iso8601,
399
+ updated_at: post.updated_at.iso8601
400
+ }
401
+ end
402
+
403
+ def render_error(message, status)
404
+ render json: { error: message }, status: status
405
+ end
406
+ end
407
+
408
+ # Application routing
409
+ Rails.application.routes.draw do
410
+ # Mount RapiTapir controllers
411
+ rapitapir_routes_for UsersController
412
+ rapitapir_routes_for PostsController
413
+
414
+ # Health check
415
+ get '/health', to: proc { |env| [200, {}, ['OK']] }
416
+
417
+ # API documentation
418
+ get '/docs', to: proc { |env|
419
+ [200, { 'Content-Type' => 'text/html' }, [generate_docs_html]]
420
+ }
421
+
422
+ get '/openapi.json', to: proc { |env|
423
+ [200, { 'Content-Type' => 'application/json' }, [generate_openapi_spec]]
424
+ }
425
+ end
426
+
427
+ def generate_docs_html
428
+ <<~HTML
429
+ <!DOCTYPE html>
430
+ <html>
431
+ <head>
432
+ <title>Blog API Documentation</title>
433
+ <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3.25.0/swagger-ui.css" />
434
+ </head>
435
+ <body>
436
+ <div id="swagger-ui"></div>
437
+ <script src="https://unpkg.com/swagger-ui-dist@3.25.0/swagger-ui-bundle.js"></script>
438
+ <script>
439
+ SwaggerUIBundle({
440
+ url: '/openapi.json',
441
+ dom_id: '#swagger-ui',
442
+ presets: [
443
+ SwaggerUIBundle.presets.apis,
444
+ SwaggerUIBundle.presets.standalone
445
+ ]
446
+ });
447
+ </script>
448
+ </body>
449
+ </html>
450
+ HTML
451
+ end
452
+
453
+ def generate_openapi_spec
454
+ require 'json'
455
+
456
+ spec = {
457
+ openapi: "3.0.0",
458
+ info: {
459
+ title: "Blog API",
460
+ version: "1.0.0",
461
+ description: "A real-world blog API built with RapiTapir and Rails"
462
+ },
463
+ servers: [
464
+ { url: "http://localhost:3000", description: "Development server" }
465
+ ],
466
+ paths: {}
467
+ }
468
+
469
+ # Add endpoints from controllers
470
+ [UsersController, PostsController].each do |controller|
471
+ controller.endpoints.each do |endpoint|
472
+ path_key = endpoint.path.gsub(/:(\w+)/, '{\1}')
473
+ method = endpoint.method.downcase
474
+
475
+ spec[:paths][path_key] ||= {}
476
+ spec[:paths][path_key][method] = {
477
+ summary: endpoint.summary || "#{method.upcase} #{path_key}",
478
+ description: endpoint.description || "",
479
+ responses: {
480
+ "200" => { description: "Success" }
481
+ }
482
+ }
483
+ end
484
+ end
485
+
486
+ JSON.pretty_generate(spec)
487
+ end
488
+
489
+ # Start the server
490
+ if __FILE__ == $0
491
+ puts "🚀 Starting Blog API server on http://localhost:3000"
492
+ puts "📚 API Documentation: http://localhost:3000/docs"
493
+ puts "📋 OpenAPI Spec: http://localhost:3000/openapi.json"
494
+ puts ""
495
+ puts "Sample endpoints:"
496
+ puts " GET /users - List users"
497
+ puts " POST /users - Create user"
498
+ puts " GET /users/1 - Get user"
499
+ puts " PUT /users/1 - Update user"
500
+ puts " DELETE /users/1 - Delete user"
501
+ puts " GET /posts - List posts"
502
+ puts " GET /posts?published=true&user_id=1"
503
+ puts " POST /posts - Create post"
504
+ puts " GET /posts/1 - Get post"
505
+ puts " PUT /posts/1 - Update post"
506
+ puts ""
507
+
508
+ require 'rack'
509
+ Rack::Handler::WEBrick.run(Rails.application, Port: 3000)
510
+ end
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test server startup without actually running it
5
+
6
+ puts "🧪 Testing Rails Server Startup..."
7
+ puts "=" * 40
8
+
9
+ begin
10
+ puts "\n1. Loading Rails app..."
11
+ require_relative 'hello_world_app'
12
+ puts "✅ App loaded successfully"
13
+
14
+ puts "\n2. Testing Rack compatibility..."
15
+ require 'rack'
16
+ puts "✅ Rack loaded: #{Rack::VERSION}"
17
+
18
+ puts "\n3. Creating Rack::Server instance..."
19
+ server = Rack::Server.new(
20
+ app: HelloWorldRailsApp,
21
+ Port: 9292,
22
+ Host: 'localhost',
23
+ environment: 'development'
24
+ )
25
+ puts "✅ Rack::Server created: #{server.class}"
26
+
27
+ puts "\n4. Testing app call method..."
28
+ env = {
29
+ 'REQUEST_METHOD' => 'GET',
30
+ 'PATH_INFO' => '/hello',
31
+ 'QUERY_STRING' => 'name=Test',
32
+ 'HTTP_HOST' => 'localhost:9292',
33
+ 'rack.url_scheme' => 'http'
34
+ }
35
+
36
+ response = HelloWorldRailsApp.call(env)
37
+ puts "✅ App responds to requests: status=#{response[0]}"
38
+
39
+ puts "\n🎉 Rails server setup appears to be working!"
40
+ puts "\n🚀 To start the server manually:"
41
+ puts " ruby examples/rails/hello_world_app.rb"
42
+
43
+ rescue => e
44
+ puts "❌ Error: #{e.message}"
45
+ puts e.backtrace[0..3].join("\n")
46
+ end
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test endpoint processing directly
5
+
6
+ puts "🧪 Testing Direct Endpoint Processing..."
7
+ puts "=" * 42
8
+
9
+ begin
10
+ require_relative 'hello_world_app'
11
+
12
+ # Create a controller instance
13
+ controller = HelloWorldController.new
14
+
15
+ # Mock Rails request environment
16
+ env = {
17
+ 'REQUEST_METHOD' => 'GET',
18
+ 'PATH_INFO' => '/hello',
19
+ 'QUERY_STRING' => 'name=Test',
20
+ 'rack.input' => StringIO.new(''),
21
+ 'rack.errors' => $stderr
22
+ }
23
+
24
+ # Create a mock request object
25
+ require 'stringio'
26
+ request = ActionDispatch::Request.new(env)
27
+
28
+ # Allow the controller to access request
29
+ controller.instance_variable_set(:@_request, request)
30
+
31
+ puts "\n1️⃣ Testing process_rapitapir_endpoint for :get_hello..."
32
+
33
+ # Call process_rapitapir_endpoint with the specific action
34
+ result = controller.send(:process_rapitapir_endpoint, :get_hello)
35
+ puts "✅ Success: #{result}"
36
+
37
+ rescue => e
38
+ puts "❌ Error during processing: #{e.message}"
39
+ puts "Backtrace:"
40
+ puts e.backtrace[0..10].join("\n")
41
+ end
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test script for Hello World Rails RapiTapir example
5
+
6
+ require_relative 'hello_world_app'
7
+
8
+ puts "\n🧪 Testing Hello World Rails RapiTapir Controller"
9
+ puts "=" * 50
10
+
11
+ # Test 1: Basic functionality
12
+ puts "\n1. Testing controller class inheritance..."
13
+ begin
14
+ controller = HelloWorldController.new
15
+ puts "✅ HelloWorldController inherits from ControllerBase"
16
+ rescue => e
17
+ puts "❌ Error: #{e.message}"
18
+ end
19
+
20
+ # Test 2: T shortcut availability
21
+ puts "\n2. Testing T shortcut availability..."
22
+ begin
23
+ string_type = HelloWorldController::T.string
24
+ puts "✅ T shortcut works: #{string_type.class}"
25
+ rescue => e
26
+ puts "❌ Error: #{e.message}"
27
+ end
28
+
29
+ # Test 3: HTTP verb methods
30
+ puts "\n3. Testing HTTP verb methods..."
31
+ begin
32
+ get_builder = HelloWorldController.GET('/test')
33
+ puts "✅ GET method works: #{get_builder.class}"
34
+ rescue => e
35
+ puts "❌ Error: #{e.message}"
36
+ end
37
+
38
+ # Test 4: Route generation
39
+ puts "\n4. Testing route generation..."
40
+ begin
41
+ router = Object.new
42
+ router.extend(RapiTapir::Server::Rails::Routes)
43
+
44
+ # Mock route methods
45
+ def router.get(path, options = {}); puts " Generated: GET #{path} => #{options[:to]}"; end
46
+ def router.post(path, options = {}); puts " Generated: POST #{path} => #{options[:to]}"; end
47
+
48
+ puts " Generating routes for HelloWorldController:"
49
+
50
+ # Check if controller has endpoints
51
+ if HelloWorldController.respond_to?(:rapitapir_endpoints)
52
+ router.rapitapir_routes_for(HelloWorldController)
53
+ puts "✅ Route generation works"
54
+ else
55
+ puts "❌ Controller doesn't have rapitapir_endpoints method"
56
+ end
57
+ rescue => e
58
+ puts "❌ Error: #{e.message}"
59
+ end
60
+
61
+ # Test 5: Schema validation
62
+ puts "\n5. Testing schema definitions..."
63
+ begin
64
+ # Test the schema types used in the controller
65
+ schema = HelloWorldController::T.hash({
66
+ 'message' => HelloWorldController::T.string,
67
+ 'timestamp' => HelloWorldController::T.string
68
+ })
69
+ puts "✅ Schema definition works: #{schema.class}"
70
+ rescue => e
71
+ puts "❌ Error: #{e.message}"
72
+ end
73
+
74
+ puts "\n🎉 Rails Hello World Controller Tests Complete!"
75
+ puts "\n🚀 To run the server:"
76
+ puts " ruby examples/rails/hello_world_app.rb"
77
+ puts "\n📖 To test endpoints manually:"
78
+ puts " curl http://localhost:9292/hello?name=Developer"
79
+ puts " curl http://localhost:9292/greet/spanish"
80
+ puts " curl -X POST http://localhost:9292/greetings -H 'Content-Type: application/json' -d '{\"name\":\"Rails\"}'"