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,406 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Minimal runnable Rails application demonstrating RapiTapir integration
4
+ # This shows the traditional Rails app structure in a single file for easy testing
5
+
6
+ require 'bundler/inline'
7
+
8
+ gemfile do
9
+ source 'https://rubygems.org'
10
+ gem 'rails', '~> 8.0'
11
+ gem 'sqlite3'
12
+ gem 'puma'
13
+ end
14
+
15
+ require 'rails/all'
16
+ require_relative '../../../lib/rapitapir'
17
+
18
+ # Simulate ActiveRecord models for the demo
19
+ class User < ActiveRecord::Base
20
+ validates :email, presence: true, uniqueness: true
21
+ validates :name, presence: true
22
+ end
23
+
24
+ class Post < ActiveRecord::Base
25
+ belongs_to :user
26
+ validates :title, presence: true
27
+ validates :content, presence: true
28
+ end
29
+
30
+ # Rails Application Setup
31
+ class TraditionalApp < Rails::Application
32
+ config.load_defaults 8.0
33
+ config.api_only = true
34
+ config.eager_load = false
35
+ config.logger = Logger.new(STDOUT)
36
+ config.log_level = :info
37
+ config.secret_key_base = 'demo_secret_key_base_for_traditional_app_example'
38
+
39
+ # Rails 8 specific configurations
40
+ config.autoload_lib(ignore: %w[assets tasks])
41
+ end
42
+
43
+ Rails.application.initialize!
44
+
45
+ # Database setup
46
+ ActiveRecord::Base.establish_connection(
47
+ adapter: 'sqlite3',
48
+ database: ':memory:'
49
+ )
50
+
51
+ # Create tables
52
+ ActiveRecord::Schema.define do
53
+ create_table :users do |t|
54
+ t.string :name, null: false
55
+ t.string :email, null: false
56
+ t.text :bio
57
+ t.timestamps
58
+ end
59
+
60
+ create_table :posts do |t|
61
+ t.references :user, null: false, foreign_key: true
62
+ t.string :title, null: false
63
+ t.text :content, null: false
64
+ t.boolean :published, default: false
65
+ t.timestamps
66
+ end
67
+
68
+ add_index :users, :email, unique: true
69
+ end
70
+
71
+ # Seed some data
72
+ user1 = User.create!(name: "Alice Johnson", email: "alice@example.com", bio: "Tech blogger")
73
+ user2 = User.create!(name: "Bob Smith", email: "bob@example.com", bio: "Developer")
74
+
75
+ Post.create!(
76
+ user: user1,
77
+ title: "Getting Started with RapiTapir",
78
+ content: "RapiTapir makes API development in Ruby a breeze...",
79
+ published: true
80
+ )
81
+
82
+ Post.create!(
83
+ user: user2,
84
+ title: "Rails + RapiTapir Best Practices",
85
+ content: "Here are some patterns I've learned...",
86
+ published: true
87
+ )
88
+
89
+ # ApplicationController - Base controller with health check
90
+ class ApplicationController < RapiTapir::Server::Rails::ControllerBase
91
+ # Global configuration for all controllers
92
+ rapitapir do
93
+ # Enable development features (automatic docs, etc.)
94
+ development_defaults!
95
+
96
+ # Global error handling
97
+ error_out(json_body(error: T.string, details: T.string.optional), 500)
98
+ error_out(json_body(error: T.string), 401)
99
+ error_out(json_body(error: T.string), 403)
100
+ error_out(json_body(error: T.string), 404)
101
+ error_out(json_body(error: T.string, errors: T.array(T.string).optional), 422)
102
+
103
+ # Health check endpoint - no separate controller needed!
104
+ GET('/health')
105
+ .out(json_body(
106
+ status: T.string,
107
+ timestamp: T.string,
108
+ version: T.string,
109
+ environment: T.string,
110
+ database: T.string,
111
+ services: T.hash(redis: T.string)
112
+ ))
113
+ .summary("Health check")
114
+ .description("Check API and service health")
115
+ .tag("System")
116
+ end
117
+
118
+ def health_check
119
+ {
120
+ status: 'ok',
121
+ timestamp: Time.current.iso8601,
122
+ version: '1.0.0',
123
+ environment: Rails.env,
124
+ database: database_status,
125
+ services: {
126
+ redis: redis_status
127
+ }
128
+ }
129
+ end
130
+
131
+ protected
132
+
133
+ # Helper method for standardized error responses
134
+ def render_error(message, status, details: nil, errors: nil)
135
+ payload = { error: message }
136
+ payload[:details] = details if details
137
+ payload[:errors] = errors if errors
138
+
139
+ render json: payload, status: status
140
+ end
141
+
142
+ # Helper for pagination metadata
143
+ def pagination_metadata(collection, page, per_page)
144
+ total = collection.respond_to?(:count) ? collection.count : collection.size
145
+ {
146
+ page: page,
147
+ per_page: per_page,
148
+ total: total,
149
+ total_pages: (total.to_f / per_page).ceil,
150
+ has_next: page < (total.to_f / per_page).ceil,
151
+ has_prev: page > 1
152
+ }
153
+ end
154
+
155
+ private
156
+
157
+ def database_status
158
+ ActiveRecord::Base.connection.execute('SELECT 1')
159
+ 'connected'
160
+ rescue => e
161
+ Rails.logger.error "Database check failed: #{e.message}"
162
+ 'disconnected'
163
+ end
164
+
165
+ def redis_status
166
+ 'not_configured'
167
+ rescue => e
168
+ Rails.logger.error "Redis check failed: #{e.message}"
169
+ 'disconnected'
170
+ end
171
+ end
172
+
173
+ # Api::V1::UsersController - Example API controller
174
+ class Api::V1::UsersController < ApplicationController
175
+ rapitapir do
176
+ # User type definitions
177
+ user_type = T.hash(
178
+ id: T.integer,
179
+ email: T.string,
180
+ name: T.string,
181
+ bio: T.string.optional,
182
+ created_at: T.string,
183
+ updated_at: T.string
184
+ )
185
+
186
+ # List users with pagination
187
+ GET('/api/v1/users')
188
+ .in(query(:page, T.integer.default(1)))
189
+ .in(query(:per_page, T.integer.default(10)))
190
+ .out(json_body(
191
+ users: T.array(user_type),
192
+ pagination: T.hash(
193
+ page: T.integer,
194
+ per_page: T.integer,
195
+ total: T.integer,
196
+ total_pages: T.integer
197
+ )
198
+ ))
199
+ .summary("List users")
200
+ .description("Get a paginated list of all users")
201
+ .tag("Users")
202
+
203
+ # Get specific user
204
+ GET('/api/v1/users/:id')
205
+ .in(path(:id, T.integer))
206
+ .out(json_body(user: user_type))
207
+ .error_out(json_body(error: T.string), 404)
208
+ .summary("Get user by ID")
209
+ .tag("Users")
210
+
211
+ # Create new user
212
+ POST('/api/v1/users')
213
+ .in(json_body(
214
+ name: T.string,
215
+ email: T.string,
216
+ bio: T.string.optional
217
+ ))
218
+ .out(json_body(user: user_type), 201)
219
+ .error_out(json_body(errors: T.array(T.string)), 422)
220
+ .summary("Create a new user")
221
+ .tag("Users")
222
+ end
223
+
224
+ def list_users
225
+ page = inputs[:page]
226
+ per_page = [inputs[:per_page], 50].min # Cap at 50
227
+
228
+ users_scope = User.all
229
+ total = users_scope.count
230
+ users = users_scope.offset((page - 1) * per_page).limit(per_page)
231
+
232
+ {
233
+ users: users.map(&method(:serialize_user)),
234
+ pagination: pagination_metadata(users_scope, page, per_page)
235
+ }
236
+ end
237
+
238
+ def get_user
239
+ user = User.find_by(id: inputs[:id])
240
+ return render_error("User not found", 404) unless user
241
+
242
+ { user: serialize_user(user) }
243
+ end
244
+
245
+ def create_user
246
+ user = User.new(user_params)
247
+
248
+ if user.save
249
+ render json: { user: serialize_user(user) }, status: 201
250
+ else
251
+ render_error("Validation failed", 422, errors: user.errors.full_messages)
252
+ end
253
+ end
254
+
255
+ private
256
+
257
+ def user_params
258
+ inputs.slice(:name, :email, :bio).compact
259
+ end
260
+
261
+ def serialize_user(user)
262
+ {
263
+ id: user.id,
264
+ name: user.name,
265
+ email: user.email,
266
+ bio: user.bio,
267
+ created_at: user.created_at.iso8601,
268
+ updated_at: user.updated_at.iso8601
269
+ }
270
+ end
271
+ end
272
+
273
+ # Api::V1::PostsController - Posts API
274
+ class Api::V1::PostsController < ApplicationController
275
+ rapitapir do
276
+ # Post schema
277
+ post_type = T.hash(
278
+ id: T.integer,
279
+ title: T.string,
280
+ content: T.string,
281
+ published: T.boolean,
282
+ user: T.hash(
283
+ id: T.integer,
284
+ name: T.string,
285
+ email: T.string
286
+ ),
287
+ created_at: T.string,
288
+ updated_at: T.string
289
+ )
290
+
291
+ # List posts
292
+ GET('/api/v1/posts')
293
+ .in(query(:published, T.boolean.optional))
294
+ .in(query(:page, T.integer.default(1)))
295
+ .out(json_body(
296
+ posts: T.array(post_type),
297
+ pagination: T.hash(
298
+ page: T.integer,
299
+ total: T.integer,
300
+ total_pages: T.integer
301
+ )
302
+ ))
303
+ .summary("List posts")
304
+ .tag("Posts")
305
+
306
+ # Get specific post
307
+ GET('/api/v1/posts/:id')
308
+ .in(path(:id, T.integer))
309
+ .out(json_body(post: post_type))
310
+ .error_out(json_body(error: T.string), 404)
311
+ .summary("Get post by ID")
312
+ .tag("Posts")
313
+ end
314
+
315
+ def list_posts
316
+ posts_scope = Post.includes(:user)
317
+
318
+ # Apply filters
319
+ posts_scope = posts_scope.where(published: inputs[:published]) if inputs.key?(:published)
320
+
321
+ # Pagination
322
+ page = inputs[:page]
323
+ per_page = 10
324
+ total = posts_scope.count
325
+ posts = posts_scope.offset((page - 1) * per_page).limit(per_page)
326
+
327
+ {
328
+ posts: posts.map(&method(:serialize_post)),
329
+ pagination: pagination_metadata(posts_scope, page, per_page)
330
+ }
331
+ end
332
+
333
+ def get_post
334
+ post = Post.includes(:user).find_by(id: inputs[:id])
335
+ return render_error("Post not found", 404) unless post
336
+
337
+ { post: serialize_post(post) }
338
+ end
339
+
340
+ private
341
+
342
+ def serialize_post(post)
343
+ {
344
+ id: post.id,
345
+ title: post.title,
346
+ content: post.content,
347
+ published: post.published,
348
+ user: {
349
+ id: post.user.id,
350
+ name: post.user.name,
351
+ email: post.user.email
352
+ },
353
+ created_at: post.created_at.iso8601,
354
+ updated_at: post.updated_at.iso8601
355
+ }
356
+ end
357
+ end
358
+
359
+ # Application routing
360
+ Rails.application.routes.draw do
361
+ # Health check (from ApplicationController)
362
+ rapitapir_routes_for ApplicationController
363
+
364
+ # API v1 routes - RapiTapir auto-generates routes from endpoint definitions
365
+ namespace :api do
366
+ namespace :v1 do
367
+ rapitapir_routes_for Api::V1::UsersController
368
+ rapitapir_routes_for Api::V1::PostsController
369
+ end
370
+ end
371
+
372
+ # Documentation routes (automatically added by development_defaults!)
373
+ # Available at:
374
+ # - GET /docs -> Swagger UI
375
+ # - GET /openapi.json -> OpenAPI 3.0 specification
376
+
377
+ # Catch-all for unmatched routes
378
+ match '*path', to: proc { |env|
379
+ [404, { 'Content-Type' => 'application/json' }, [{ error: 'Route not found' }.to_json]]
380
+ }, via: :all
381
+ end
382
+
383
+ # Start the server
384
+ if __FILE__ == $0
385
+ puts "๐Ÿš€ Starting Traditional Rails App with RapiTapir on http://localhost:3000"
386
+ puts "๐Ÿ“š API Documentation: http://localhost:3000/docs"
387
+ puts "๐Ÿ“‹ OpenAPI Spec: http://localhost:3000/openapi.json"
388
+ puts "๐Ÿฅ Health Check: http://localhost:3000/health"
389
+ puts ""
390
+ puts "๐Ÿ“‹ Available Endpoints:"
391
+ puts " GET /health - System health check"
392
+ puts " GET /api/v1/users - List users"
393
+ puts " GET /api/v1/users/1 - Get specific user"
394
+ puts " POST /api/v1/users - Create new user"
395
+ puts " GET /api/v1/posts - List posts"
396
+ puts " GET /api/v1/posts/1 - Get specific post"
397
+ puts ""
398
+ puts "๐Ÿงช Test Examples:"
399
+ puts " curl http://localhost:3000/health"
400
+ puts " curl http://localhost:3000/api/v1/users"
401
+ puts " curl http://localhost:3000/api/v1/posts?published=true"
402
+ puts ""
403
+
404
+ require 'rack'
405
+ Rack::Handler::WEBrick.run(Rails.application, Port: 3000)
406
+ end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Example Rails controller using RapiTapir
3
+ # LEGACY Example Rails controller using RapiTapir (verbose approach)
4
+ #
5
+ # โš ๏ธ This example shows the OLD way of integrating RapiTapir with Rails.
6
+ # ๐Ÿ“– For the NEW, enhanced approach, see enhanced_users_controller.rb
4
7
  #
5
8
  # To use this in a Rails app:
6
9
  # 1. Add this file to app/controllers/
@@ -0,0 +1,43 @@
1
+ # Gemfile for serverless deployments
2
+ source 'https://rubygems.org'
3
+
4
+ # Core RapiTapir dependency
5
+ gem 'rapitapir', path: '../..' # Use local version for examples
6
+
7
+ # Serverless runtime dependencies
8
+ gem 'sinatra', '~> 3.0'
9
+ gem 'rack', '~> 2.2'
10
+ gem 'json', '~> 2.6'
11
+
12
+ # Platform-specific gems
13
+ group :aws do
14
+ gem 'aws-sdk-dynamodb', '~> 1.0'
15
+ gem 'aws-sdk-cloudwatch', '~> 1.0'
16
+ gem 'aws-sdk-s3', '~> 1.0'
17
+ end
18
+
19
+ group :gcp do
20
+ gem 'functions_framework', '~> 1.0'
21
+ gem 'google-cloud-firestore', '~> 2.0'
22
+ gem 'google-cloud-logging', '~> 2.0'
23
+ end
24
+
25
+ group :azure do
26
+ gem 'azure-storage-blob', '~> 2.0'
27
+ gem 'azure-cosmos', '~> 0.1'
28
+ end
29
+
30
+ group :development, :test do
31
+ gem 'rspec', '~> 3.11'
32
+ gem 'rack-test', '~> 2.0'
33
+ gem 'webmock', '~> 3.18'
34
+ gem 'vcr', '~> 6.1'
35
+ gem 'pry', '~> 0.14'
36
+ end
37
+
38
+ group :production do
39
+ gem 'newrelic_rpm', '~> 8.0' # For monitoring (optional)
40
+ end
41
+
42
+ # Ruby version for serverless runtimes
43
+ ruby '3.2.0'