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
@@ -2,145 +2,558 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- RapiTapir's Auto-Derivation feature automatically generates type schemas from structured data sources that have explicit type information. This addresses Ruby's lack of built-in type declarations by focusing on reliable sources.
5
+ RapiTapir's Auto-Derivation feature automatically generates type schemas from structured data sources with explicit type information. This feature accelerates API development by reducing manual schema definition while maintaining type safety.
6
6
 
7
- ## Supported Sources
7
+ ## Supported Data Sources
8
8
 
9
- ### 1. JSON Schema (Primary Use Case)
10
- Perfect for API contracts and external service integration.
9
+ ### 1. JSON Schema (API Contracts)
10
+ Perfect for external API integration and OpenAPI specifications.
11
11
 
12
12
  ```ruby
13
- # API response schema
14
- user_schema = {
13
+ # Example: GitHub API user schema
14
+ github_user_schema = {
15
15
  "type" => "object",
16
16
  "properties" => {
17
17
  "id" => { "type" => "integer" },
18
- "name" => { "type" => "string" },
18
+ "login" => { "type" => "string" },
19
+ "avatar_url" => { "type" => "string", "format" => "uri" },
19
20
  "email" => { "type" => "string", "format" => "email" },
20
- "active" => { "type" => "boolean" },
21
- "tags" => { "type" => "array", "items" => { "type" => "string" } }
21
+ "created_at" => { "type" => "string", "format" => "date-time" },
22
+ "public_repos" => { "type" => "integer" },
23
+ "followers" => { "type" => "integer" },
24
+ "following" => { "type" => "integer" }
22
25
  },
23
- "required" => ["id", "name", "email"]
26
+ "required" => ["id", "login", "avatar_url"]
24
27
  }
25
28
 
26
- # Auto-derive RapiTapir schema
27
- USER_SCHEMA = RapiTapir::Types.from_json_schema(user_schema)
29
+ # Auto-derive RapiTapir schema using T shortcut
30
+ GITHUB_USER_SCHEMA = T.from_json_schema(github_user_schema)
28
31
  ```
29
32
 
30
- ### 2. OpenStruct (Configuration Objects)
31
- Great for configuration objects and dynamic data structures.
33
+ ### 2. Hash Structures (Sample Data)
34
+ Generate schemas from existing hash data with type inference.
35
+
36
+ ```ruby
37
+ # Sample user data
38
+ sample_user = {
39
+ id: 123,
40
+ name: "John Doe",
41
+ email: "john@example.com",
42
+ active: true,
43
+ created_at: Time.now,
44
+ tags: ["admin", "premium"],
45
+ profile: {
46
+ bio: "Software developer",
47
+ age: 30,
48
+ location: "San Francisco"
49
+ }
50
+ }
51
+
52
+ # Auto-derive schema with intelligent type inference
53
+ USER_SCHEMA = T.from_hash(sample_user)
54
+ ```
55
+
56
+ ### 3. OpenStruct (Configuration Objects)
57
+ Ideal for configuration schemas and dynamic data structures.
32
58
 
33
59
  ```ruby
34
60
  require 'ostruct'
35
61
 
36
62
  # Configuration object
37
- config = OpenStruct.new(
63
+ api_config = OpenStruct.new(
38
64
  host: "api.example.com",
39
65
  port: 443,
40
66
  ssl: true,
41
67
  timeout: 30.5,
42
- features: ["auth", "logging"]
68
+ max_retries: 3,
69
+ features: ["auth", "logging", "metrics"],
70
+ database: OpenStruct.new(
71
+ host: "db.example.com",
72
+ port: 5432,
73
+ ssl: true
74
+ )
43
75
  )
44
76
 
45
- # Auto-derive RapiTapir schema
46
- CONFIG_SCHEMA = RapiTapir::Types.from_open_struct(config)
77
+ # Auto-derive nested schema
78
+ CONFIG_SCHEMA = T.from_open_struct(api_config)
79
+ ```
80
+
81
+ ### 4. ActiveRecord Models (Rails Integration)
82
+ Extract schemas from existing model definitions.
83
+
84
+ ```ruby
85
+ # Derive schema from ActiveRecord model
86
+ class User < ActiveRecord::Base
87
+ # has attributes: id, name, email, created_at, updated_at
88
+ end
89
+
90
+ # Auto-derive from model
91
+ USER_SCHEMA = T.from_activerecord(User)
92
+
93
+ # With field filtering
94
+ PUBLIC_USER_SCHEMA = T.from_activerecord(User, only: [:id, :name, :email])
47
95
  ```
48
96
 
49
- ### 3. Protobuf Messages (When Available)
50
- Excellent for gRPC services and binary protocols.
97
+ ### 5. Protobuf Messages (gRPC Integration)
98
+ Generate schemas from Protocol Buffer definitions.
51
99
 
52
100
  ```ruby
53
101
  # Requires google-protobuf gem
54
- USER_SCHEMA = RapiTapir::Types.from_protobuf(UserProto)
102
+ require 'google/protobuf'
103
+
104
+ # Auto-derive from protobuf message
105
+ USER_SCHEMA = T.from_protobuf(UserProto)
106
+ SEARCH_REQUEST_SCHEMA = T.from_protobuf(SearchRequestProto)
55
107
  ```
56
108
 
57
- ## Field Filtering
109
+ ## Advanced Features
58
110
 
59
- All methods support field filtering:
111
+ ### Field Filtering
112
+
113
+ Control which fields are included in the derived schema:
60
114
 
61
115
  ```ruby
62
116
  # Include only specific fields
63
- schema = RapiTapir::Types.from_json_schema(json_schema, only: [:id, :name])
64
-
65
- # Exclude specific fields
66
- schema = RapiTapir::Types.from_json_schema(json_schema, except: [:internal_id])
67
- ```
68
-
69
- ## Supported JSON Schema Features
70
-
71
- - **Basic Types**: string, integer, number, boolean, array, object
72
- - **String Formats**: email, uuid, date, date-time
73
- - **Required Fields**: Automatically handled with optional types
74
- - **Nested Objects**: Converted to hash types
75
- - **Arrays**: With typed items
76
-
77
- ## Type Mappings
78
-
79
- ### JSON Schema → RapiTapir
80
- - `string` → `RapiTapir::Types.string`
81
- - `string` (format: email) → `RapiTapir::Types.email`
82
- - `string` (format: uuid) → `RapiTapir::Types.uuid`
83
- - `string` (format: date) → `RapiTapir::Types.date`
84
- - `string` (format: date-time) → `RapiTapir::Types.datetime`
85
- - `integer` → `RapiTapir::Types.integer`
86
- - `number` `RapiTapir::Types.float`
87
- - `boolean` `RapiTapir::Types.boolean`
88
- - `array` → `RapiTapir::Types.array(item_type)`
89
- - `object` → `RapiTapir::Types.hash({})`
90
-
91
- ### Ruby Values → RapiTapir (OpenStruct)
92
- - `String` → `RapiTapir::Types.string`
93
- - `Integer` → `RapiTapir::Types.integer`
94
- - `Float` `RapiTapir::Types.float`
95
- - `TrueClass/FalseClass` → `RapiTapir::Types.boolean`
96
- - `Date` `RapiTapir::Types.date`
97
- - `Time/DateTime` → `RapiTapir::Types.datetime`
98
- - `Array` → `RapiTapir::Types.array(inferred_item_type)`
99
- - `Hash` `RapiTapir::Types.hash({})`
100
-
101
- ## Integration Examples
102
-
103
- ### API Endpoint with JSON Schema
104
- ```ruby
105
- # Define endpoint with auto-derived input/output schemas
106
- api.post('/users') do |endpoint|
107
- endpoint
108
- .in(json_body(RapiTapir::Types.from_json_schema(create_user_schema)))
109
- .out(json_body(RapiTapir::Types.from_json_schema(user_response_schema)))
110
- .handle do |input|
111
- # Handle user creation
117
+ user_schema = T.from_json_schema(github_user_schema, only: [:id, :login, :email])
118
+
119
+ # Exclude specific fields (useful for sensitive data)
120
+ public_user_schema = T.from_hash(user_data, except: [:password_hash, :api_keys])
121
+
122
+ # Complex filtering with nested paths
123
+ filtered_schema = T.from_hash(complex_data,
124
+ only: ['user.id', 'user.profile.name', 'metadata.version']
125
+ )
126
+ ```
127
+
128
+ ### Type Enhancement
129
+
130
+ Enhance auto-derived schemas with additional constraints:
131
+
132
+ ```ruby
133
+ # Basic auto-derivation
134
+ base_schema = T.from_hash({
135
+ name: "John",
136
+ age: 30,
137
+ email: "john@example.com"
138
+ })
139
+
140
+ # Enhance with constraints
141
+ enhanced_schema = T.enhance(base_schema) do |schema|
142
+ schema.field(:name).min_length(1).max_length(100)
143
+ schema.field(:age).minimum(0).maximum(150)
144
+ schema.field(:email).format(:email)
145
+ end
146
+ ```
147
+
148
+ ### Smart Type Inference
149
+
150
+ RapiTapir uses intelligent type inference for common patterns:
151
+
152
+ ```ruby
153
+ # Recognizes common patterns
154
+ sample_data = {
155
+ id: "550e8400-e29b-41d4-a716-446655440000", # → T.uuid
156
+ email: "user@example.com", # → T.email
157
+ url: "https://example.com", # T.url
158
+ created_at: "2024-01-15T10:30:00Z", # → T.datetime
159
+ price: "19.99", # T.decimal
160
+ phone: "+1-555-123-4567", # → T.phone
161
+ tags: ["admin", "premium"] # → T.array(T.string)
162
+ }
163
+
164
+ SMART_SCHEMA = T.from_hash(sample_data, smart_inference: true)
165
+ ```
166
+
167
+ ## AI-Enhanced Auto-Derivation
168
+
169
+ ### LLM-Powered Schema Generation
170
+
171
+ Use AI to generate schemas from natural language descriptions:
172
+
173
+ ```ruby
174
+ class AIEnhancedAPI < SinatraRapiTapir
175
+ rapitapir do
176
+ enable_llm_schema_generation(
177
+ provider: :openai,
178
+ model: 'gpt-4',
179
+ confidence_threshold: 0.8
180
+ )
181
+ end
182
+
183
+ # Generate schema from description
184
+ USER_PROFILE_SCHEMA = T.from_description(
185
+ "A user profile with name, email, bio (optional), age (18-100),
186
+ preferences object with theme and notifications settings,
187
+ and an array of skill tags"
188
+ )
189
+
190
+ # Use in endpoints
191
+ endpoint(
192
+ PUT('/profile')
193
+ .summary('Update user profile with AI-derived schema')
194
+ .body(USER_PROFILE_SCHEMA, description: 'User profile data')
195
+ .tags('Users', 'AI')
196
+ .ok(USER_PROFILE_SCHEMA)
197
+ .build
198
+ ) do |inputs|
199
+ # Handle profile update
200
+ current_user.update!(inputs[:body])
201
+ current_user.to_h
202
+ end
203
+ end
204
+ ```
205
+
206
+ ### Schema Validation with AI
207
+
208
+ Validate and improve schemas using AI analysis:
209
+
210
+ ```ruby
211
+ # AI-enhanced schema validation
212
+ validated_schema = T.validate_with_ai(base_schema,
213
+ context: "REST API for e-commerce user management",
214
+ suggestions: true,
215
+ security_check: true
216
+ )
217
+
218
+ # AI suggests improvements:
219
+ # - Add email format validation
220
+ # - Include password strength requirements
221
+ # - Add rate limiting fields
222
+ # - Suggest security headers
223
+ ```
224
+
225
+ ## Practical Examples
226
+
227
+ ### External API Integration
228
+
229
+ ```ruby
230
+ class GitHubIntegrationAPI < SinatraRapiTapir
231
+ # Fetch and cache GitHub API schema
232
+ GITHUB_USER_SCHEMA = T.from_url(
233
+ 'https://api.github.com/users/octocat',
234
+ cache_ttl: 3600
235
+ )
236
+
237
+ endpoint(
238
+ GET('/users/:username/github')
239
+ .path_param(:username, T.string, description: 'GitHub username')
240
+ .summary('Get GitHub user profile')
241
+ .tags('Integration', 'GitHub')
242
+ .ok(GITHUB_USER_SCHEMA)
243
+ .error_response(404, T.hash({
244
+ "error" => T.string,
245
+ "username" => T.string
246
+ }))
247
+ .build
248
+ ) do |inputs|
249
+ username = inputs[:username]
250
+
251
+ response = HTTP.get("https://api.github.com/users/#{username}")
252
+
253
+ if response.status.success?
254
+ JSON.parse(response.body)
255
+ else
256
+ halt 404, {
257
+ error: "GitHub user not found",
258
+ username: username
259
+ }.to_json
112
260
  end
261
+ end
262
+ end
263
+ ```
264
+
265
+ ### Configuration Management API
266
+
267
+ ```ruby
268
+ class ConfigAPI < SinatraRapiTapir
269
+ # Auto-derive from default configuration
270
+ DEFAULT_CONFIG = {
271
+ database: {
272
+ host: 'localhost',
273
+ port: 5432,
274
+ ssl: true,
275
+ pool_size: 10,
276
+ timeout: 30
277
+ },
278
+ redis: {
279
+ host: 'localhost',
280
+ port: 6379,
281
+ db: 0
282
+ },
283
+ features: {
284
+ rate_limiting: true,
285
+ caching: true,
286
+ metrics: true
287
+ },
288
+ api: {
289
+ version: '1.0.0',
290
+ rate_limit: 1000,
291
+ timeout: 60
292
+ }
293
+ }
294
+
295
+ CONFIG_SCHEMA = T.from_hash(DEFAULT_CONFIG)
296
+
297
+ endpoint(
298
+ PUT('/config')
299
+ .summary('Update application configuration')
300
+ .body(CONFIG_SCHEMA, description: 'Configuration updates')
301
+ .tags('Configuration')
302
+ .ok(T.hash({
303
+ "updated_config" => CONFIG_SCHEMA,
304
+ "restart_required" => T.boolean,
305
+ "updated_at" => T.datetime
306
+ }))
307
+ .build
308
+ ) do |inputs|
309
+ new_config = inputs[:body]
310
+
311
+ # Validate configuration
312
+ validator = ConfigValidator.new(new_config)
313
+ halt 422, validator.errors.to_json unless validator.valid?
314
+
315
+ # Apply configuration
316
+ ConfigManager.update!(new_config)
317
+
318
+ {
319
+ updated_config: ConfigManager.current_config,
320
+ restart_required: ConfigManager.restart_required?,
321
+ updated_at: Time.now
322
+ }
323
+ end
113
324
  end
114
325
  ```
115
326
 
116
- ### Configuration Validation
327
+ ### Multi-Source Schema Composition
328
+
117
329
  ```ruby
118
- # Validate configuration objects
119
- config_schema = RapiTapir::Types.from_open_struct(default_config)
330
+ class CompositeAPI < SinatraRapiTapir
331
+ # Combine schemas from multiple sources
332
+ BASE_USER_SCHEMA = T.from_hash({
333
+ id: 1,
334
+ name: "John Doe",
335
+ email: "john@example.com"
336
+ })
337
+
338
+ EXTERNAL_PROFILE_SCHEMA = T.from_json_schema({
339
+ "type" => "object",
340
+ "properties" => {
341
+ "bio" => { "type" => "string" },
342
+ "website" => { "type" => "string", "format" => "uri" },
343
+ "location" => { "type" => "string" }
344
+ }
345
+ })
346
+
347
+ PREFERENCES_SCHEMA = T.from_open_struct(OpenStruct.new(
348
+ theme: 'dark',
349
+ notifications: true,
350
+ language: 'en'
351
+ ))
352
+
353
+ # Compose complete user schema
354
+ COMPLETE_USER_SCHEMA = T.compose(
355
+ BASE_USER_SCHEMA,
356
+ profile: EXTERNAL_PROFILE_SCHEMA,
357
+ preferences: PREFERENCES_SCHEMA
358
+ )
359
+
360
+ endpoint(
361
+ GET('/users/:id/complete')
362
+ .path_param(:id, T.integer, description: 'User ID')
363
+ .summary('Get complete user profile')
364
+ .tags('Users')
365
+ .ok(COMPLETE_USER_SCHEMA)
366
+ .build
367
+ ) do |inputs|
368
+ user = User.find(inputs[:id])
369
+ profile = ProfileService.get(user.id)
370
+ preferences = PreferencesService.get(user.id)
371
+
372
+ {
373
+ **user.to_h,
374
+ profile: profile,
375
+ preferences: preferences
376
+ }
377
+ end
378
+ end
379
+ ```
380
+
381
+ ## Type Mappings Reference
120
382
 
121
- api.post('/configure') do |endpoint|
122
- endpoint
123
- .in(json_body(config_schema))
124
- .handle do |config|
125
- # Apply configuration
383
+ ### JSON Schema → RapiTapir Types
384
+
385
+ | JSON Schema | RapiTapir Type |
386
+ |-------------|----------------|
387
+ | `"type": "string"` | `T.string` |
388
+ | `"type": "string", "format": "email"` | `T.email` |
389
+ | `"type": "string", "format": "uuid"` | `T.uuid` |
390
+ | `"type": "string", "format": "uri"` | `T.url` |
391
+ | `"type": "string", "format": "date"` | `T.date` |
392
+ | `"type": "string", "format": "date-time"` | `T.datetime` |
393
+ | `"type": "integer"` | `T.integer` |
394
+ | `"type": "number"` | `T.float` |
395
+ | `"type": "boolean"` | `T.boolean` |
396
+ | `"type": "array"` | `T.array(item_type)` |
397
+ | `"type": "object"` | `T.hash({})` |
398
+
399
+ ### Ruby Values → RapiTapir Types
400
+
401
+ | Ruby Type | RapiTapir Type |
402
+ |-----------|----------------|
403
+ | `String` | `T.string` |
404
+ | `Integer` | `T.integer` |
405
+ | `Float` | `T.float` |
406
+ | `TrueClass/FalseClass` | `T.boolean` |
407
+ | `Date` | `T.date` |
408
+ | `Time/DateTime` | `T.datetime` |
409
+ | `Array` | `T.array(inferred_type)` |
410
+ | `Hash` | `T.hash({})` |
411
+ | `NilClass` | `T.optional(inferred_type)` |
412
+
413
+ ### Smart Pattern Recognition
414
+
415
+ | Pattern | Detected As |
416
+ |---------|-------------|
417
+ | UUID format strings | `T.uuid` |
418
+ | Email format strings | `T.email` |
419
+ | URL format strings | `T.url` |
420
+ | ISO date strings | `T.datetime` |
421
+ | Phone number strings | `T.phone` |
422
+ | Decimal strings | `T.decimal` |
423
+
424
+ ## Performance Considerations
425
+
426
+ ### Caching Auto-Derived Schemas
427
+
428
+ ```ruby
429
+ # Cache expensive derivations
430
+ class SchemaCache
431
+ @cache = {}
432
+
433
+ def self.from_url(url, ttl: 3600)
434
+ cache_key = "schema:#{url}"
435
+
436
+ if cached = @cache[cache_key]
437
+ return cached[:schema] if cached[:expires_at] > Time.now
126
438
  end
439
+
440
+ schema = T.from_url(url)
441
+ @cache[cache_key] = {
442
+ schema: schema,
443
+ expires_at: Time.now + ttl
444
+ }
445
+
446
+ schema
447
+ end
127
448
  end
128
449
  ```
129
450
 
130
- ## Benefits
451
+ ### Lazy Schema Loading
131
452
 
132
- 1. **Reliable Type Information**: Uses sources with explicit types
133
- 2. **Reduced Boilerplate**: No manual schema definition needed
134
- 3. **External Integration**: Perfect for API contracts and third-party schemas
135
- 4. **Field Control**: Flexible filtering with only/except
136
- 5. **Error Safety**: Proper validation and error handling
453
+ ```ruby
454
+ class LazyAPI < SinatraRapiTapir
455
+ # Define schemas lazily to improve startup time
456
+ def self.user_schema
457
+ @user_schema ||= T.from_url('https://api.example.com/schema/user')
458
+ end
459
+
460
+ endpoint(
461
+ GET('/users')
462
+ .ok(T.array(user_schema))
463
+ .build
464
+ ) { User.all }
465
+ end
466
+ ```
467
+
468
+ ## Best Practices
137
469
 
138
- ## Limitations
470
+ ### 1. Use Reliable Sources
471
+ ```ruby
472
+ # ✅ Good - explicit type information
473
+ schema = T.from_json_schema(api_spec)
139
474
 
140
- - Ruby classes without explicit types are not supported (by design)
141
- - Constraint validation from JSON Schema is mapped to basic types
142
- - Protobuf support requires the google-protobuf gem
475
+ # Avoid - unreliable runtime inference
476
+ schema = T.from_object(random_object)
477
+ ```
478
+
479
+ ### 2. Apply Field Filtering
480
+ ```ruby
481
+ # ✅ Filter sensitive fields
482
+ public_schema = T.from_hash(user_data, except: [:password, :tokens])
483
+
484
+ # ✅ Include only needed fields
485
+ minimal_schema = T.from_activerecord(User, only: [:id, :name, :email])
486
+ ```
487
+
488
+ ### 3. Enhance with Constraints
489
+ ```ruby
490
+ # ✅ Add business rules after derivation
491
+ enhanced = T.enhance(base_schema) do |s|
492
+ s.field(:age).minimum(18).maximum(100)
493
+ s.field(:email).required
494
+ end
495
+ ```
496
+
497
+ ### 4. Cache Expensive Operations
498
+ ```ruby
499
+ # ✅ Cache external schema fetches
500
+ EXTERNAL_SCHEMA = T.from_url(schema_url, cache: true)
501
+ ```
502
+
503
+ ### 5. Validate Auto-Derived Schemas
504
+ ```ruby
505
+ # ✅ Test derived schemas
506
+ RSpec.describe 'Auto-derived schemas' do
507
+ it 'validates expected structure' do
508
+ schema = T.from_hash(sample_data)
509
+ expect(schema.validate!(test_data)).to be_truthy
510
+ end
511
+ end
512
+ ```
513
+
514
+ ## Error Handling
515
+
516
+ ```ruby
517
+ begin
518
+ schema = T.from_json_schema(external_schema)
519
+ rescue T::AutoDerivation::InvalidSchemaError => e
520
+ logger.error("Schema derivation failed: #{e.message}")
521
+ # Fall back to manual schema
522
+ schema = fallback_schema
523
+ rescue T::AutoDerivation::NetworkError => e
524
+ logger.warn("Network error fetching schema: #{e.message}")
525
+ # Use cached version or default
526
+ schema = cached_schema || default_schema
527
+ end
528
+ ```
529
+
530
+ ## Integration with AI Features
531
+
532
+ Auto-derivation works seamlessly with RapiTapir's AI features:
533
+
534
+ ```ruby
535
+ class AIIntegratedAPI < SinatraRapiTapir
536
+ # Auto-derive schema, then enhance with AI
537
+ BASE_SCHEMA = T.from_hash(sample_data)
538
+
539
+ endpoint(
540
+ POST('/analyze')
541
+ .body(BASE_SCHEMA)
542
+ .enable_llm_instructions(purpose: :analysis)
543
+ .enable_rag(retrieval_backend: :memory)
544
+ .ok(T.hash({
545
+ "analysis" => T.string,
546
+ "confidence" => T.float,
547
+ "suggestions" => T.array(T.string)
548
+ }))
549
+ .build
550
+ ) do |inputs|
551
+ # AI-powered analysis using auto-derived schema
552
+ AIAnalysisService.analyze(inputs[:body], context: rag_context)
553
+ end
554
+ end
555
+ ```
143
556
 
144
- ## Why This Approach?
557
+ ---
145
558
 
146
- Unlike languages with built-in type systems, Ruby's dynamic nature makes type inference unreliable. This feature focuses on sources that provide explicit type information, making auto-derivation practical and dependable for real-world use cases.
559
+ Auto-derivation significantly accelerates API development while maintaining the type safety and validation benefits that make RapiTapir powerful. By leveraging structured data sources with explicit type information, you can build robust APIs with minimal manual schema definition.