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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -7
- data/.rubocop_todo.yml +83 -0
- data/README.md +1319 -235
- data/RUBY_WEEKLY_LAUNCH_POST.md +219 -0
- data/docs/RAILS_INTEGRATION_IMPLEMENTATION.md +209 -0
- data/docs/SINATRA_EXTENSION.md +399 -348
- data/docs/STRICT_VALIDATION.md +229 -0
- data/docs/VALIDATION_IMPROVEMENTS.md +218 -0
- data/docs/ai-integration-plan.md +112 -0
- data/docs/auto-derivation.md +505 -92
- data/docs/endpoint-definition.md +536 -129
- data/docs/n8n-integration.md +212 -0
- data/docs/observability.md +810 -500
- data/docs/using-mcp.md +93 -0
- data/examples/ai/knowledge_base_rag.rb +83 -0
- data/examples/ai/user_management_mcp.rb +92 -0
- data/examples/ai/user_validation_llm.rb +187 -0
- data/examples/rails/RAILS_8_GUIDE.md +165 -0
- data/examples/rails/RAILS_LOADING_FIX.rb +35 -0
- data/examples/rails/README.md +497 -0
- data/examples/rails/comprehensive_test.rb +91 -0
- data/examples/rails/config/routes.rb +48 -0
- data/examples/rails/debug_controller.rb +63 -0
- data/examples/rails/detailed_test.rb +46 -0
- data/examples/rails/enhanced_users_controller.rb +278 -0
- data/examples/rails/final_server_test.rb +50 -0
- data/examples/rails/hello_world_app.rb +116 -0
- data/examples/rails/hello_world_controller.rb +186 -0
- data/examples/rails/hello_world_routes.rb +28 -0
- data/examples/rails/rails8_minimal_demo.rb +132 -0
- data/examples/rails/rails8_simple_demo.rb +140 -0
- data/examples/rails/rails8_working_demo.rb +255 -0
- data/examples/rails/real_world_blog_api.rb +510 -0
- data/examples/rails/server_test.rb +46 -0
- data/examples/rails/test_direct_processing.rb +41 -0
- data/examples/rails/test_hello_world.rb +80 -0
- data/examples/rails/test_rails_integration.rb +54 -0
- data/examples/rails/traditional_app/Gemfile +37 -0
- data/examples/rails/traditional_app/README.md +265 -0
- data/examples/rails/traditional_app/app/controllers/api/v1/posts_controller.rb +254 -0
- data/examples/rails/traditional_app/app/controllers/api/v1/users_controller.rb +220 -0
- data/examples/rails/traditional_app/app/controllers/application_controller.rb +86 -0
- data/examples/rails/traditional_app/app/controllers/application_controller_simplified.rb +87 -0
- data/examples/rails/traditional_app/app/controllers/documentation_controller.rb +149 -0
- data/examples/rails/traditional_app/app/controllers/health_controller.rb +42 -0
- data/examples/rails/traditional_app/config/routes.rb +25 -0
- data/examples/rails/traditional_app/config/routes_best_practice.rb +25 -0
- data/examples/rails/traditional_app/config/routes_simplified.rb +36 -0
- data/examples/rails/traditional_app_runnable.rb +406 -0
- data/examples/rails/users_controller.rb +4 -1
- data/examples/serverless/Gemfile +43 -0
- data/examples/serverless/QUICKSTART.md +331 -0
- data/examples/serverless/README.md +520 -0
- data/examples/serverless/aws_lambda_example.rb +307 -0
- data/examples/serverless/aws_sam_template.yaml +215 -0
- data/examples/serverless/azure_functions_example.rb +407 -0
- data/examples/serverless/deploy.rb +204 -0
- data/examples/serverless/gcp_cloud_functions_example.rb +367 -0
- data/examples/serverless/gcp_function.yaml +23 -0
- data/examples/serverless/host.json +24 -0
- data/examples/serverless/package.json +32 -0
- data/examples/serverless/spec/aws_lambda_spec.rb +196 -0
- data/examples/serverless/spec/spec_helper.rb +89 -0
- data/examples/serverless/vercel.json +31 -0
- data/examples/serverless/vercel_example.rb +404 -0
- data/examples/strict_validation_examples.rb +104 -0
- data/examples/validation_error_examples.rb +173 -0
- data/lib/rapitapir/ai/llm_instruction.rb +456 -0
- data/lib/rapitapir/ai/mcp.rb +134 -0
- data/lib/rapitapir/ai/rag.rb +287 -0
- data/lib/rapitapir/ai/rag_middleware.rb +147 -0
- data/lib/rapitapir/auth/oauth2.rb +43 -57
- data/lib/rapitapir/cli/command.rb +362 -2
- data/lib/rapitapir/cli/mcp_export.rb +18 -0
- data/lib/rapitapir/cli/validator.rb +2 -6
- data/lib/rapitapir/core/endpoint.rb +59 -6
- data/lib/rapitapir/core/enhanced_endpoint.rb +2 -6
- data/lib/rapitapir/dsl/fluent_endpoint_builder.rb +53 -0
- data/lib/rapitapir/endpoint_registry.rb +47 -0
- data/lib/rapitapir/observability/health_check.rb +4 -4
- data/lib/rapitapir/observability/logging.rb +10 -10
- data/lib/rapitapir/schema.rb +2 -2
- data/lib/rapitapir/server/rack_adapter.rb +1 -3
- data/lib/rapitapir/server/rails/configuration.rb +77 -0
- data/lib/rapitapir/server/rails/controller_base.rb +185 -0
- data/lib/rapitapir/server/rails/documentation_helpers.rb +76 -0
- data/lib/rapitapir/server/rails/resource_builder.rb +181 -0
- data/lib/rapitapir/server/rails/routes.rb +114 -0
- data/lib/rapitapir/server/rails_adapter.rb +10 -3
- data/lib/rapitapir/server/rails_adapter_class.rb +1 -3
- data/lib/rapitapir/server/rails_controller.rb +1 -3
- data/lib/rapitapir/server/rails_integration.rb +67 -0
- data/lib/rapitapir/server/rails_response_handler.rb +16 -3
- data/lib/rapitapir/server/sinatra_adapter.rb +29 -5
- data/lib/rapitapir/server/sinatra_integration.rb +4 -4
- data/lib/rapitapir/sinatra/extension.rb +2 -2
- data/lib/rapitapir/sinatra/oauth2_helpers.rb +34 -40
- data/lib/rapitapir/types/array.rb +4 -0
- data/lib/rapitapir/types/auto_derivation.rb +4 -18
- data/lib/rapitapir/types/datetime.rb +1 -3
- data/lib/rapitapir/types/float.rb +2 -6
- data/lib/rapitapir/types/hash.rb +40 -2
- data/lib/rapitapir/types/integer.rb +4 -12
- data/lib/rapitapir/types/object.rb +6 -2
- data/lib/rapitapir/types.rb +6 -2
- data/lib/rapitapir/version.rb +1 -1
- data/lib/rapitapir.rb +5 -3
- metadata +74 -2
data/docs/auto-derivation.md
CHANGED
@@ -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
|
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 (
|
10
|
-
Perfect for API
|
9
|
+
### 1. JSON Schema (API Contracts)
|
10
|
+
Perfect for external API integration and OpenAPI specifications.
|
11
11
|
|
12
12
|
```ruby
|
13
|
-
# API
|
14
|
-
|
13
|
+
# Example: GitHub API user schema
|
14
|
+
github_user_schema = {
|
15
15
|
"type" => "object",
|
16
16
|
"properties" => {
|
17
17
|
"id" => { "type" => "integer" },
|
18
|
-
"
|
18
|
+
"login" => { "type" => "string" },
|
19
|
+
"avatar_url" => { "type" => "string", "format" => "uri" },
|
19
20
|
"email" => { "type" => "string", "format" => "email" },
|
20
|
-
"
|
21
|
-
"
|
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", "
|
26
|
+
"required" => ["id", "login", "avatar_url"]
|
24
27
|
}
|
25
28
|
|
26
|
-
# Auto-derive RapiTapir schema
|
27
|
-
|
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.
|
31
|
-
|
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
|
-
|
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
|
-
|
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
|
46
|
-
CONFIG_SCHEMA =
|
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
|
-
###
|
50
|
-
|
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
|
-
|
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
|
-
##
|
109
|
+
## Advanced Features
|
58
110
|
|
59
|
-
|
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
|
-
|
64
|
-
|
65
|
-
# Exclude specific fields
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
###
|
327
|
+
### Multi-Source Schema Composition
|
328
|
+
|
117
329
|
```ruby
|
118
|
-
|
119
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
451
|
+
### Lazy Schema Loading
|
131
452
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
470
|
+
### 1. Use Reliable Sources
|
471
|
+
```ruby
|
472
|
+
# ✅ Good - explicit type information
|
473
|
+
schema = T.from_json_schema(api_spec)
|
139
474
|
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
557
|
+
---
|
145
558
|
|
146
|
-
|
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.
|