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,46 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Detailed Rails integration test
5
+
6
+ puts "🧪 Testing Rails Integration in Detail..."
7
+ puts "=" * 50
8
+
9
+ begin
10
+ puts "\n1. Loading Rails app..."
11
+ require_relative 'hello_world_app'
12
+ puts "✅ App loaded: #{HelloWorldRailsApp.class}"
13
+
14
+ puts "\n2. Checking controller endpoints..."
15
+ if HelloWorldController.respond_to?(:rapitapir_endpoints)
16
+ endpoints = HelloWorldController.rapitapir_endpoints
17
+ puts "✅ Found #{endpoints.count} endpoints:"
18
+ endpoints.each do |action, config|
19
+ endpoint = config[:endpoint]
20
+ puts " #{endpoint.method} #{endpoint.path} => #{action}"
21
+ end
22
+ else
23
+ puts "❌ No rapitapir_endpoints found"
24
+ end
25
+
26
+ puts "\n3. Checking routes..."
27
+ routes = HelloWorldRailsApp.routes.routes
28
+ puts "✅ Found #{routes.count} routes:"
29
+ routes.each do |route|
30
+ puts " #{route.verb} #{route.path.spec} => #{route.defaults[:controller]}##{route.defaults[:action]}"
31
+ end
32
+
33
+ puts "\n4. Testing controller instantiation..."
34
+ controller = HelloWorldController.new
35
+ puts "✅ Controller instantiated: #{controller.class}"
36
+
37
+ puts "\n5. Testing Rails engine..."
38
+ app = HelloWorldRailsApp
39
+ puts "✅ Rails app ready: #{app}"
40
+
41
+ puts "\n🎉 Rails integration appears to be working!"
42
+
43
+ rescue => e
44
+ puts "❌ Error: #{e.message}"
45
+ puts e.backtrace[0..5].join("\n")
46
+ end
@@ -0,0 +1,278 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Enhanced Rails controller example using RapiTapir's new base class
4
+ # This demonstrates the improved developer experience that matches Sinatra's elegance
5
+ #
6
+ # Usage in a Rails app:
7
+ # 1. Add this file to app/controllers/
8
+ # 2. Add routes using the automatic route generator
9
+ # 3. That's it! No boilerplate needed.
10
+
11
+ require_relative '../../lib/rapitapir'
12
+ require_relative '../../lib/rapitapir/server/rails/controller_base'
13
+
14
+ class EnhancedUsersController < RapiTapir::Server::Rails::ControllerBase
15
+ # Configure RapiTapir - same clean syntax as Sinatra
16
+ rapitapir do
17
+ info(
18
+ title: 'Enhanced Users API',
19
+ description: 'A clean, type-safe user management API built with RapiTapir for Rails',
20
+ version: '1.0.0'
21
+ )
22
+ # Future: development_defaults! will auto-setup CORS, docs, health checks
23
+ end
24
+
25
+ # Define schema using T shortcut (automatically available!)
26
+ USER_SCHEMA = T.hash({
27
+ "id" => T.integer,
28
+ "name" => T.string(min_length: 1, max_length: 100),
29
+ "email" => T.email,
30
+ "active" => T.boolean,
31
+ "created_at" => T.datetime,
32
+ "updated_at" => T.datetime
33
+ })
34
+
35
+ USER_CREATE_SCHEMA = T.hash({
36
+ "name" => T.string(min_length: 1, max_length: 100),
37
+ "email" => T.email,
38
+ "active" => T.optional(T.boolean)
39
+ })
40
+
41
+ USER_UPDATE_SCHEMA = T.hash({
42
+ "name" => T.optional(T.string(min_length: 1, max_length: 100)),
43
+ "email" => T.optional(T.email),
44
+ "active" => T.optional(T.boolean)
45
+ })
46
+
47
+ before_action :setup_users_data
48
+
49
+ # Option 1: Use the enhanced api_resource DSL (recommended for CRUD)
50
+ api_resource '/users', schema: USER_SCHEMA do
51
+ crud do
52
+ index do
53
+ # Access Rails helpers and instance variables naturally
54
+ users = @users.values
55
+
56
+ # Apply optional filtering (Rails-style)
57
+ users = users.select { |u| u[:active] } if params[:active] == 'true'
58
+
59
+ # Simple pagination
60
+ limit = params[:limit]&.to_i || 20
61
+ offset = params[:offset]&.to_i || 0
62
+
63
+ users[offset, limit] || []
64
+ end
65
+
66
+ show do |inputs|
67
+ user = @users[inputs[:id]]
68
+
69
+ # Rails-style error handling
70
+ if user.nil?
71
+ render json: { error: 'User not found' }, status: :not_found
72
+ return
73
+ end
74
+
75
+ user
76
+ end
77
+
78
+ create do |inputs|
79
+ new_id = (@users.keys.max || 0) + 1
80
+
81
+ new_user = {
82
+ id: new_id,
83
+ name: inputs[:body]['name'],
84
+ email: inputs[:body]['email'],
85
+ active: inputs[:body]['active'] || true,
86
+ created_at: Time.now.iso8601,
87
+ updated_at: Time.now.iso8601
88
+ }
89
+
90
+ @users[new_id] = new_user
91
+
92
+ # Rails-style status setting
93
+ response.status = 201
94
+ new_user
95
+ end
96
+
97
+ update do |inputs|
98
+ user = @users[inputs[:id]]
99
+
100
+ if user.nil?
101
+ render json: { error: 'User not found' }, status: :not_found
102
+ return
103
+ end
104
+
105
+ # Update fields
106
+ update_data = inputs[:body]
107
+ user[:name] = update_data['name'] if update_data['name']
108
+ user[:email] = update_data['email'] if update_data['email']
109
+ user[:active] = update_data['active'] if update_data.key?('active')
110
+ user[:updated_at] = Time.now.iso8601
111
+
112
+ @users[inputs[:id]] = user
113
+ user
114
+ end
115
+
116
+ destroy do |inputs|
117
+ user = @users[inputs[:id]]
118
+
119
+ if user.nil?
120
+ render json: { error: 'User not found' }, status: :not_found
121
+ return
122
+ end
123
+
124
+ @users.delete(inputs[:id])
125
+
126
+ # Rails-style head response
127
+ head :no_content
128
+ end
129
+ end
130
+ end
131
+
132
+ # Option 2: Individual endpoint definitions with enhanced HTTP verb DSL
133
+ endpoint(
134
+ GET('/users/active')
135
+ .summary('Get active users')
136
+ .description('Retrieve a list of all active users in the system')
137
+ .tags('Users')
138
+ .query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)), description: 'Maximum number of results')
139
+ .ok(T.array(USER_SCHEMA))
140
+ .build
141
+ ) do |inputs|
142
+ active_users = @users.values.select { |u| u[:active] }
143
+ limit = inputs[:limit] || 20
144
+ active_users.first(limit)
145
+ end
146
+
147
+ endpoint(
148
+ POST('/users/bulk')
149
+ .summary('Create multiple users')
150
+ .description('Create multiple users in a single request')
151
+ .tags('Users')
152
+ .json_body(T.array(USER_CREATE_SCHEMA))
153
+ .created(T.array(USER_SCHEMA))
154
+ .bad_request(T.hash({
155
+ "error" => T.string,
156
+ "failed_users" => T.array(T.hash({
157
+ "index" => T.integer,
158
+ "errors" => T.array(T.string)
159
+ }))
160
+ }))
161
+ .build
162
+ ) do |inputs|
163
+ created_users = []
164
+ failed_users = []
165
+
166
+ inputs[:body].each_with_index do |user_data, index|
167
+ # Simple validation
168
+ errors = []
169
+ errors << 'Name is required' if user_data['name'].nil? || user_data['name'].empty?
170
+ errors << 'Email is required' if user_data['email'].nil? || user_data['email'].empty?
171
+ errors << 'Email already exists' if @users.values.any? { |u| u[:email] == user_data['email'] }
172
+
173
+ if errors.any?
174
+ failed_users << { index: index, errors: errors }
175
+ next
176
+ end
177
+
178
+ new_id = (@users.keys.max || 0) + 1
179
+ new_user = {
180
+ id: new_id,
181
+ name: user_data['name'],
182
+ email: user_data['email'],
183
+ active: user_data['active'] || true,
184
+ created_at: Time.now.iso8601,
185
+ updated_at: Time.now.iso8601
186
+ }
187
+
188
+ @users[new_id] = new_user
189
+ created_users << new_user
190
+ end
191
+
192
+ if failed_users.any?
193
+ render json: {
194
+ error: 'Some users could not be created',
195
+ failed_users: failed_users
196
+ }, status: :bad_request
197
+ return
198
+ end
199
+
200
+ response.status = 201
201
+ created_users
202
+ end
203
+
204
+ endpoint(
205
+ GET('/users/search')
206
+ .summary('Search users')
207
+ .description('Search users by name or email')
208
+ .tags('Users', 'Search')
209
+ .query(:q, T.string(min_length: 1), description: 'Search query')
210
+ .query(:fields, T.optional(T.array(T.string(enum: %w[name email]))), description: 'Fields to search in')
211
+ .query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)), description: 'Maximum number of results')
212
+ .ok(T.array(USER_SCHEMA))
213
+ .bad_request(T.hash({ "error" => T.string }))
214
+ .build
215
+ ) do |inputs|
216
+ query = inputs[:q].downcase
217
+ fields = inputs[:fields] || %w[name email]
218
+ limit = inputs[:limit] || 20
219
+
220
+ results = @users.values.select do |user|
221
+ fields.any? do |field|
222
+ user[field.to_sym]&.downcase&.include?(query)
223
+ end
224
+ end
225
+
226
+ results.first(limit)
227
+ end
228
+
229
+ private
230
+
231
+ def setup_users_data
232
+ @users = {
233
+ 1 => {
234
+ id: 1,
235
+ name: 'John Doe',
236
+ email: 'john@example.com',
237
+ active: true,
238
+ created_at: 1.week.ago.iso8601,
239
+ updated_at: 1.week.ago.iso8601
240
+ },
241
+ 2 => {
242
+ id: 2,
243
+ name: 'Jane Smith',
244
+ email: 'jane@example.com',
245
+ active: true,
246
+ created_at: 3.days.ago.iso8601,
247
+ updated_at: 3.days.ago.iso8601
248
+ },
249
+ 3 => {
250
+ id: 3,
251
+ name: 'Bob Wilson',
252
+ email: 'bob@example.com',
253
+ active: false,
254
+ created_at: 1.day.ago.iso8601,
255
+ updated_at: 1.day.ago.iso8601
256
+ }
257
+ }
258
+ end
259
+ end
260
+
261
+ # Example routes configuration for config/routes.rb:
262
+ #
263
+ # Rails.application.routes.draw do
264
+ # # Option 1: Automatic route generation for specific controller
265
+ # rapitapir_routes_for EnhancedUsersController
266
+ #
267
+ # # Option 2: Auto-discover all RapiTapir controllers
268
+ # rapitapir_auto_routes
269
+ #
270
+ # # Option 3: Manual routes (still works)
271
+ # resources :enhanced_users, only: [:index, :show, :create, :update, :destroy] do
272
+ # collection do
273
+ # get :active
274
+ # post :bulk
275
+ # get :search
276
+ # end
277
+ # end
278
+ # end
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test the Rails server startup
5
+
6
+ puts "🧪 Testing Rails Server Startup (Final)..."
7
+ puts "=" * 45
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 direct app call..."
15
+ env = {
16
+ 'REQUEST_METHOD' => 'GET',
17
+ 'PATH_INFO' => '/hello',
18
+ 'QUERY_STRING' => 'name=Test',
19
+ 'HTTP_HOST' => 'localhost:9292',
20
+ 'rack.url_scheme' => 'http',
21
+ 'rack.input' => StringIO.new(''),
22
+ 'rack.errors' => $stderr,
23
+ 'rack.version' => [1, 3],
24
+ 'rack.multithread' => true,
25
+ 'rack.multiprocess' => false,
26
+ 'rack.run_once' => false
27
+ }
28
+
29
+ require 'stringio'
30
+ env['rack.input'] = StringIO.new('')
31
+
32
+ status, headers, body = HelloWorldRailsApp.call(env)
33
+ puts "✅ App responds: status=#{status}"
34
+
35
+ puts "\n3. Testing WEBrick server creation..."
36
+ require 'webrick'
37
+ server = WEBrick::HTTPServer.new(
38
+ Port: 9293, # Different port for testing
39
+ Host: 'localhost'
40
+ )
41
+ puts "✅ WEBrick server created"
42
+
43
+ puts "\n🎉 Rails server setup is ready!"
44
+ puts "\n🚀 The server should start successfully."
45
+ puts "📝 Run: ruby examples/rails/hello_world_app.rb"
46
+
47
+ rescue => e
48
+ puts "❌ Error: #{e.message}"
49
+ puts e.backtrace[0..3].join("\n")
50
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Minimal Rails Application for Hello World RapiTapir Demo
4
+ # This creates a minimal Rails app to demonstrate the Hello World controller
5
+
6
+ require 'rails'
7
+ require 'action_controller/railtie'
8
+ require 'json'
9
+
10
+ # Minimal Rails application
11
+ class HelloWorldRailsApp < Rails::Application
12
+ config.load_defaults Rails::VERSION::STRING.to_f
13
+ config.api_only = true
14
+ config.eager_load = false
15
+ config.cache_classes = false
16
+ config.secret_key_base = 'dummy_secret_for_demo'
17
+
18
+ # Disable unnecessary middleware for demo
19
+ config.middleware.delete ActionDispatch::Cookies
20
+ config.middleware.delete ActionDispatch::Session::CookieStore
21
+ config.middleware.delete ActionDispatch::Flash
22
+
23
+ # CORS for development (optional)
24
+ config.middleware.insert_before 0, Rack::Cors do
25
+ allow do
26
+ origins '*'
27
+ resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head]
28
+ end
29
+ end if defined?(Rack::Cors)
30
+ end
31
+
32
+ # Initialize the Rails application
33
+ HelloWorldRailsApp.initialize!
34
+
35
+ # Load our controller
36
+ require_relative 'hello_world_controller'
37
+
38
+ # Load routes helper and include it in Rails routes
39
+ require_relative '../../lib/rapitapir/server/rails/routes'
40
+ require_relative '../../lib/rapitapir/server/rails/documentation_helpers'
41
+ ActionDispatch::Routing::Mapper.include(RapiTapir::Server::Rails::Routes)
42
+ ActionDispatch::Routing::Mapper.include(RapiTapir::Server::Rails::DocumentationHelpers)
43
+
44
+ # Define routes
45
+ HelloWorldRailsApp.routes.draw do
46
+ # Auto-generate routes from RapiTapir endpoints
47
+ rapitapir_routes_for HelloWorldController
48
+
49
+ # Documentation endpoints
50
+ add_documentation_routes(
51
+ HelloWorldController,
52
+ {
53
+ docs_path: '/docs',
54
+ openapi_path: '/openapi.json',
55
+ info: {
56
+ title: 'Hello World Rails API',
57
+ version: '1.0.0',
58
+ description: 'A beautiful Rails API built with RapiTapir - same elegant syntax as Sinatra!'
59
+ },
60
+ servers: [
61
+ {
62
+ url: 'http://localhost:9292',
63
+ description: 'Development server (Rails + RapiTapir)'
64
+ }
65
+ ]
66
+ }
67
+ )
68
+
69
+ # Welcome page
70
+ root 'hello_world#welcome'
71
+
72
+ # Manual health check (for demo purposes)
73
+ get '/manual-health', to: proc { |env|
74
+ [200, {'Content-Type' => 'application/json'},
75
+ [{ status: 'healthy', message: 'Manual route working!' }.to_json]]
76
+ }
77
+ end
78
+
79
+ # If running this file directly
80
+ if __FILE__ == $PROGRAM_NAME
81
+ require 'rack'
82
+
83
+ puts "\n🌟 Hello World Rails API with RapiTapir"
84
+ puts "🚀 Clean syntax: class HelloWorldController < RapiTapir::Server::Rails::ControllerBase"
85
+ puts "🔗 Enhanced Rails integration with Sinatra-like elegance"
86
+ puts ""
87
+ puts "📋 Available endpoints:"
88
+ puts " GET / - Welcome message"
89
+ puts " GET /hello?name=YourName - Personalized greeting"
90
+ puts " GET /greet/:language - Multilingual greetings"
91
+ puts " POST /greetings - Create custom greeting"
92
+ puts " GET /health - Health check"
93
+ puts " GET /docs - 📖 Interactive API Documentation (Swagger UI)"
94
+ puts " GET /openapi.json - 🔧 OpenAPI 3.0 Specification"
95
+ puts ""
96
+ puts "🌐 Starting server on http://localhost:9292"
97
+ puts "❤️ Try: curl http://localhost:9292/hello?name=Developer"
98
+ puts "🌍 Try: curl http://localhost:9292/greet/spanish"
99
+ puts ""
100
+ puts "✨ Beautiful, type-safe Rails API with RapiTapir integration!"
101
+ puts ""
102
+ puts "🌐 Starting server on http://localhost:9292"
103
+ puts "📖 Try these endpoints:"
104
+ puts " curl 'http://localhost:9292/hello?name=Developer'"
105
+ puts " curl 'http://localhost:9292/greet/spanish'"
106
+ puts " curl -X POST 'http://localhost:9292/greetings' -H 'Content-Type: application/json' -d '{\"name\":\"Rails\"}'"
107
+ puts " open http://localhost:9292/docs # 📖 Interactive API Documentation!"
108
+ puts ""
109
+
110
+ # Start server using Rackup (modern Rack)
111
+ require 'rackup/handler/webrick'
112
+
113
+ puts "🚀 Starting server with Rackup..."
114
+
115
+ Rackup::Handler::WEBrick.run(HelloWorldRailsApp, Port: 9292, Host: 'localhost')
116
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RapiTapir Rails Integration - Hello World Example
4
+ #
5
+ # The most minimal example showing how to create a beautiful, type-safe API
6
+ # with automatic OpenAPI documentation using Rails and the enhanced RapiTapir integration!
7
+
8
+ require_relative '../../lib/rapitapir'
9
+ require_relative '../../lib/rapitapir/server/rails/controller_base'
10
+
11
+ # Your entire Rails API controller in under 20 lines! 🚀
12
+ class HelloWorldController < RapiTapir::Server::Rails::ControllerBase
13
+
14
+ # One-line API configuration - same as Sinatra!
15
+ rapitapir do
16
+ info(title: 'Hello World Rails API', version: '1.0.0')
17
+ development_defaults! # Enable docs, health checks, CORS, etc.
18
+ end
19
+
20
+ # Hello World endpoint - beautifully typed and documented using enhanced DSL
21
+ endpoint(
22
+ GET('/hello')
23
+ .query(:name, T.optional(T.string), description: 'Name to greet')
24
+ .summary('Say hello to someone')
25
+ .description('Returns a personalized greeting with Rails magic')
26
+ .tags('Greetings')
27
+ .ok(T.hash({
28
+ 'message' => T.string,
29
+ 'timestamp' => T.string,
30
+ 'framework' => T.string
31
+ }))
32
+ .build
33
+ ) do |inputs|
34
+ name = inputs[:name] || 'Rails Developer'
35
+
36
+ # Return data - let RapiTapir handle the rendering
37
+ {
38
+ message: "Hello, #{name}! Welcome to RapiTapir with Rails!",
39
+ timestamp: Time.now.iso8601,
40
+ framework: 'Rails + RapiTapir'
41
+ }
42
+ end
43
+
44
+ # Another endpoint showing path parameters with enhanced DSL
45
+ endpoint(
46
+ GET('/greet/:language')
47
+ .path_param(:language, T.string, description: 'Language for greeting')
48
+ .summary('Multilingual greeting')
49
+ .description('Get a greeting in different languages using Rails')
50
+ .tags('Greetings', 'i18n')
51
+ .ok(T.hash({
52
+ 'greeting' => T.string,
53
+ 'language' => T.string,
54
+ 'powered_by' => T.string
55
+ }))
56
+ .not_found(T.hash({ 'error' => T.string }))
57
+ .build
58
+ ) do |inputs|
59
+ greetings = {
60
+ 'english' => 'Hello!',
61
+ 'spanish' => '¡Hola!',
62
+ 'french' => 'Bonjour!',
63
+ 'italian' => 'Ciao!',
64
+ 'german' => 'Hallo!',
65
+ 'japanese' => 'こんにちは!',
66
+ 'portuguese' => 'Olá!',
67
+ 'russian' => 'Привет!'
68
+ }
69
+
70
+ language = inputs[:language].downcase
71
+ greeting = greetings[language]
72
+
73
+ if greeting.nil?
74
+ # Return error response structure
75
+ { error: "Language '#{inputs[:language]}' not supported", _status: 404 }
76
+ else
77
+ # Return success response
78
+ {
79
+ greeting: greeting,
80
+ language: language.capitalize,
81
+ powered_by: 'RapiTapir + Rails'
82
+ }
83
+ end
84
+ end
85
+
86
+ # Demonstrate Rails-style POST endpoint with JSON body
87
+ endpoint(
88
+ POST('/greetings')
89
+ .summary('Create a custom greeting')
90
+ .description('Create a personalized greeting message')
91
+ .tags('Greetings')
92
+ .json_body(T.hash({
93
+ 'name' => T.string(min_length: 1),
94
+ 'greeting_style' => T.optional(T.string(enum: %w[formal casual friendly professional]))
95
+ }))
96
+ .created(T.hash({
97
+ 'id' => T.integer,
98
+ 'message' => T.string,
99
+ 'style' => T.string,
100
+ 'created_at' => T.string
101
+ }))
102
+ .bad_request(T.hash({
103
+ 'error' => T.string,
104
+ 'details' => T.array(T.string)
105
+ }))
106
+ .build
107
+ ) do |inputs|
108
+ body = inputs[:body]
109
+ name = body['name']
110
+ style = body['greeting_style'] || 'friendly'
111
+
112
+ # Validation
113
+ errors = []
114
+ errors << 'Name cannot be empty' if name.nil? || name.strip.empty?
115
+
116
+ if errors.any?
117
+ # Return validation error
118
+ { error: 'Validation failed', details: errors, _status: 400 }
119
+ else
120
+ # Style-based greetings
121
+ greetings = {
122
+ 'formal' => "Good day, #{name}. It is a pleasure to make your acquaintance.",
123
+ 'casual' => "Hey #{name}! What's up?",
124
+ 'friendly' => "Hello there, #{name}! Hope you're having a great day!",
125
+ 'professional' => "Hello #{name}, welcome to our platform."
126
+ }
127
+
128
+ # Simulate database save with incremental ID
129
+ greeting_id = rand(1000..9999)
130
+
131
+ # Return created response
132
+ {
133
+ id: greeting_id,
134
+ message: greetings[style],
135
+ style: style,
136
+ created_at: Time.now.iso8601,
137
+ _status: 201
138
+ }
139
+ end
140
+ end
141
+
142
+ # Health check endpoint (will be auto-generated in future versions)
143
+ endpoint(
144
+ GET('/health')
145
+ .summary('Health check')
146
+ .description('Check if the Rails API is running')
147
+ .tags('System')
148
+ .ok(T.hash({
149
+ 'status' => T.string,
150
+ 'timestamp' => T.string,
151
+ 'framework' => T.string,
152
+ 'ruby_version' => T.string
153
+ }))
154
+ .build
155
+ ) do |inputs|
156
+ # Return health status data
157
+ {
158
+ status: 'healthy',
159
+ timestamp: Time.now.iso8601,
160
+ framework: "Rails #{Rails.version rescue 'Unknown'} + RapiTapir",
161
+ ruby_version: RUBY_VERSION
162
+ }
163
+ end
164
+
165
+ # Custom action demonstrating Rails conventions
166
+ def welcome
167
+ render json: {
168
+ message: 'Welcome to RapiTapir with Rails!',
169
+ documentation: 'Visit /docs for interactive API documentation',
170
+ examples: {
171
+ hello: '/hello?name=YourName',
172
+ greet: '/greet/spanish',
173
+ health: '/health'
174
+ }
175
+ }
176
+ end
177
+
178
+ private
179
+
180
+ # Rails-style before_action can still be used
181
+ # before_action :log_request, only: [:hello, :greet]
182
+
183
+ def log_request
184
+ Rails.logger.info "RapiTapir request: #{request.method} #{request.path}"
185
+ end
186
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example routes configuration for Hello World Rails API
4
+ # Place this in your config/routes.rb file
5
+
6
+ Rails.application.routes.draw do
7
+ # Option 1: Auto-generate routes from RapiTapir endpoints (Recommended)
8
+ rapitapir_routes_for HelloWorldController
9
+
10
+ # This will automatically generate:
11
+ # GET /hello hello_world#hello
12
+ # GET /greet/:language hello_world#greet
13
+ # POST /greetings hello_world#greetings
14
+ # GET /health hello_world#health
15
+
16
+ # Option 2: Manual routes (if you prefer explicit control)
17
+ # get '/hello', to: 'hello_world#hello'
18
+ # get '/greet/:language', to: 'hello_world#greet'
19
+ # post '/greetings', to: 'hello_world#create'
20
+ # get '/health', to: 'hello_world#health'
21
+
22
+ # Welcome page (custom action not defined by RapiTapir)
23
+ root 'hello_world#welcome'
24
+
25
+ # Future: Auto-generated documentation endpoints
26
+ # get '/docs', to: 'rapitapir_docs#index'
27
+ # get '/openapi.json', to: 'rapitapir_docs#openapi'
28
+ end