rapitapir 0.1.1 โ 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
- data/rapitapir.gemspec +7 -5
- metadata +116 -16
data/README.md
CHANGED
@@ -1,278 +1,824 @@
|
|
1
|
-
# RapiTapir
|
1
|
+
# RapiTapir
|
2
2
|
|
3
|
-
-
|
4
|
-
-
|
5
|
-
- **๐ฏ Enhanced HTTP DSL**: Built-in GET, POST, PUT, DELETE methods with fluent chaining
|
6
|
-
- **๐ง Zero Boilerplate**: Automatic extension registration and feature setup
|
7
|
-
- ๐ **Type Shortcuts**: Clean syntax with global `T` constant (automatic - no setup needed!)
|
8
|
-
- **๐ GitHub Pages Ready**: Modern documentation deployment with GitHub Actions
|
9
|
-
- **๐งช Comprehensive Tests**: 470 tests passing with 70% coverage modern Ruby library for building type-safe HTTP APIs with automatic OpenAPI documentation**
|
10
|
-
|
11
|
-
[](spec/)
|
12
|
-
[](coverage/)
|
3
|
+
[](spec/)
|
4
|
+
[](coverage/)
|
13
5
|
[](Gemfile)
|
14
6
|
[](LICENSE)
|
7
|
+
[](https://github.com/rubocop/rubocop)
|
8
|
+
[](lib/rapitapir/ai/)
|
15
9
|
|
16
|
-
**RapiTapir ๐ฆ** combines the expressiveness of Ruby with the safety of strong typing to create APIs that are both powerful and reliable. Define your endpoints once with our fluent DSL, and get automatic validation, documentation, and
|
10
|
+
**RapiTapir ๐ฆ** combines the expressiveness of Ruby with the safety of strong typing to create APIs that are both powerful and reliable. Define your endpoints once with our fluent DSL, and get automatic validation, documentation, client generation, and AI-powered features.
|
17
11
|
|
18
|
-
##
|
12
|
+
## ๐ Latest Features (v2.0)
|
19
13
|
|
20
|
-
-
|
21
|
-
-
|
22
|
-
-
|
23
|
-
-
|
24
|
-
-
|
25
|
-
-
|
14
|
+
- **๐ค AI Integration**: Built-in LLM instruction generation, RAG pipelines, and MCP export
|
15
|
+
- **โจ SinatraRapiTapir Base Class**: `class MyAPI < SinatraRapiTapir` - zero-boilerplate API creation
|
16
|
+
- **๐ฏ Enhanced HTTP DSL**: Native GET, POST, PUT, DELETE methods with fluent chaining
|
17
|
+
- **๐ง Zero Configuration**: Automatic extension registration and intelligent defaults
|
18
|
+
- **โก T Shortcut**: Use `T.string` instead of `RapiTapir::Types.string` everywhere
|
19
|
+
- **๐ GitHub Pages Ready**: Modern documentation deployment with GitHub Actions
|
20
|
+
- **๐ CLI Toolkit**: Complete command-line interface for generation, validation, and serving
|
21
|
+
- **๐งช Comprehensive Tests**: 643 tests passing with 67.67% coverage
|
26
22
|
|
27
23
|
## โจ Why RapiTapir?
|
28
24
|
|
29
|
-
- **๐ Type Safety**: Strong typing
|
25
|
+
- **๐ Type Safety**: Strong typing with runtime validation and compile-time confidence
|
30
26
|
- **๐ Auto Documentation**: OpenAPI 3.0 specs generated automatically from your code
|
31
27
|
- **๐ Framework Agnostic**: Works with Sinatra, Rails, and any Rack-based framework
|
32
28
|
- **๐ก๏ธ Production Ready**: Built-in security, observability, and authentication features
|
33
29
|
- **๐ Ruby Native**: Designed specifically for Ruby developers who love clean, readable code
|
34
30
|
- **๐ง Zero Config**: Get started in minutes with sensible defaults
|
35
|
-
-
|
36
|
-
-
|
37
|
-
-
|
38
|
-
- **๏ฟฝ๐ GitHub Pages**: Modern documentation deployment with GitHub Actions
|
31
|
+
- **๐ค AI Powered**: Built-in LLM instruction generation, RAG pipelines, and MCP export
|
32
|
+
- **โก Enhanced DSL**: Clean syntax with `T.string`, HTTP verbs, and resource builders
|
33
|
+
- **๐ CLI Toolkit**: Complete command-line interface for all development tasks
|
39
34
|
|
40
35
|
## ๐ Quick Start
|
41
36
|
|
42
|
-
###
|
43
|
-
|
44
|
-
Add to your Gemfile:
|
37
|
+
### Minimal Example (30 seconds)
|
45
38
|
|
46
39
|
```ruby
|
47
|
-
|
40
|
+
require 'rapitapir'
|
41
|
+
|
42
|
+
class HelloAPI < SinatraRapiTapir
|
43
|
+
# That's it! Zero configuration needed.
|
44
|
+
endpoint(
|
45
|
+
GET('/hello')
|
46
|
+
.query(:name, T.string, description: 'Your name')
|
47
|
+
.ok(T.hash({ "message" => T.string }))
|
48
|
+
.build
|
49
|
+
) { |inputs| { message: "Hello, #{inputs[:name]}!" } }
|
50
|
+
|
51
|
+
run! if __FILE__ == $0
|
52
|
+
end
|
48
53
|
```
|
49
54
|
|
50
|
-
|
55
|
+
**Start server**: `ruby hello_api.rb`
|
56
|
+
**Try it**: `curl "http://localhost:4567/hello?name=World"`
|
57
|
+
**Docs**: `http://localhost:4567/docs`
|
58
|
+
|
59
|
+
### Complete Example (5 minutes)
|
51
60
|
|
52
61
|
```ruby
|
53
|
-
require 'rapitapir'
|
62
|
+
require 'rapitapir'
|
54
63
|
|
55
64
|
class BookAPI < SinatraRapiTapir
|
56
65
|
# Configure API information
|
57
66
|
rapitapir do
|
58
67
|
info(
|
59
|
-
title: 'Book API',
|
60
|
-
description: 'A
|
61
|
-
version: '
|
68
|
+
title: 'Book Management API',
|
69
|
+
description: 'A comprehensive book management system with AI features',
|
70
|
+
version: '2.0.0'
|
62
71
|
)
|
63
|
-
development_defaults! # Auto CORS, docs, health checks
|
72
|
+
development_defaults! # Auto CORS, docs, health checks, metrics
|
64
73
|
end
|
65
74
|
|
66
|
-
# Define
|
75
|
+
# Define schemas using T shortcut (available everywhere!)
|
67
76
|
BOOK_SCHEMA = T.hash({
|
68
77
|
"id" => T.integer,
|
69
78
|
"title" => T.string(min_length: 1, max_length: 255),
|
70
79
|
"author" => T.string(min_length: 1),
|
71
80
|
"published" => T.boolean,
|
72
|
-
"isbn" => T.optional(T.string),
|
73
|
-
"pages" => T.optional(T.integer(minimum: 1))
|
81
|
+
"isbn" => T.optional(T.string(pattern: /^\d{13}$/)),
|
82
|
+
"pages" => T.optional(T.integer(minimum: 1)),
|
83
|
+
"tags" => T.optional(T.array(T.string)),
|
84
|
+
"metadata" => T.optional(T.hash({
|
85
|
+
"genre" => T.string,
|
86
|
+
"rating" => T.float(minimum: 0, maximum: 5)
|
87
|
+
}))
|
74
88
|
})
|
75
89
|
|
76
|
-
#
|
90
|
+
# RESTful resource with full CRUD
|
77
91
|
api_resource '/books', schema: BOOK_SCHEMA do
|
78
92
|
crud do
|
79
|
-
index
|
80
|
-
|
81
|
-
|
82
|
-
|
93
|
+
index do
|
94
|
+
# Automatic pagination and filtering
|
95
|
+
books = Book.all
|
96
|
+
books = books.where(published: true) if params[:published] == 'true'
|
97
|
+
books.limit(params[:limit] || 50).map(&:to_h)
|
83
98
|
end
|
84
99
|
|
100
|
+
show { |inputs| Book.find(inputs[:id])&.to_h || halt(404) }
|
101
|
+
|
85
102
|
create do |inputs|
|
86
|
-
Book.create(inputs[:body])
|
103
|
+
book = Book.create(inputs[:body])
|
104
|
+
status 201
|
105
|
+
book.to_h
|
87
106
|
end
|
107
|
+
|
108
|
+
update { |inputs| Book.update(inputs[:id], inputs[:body]).to_h }
|
109
|
+
destroy { |inputs| Book.delete(inputs[:id]); status 204 }
|
88
110
|
end
|
89
111
|
|
90
|
-
# Custom
|
112
|
+
# Custom endpoints with full type safety
|
91
113
|
custom :get, 'featured' do
|
92
|
-
Book.where(featured: true)
|
114
|
+
Book.where(featured: true).map(&:to_h)
|
115
|
+
end
|
116
|
+
|
117
|
+
custom :post, ':id/reviews' do |inputs|
|
118
|
+
book = Book.find(inputs[:id])
|
119
|
+
review = book.add_review(inputs[:body])
|
120
|
+
status 201
|
121
|
+
review.to_h
|
93
122
|
end
|
94
123
|
end
|
95
124
|
|
96
|
-
#
|
125
|
+
# Advanced search with AI-powered features
|
97
126
|
endpoint(
|
98
127
|
GET('/books/search')
|
99
128
|
.query(:q, T.string(min_length: 1), description: 'Search query')
|
100
129
|
.query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)), description: 'Results limit')
|
101
|
-
.
|
102
|
-
.
|
103
|
-
.
|
104
|
-
.
|
105
|
-
.
|
130
|
+
.query(:ai_powered, T.optional(T.boolean), description: 'Use AI-enhanced search')
|
131
|
+
.summary('Search books with optional AI enhancement')
|
132
|
+
.description('Search books by title, author, or content with optional AI semantic search')
|
133
|
+
.tags('Search', 'AI')
|
134
|
+
.ok(T.hash({
|
135
|
+
"books" => T.array(BOOK_SCHEMA),
|
136
|
+
"total" => T.integer,
|
137
|
+
"ai_enhanced" => T.boolean,
|
138
|
+
"suggestions" => T.optional(T.array(T.string))
|
139
|
+
}))
|
140
|
+
.bad_request(T.hash({ "error" => T.string }))
|
141
|
+
.enable_rag # AI-powered retrieval augmented generation
|
142
|
+
.enable_mcp # Export to Model Context Protocol
|
106
143
|
.build
|
107
144
|
) do |inputs|
|
108
145
|
query = inputs[:q]
|
109
146
|
limit = inputs[:limit] || 20
|
147
|
+
ai_powered = inputs[:ai_powered] || false
|
148
|
+
|
149
|
+
if ai_powered
|
150
|
+
# Use AI-enhanced search
|
151
|
+
results = BookSearchService.ai_search(query, limit: limit)
|
152
|
+
suggestions = BookSearchService.get_suggestions(query)
|
153
|
+
else
|
154
|
+
results = Book.search(query).limit(limit)
|
155
|
+
suggestions = nil
|
156
|
+
end
|
110
157
|
|
111
|
-
|
112
|
-
|
158
|
+
{
|
159
|
+
books: results.map(&:to_h),
|
160
|
+
total: results.count,
|
161
|
+
ai_enhanced: ai_powered,
|
162
|
+
suggestions: suggestions
|
163
|
+
}
|
113
164
|
end
|
114
165
|
|
115
166
|
run! if __FILE__ == $0
|
116
167
|
end
|
117
168
|
```
|
118
169
|
|
119
|
-
Start
|
120
|
-
-
|
121
|
-
-
|
170
|
+
**Start and explore**:
|
171
|
+
- **Server**: `ruby book_api.rb`
|
172
|
+
- **Interactive Docs**: `http://localhost:4567/docs`
|
173
|
+
- **OpenAPI Spec**: `http://localhost:4567/openapi.json`
|
174
|
+
- **Health Check**: `http://localhost:4567/health`
|
175
|
+
## ๐๏ธ Advanced Features
|
122
176
|
|
123
|
-
|
177
|
+
### ๐ค AI-Powered APIs
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class AIBookAPI < SinatraRapiTapir
|
181
|
+
rapitapir do
|
182
|
+
info(title: 'AI-Powered Book API', version: '2.0.0')
|
183
|
+
development_defaults!
|
184
|
+
end
|
185
|
+
|
186
|
+
# Configure AI providers
|
187
|
+
configure do
|
188
|
+
set :openai_api_key, ENV['OPENAI_API_KEY']
|
189
|
+
set :rag_backend, :memory # or :elasticsearch, :postgresql
|
190
|
+
end
|
191
|
+
|
192
|
+
# AI-enhanced book recommendations
|
193
|
+
endpoint(
|
194
|
+
GET('/books/recommendations')
|
195
|
+
.query(:user_id, T.integer, description: 'User ID for personalization')
|
196
|
+
.query(:preferences, T.optional(T.string), description: 'User preferences text')
|
197
|
+
.summary('Get AI-powered book recommendations')
|
198
|
+
.description('Uses LLM to generate personalized book recommendations')
|
199
|
+
.tags('AI', 'Recommendations')
|
200
|
+
.ok(T.hash({
|
201
|
+
"recommendations" => T.array(BOOK_SCHEMA),
|
202
|
+
"reasoning" => T.string,
|
203
|
+
"confidence" => T.float(minimum: 0, maximum: 1)
|
204
|
+
}))
|
205
|
+
.enable_llm_instructions(purpose: :completion) # Generate LLM instructions
|
206
|
+
.enable_rag(
|
207
|
+
retrieval_backend: :memory,
|
208
|
+
llm_provider: :openai,
|
209
|
+
context_window: 4000
|
210
|
+
)
|
211
|
+
.enable_mcp # Export for AI agent consumption
|
212
|
+
.build
|
213
|
+
) do |inputs|
|
214
|
+
user = User.find(inputs[:user_id])
|
215
|
+
preferences = inputs[:preferences] || user.inferred_preferences
|
216
|
+
|
217
|
+
# RAG pipeline automatically injects relevant context
|
218
|
+
recommendations = AIRecommendationService.generate(
|
219
|
+
user: user,
|
220
|
+
preferences: preferences,
|
221
|
+
context: rag_context # Automatically provided by RAG middleware
|
222
|
+
)
|
223
|
+
|
224
|
+
{
|
225
|
+
recommendations: recommendations[:books],
|
226
|
+
reasoning: recommendations[:explanation],
|
227
|
+
confidence: recommendations[:confidence_score]
|
228
|
+
}
|
229
|
+
end
|
230
|
+
|
231
|
+
# LLM instruction generation endpoint
|
232
|
+
endpoint(
|
233
|
+
GET('/ai/instructions')
|
234
|
+
.query(:endpoint_id, T.string, description: 'Endpoint ID to generate instructions for')
|
235
|
+
.query(:purpose, T.string(enum: %w[validation transformation analysis documentation testing completion]),
|
236
|
+
description: 'Instruction purpose')
|
237
|
+
.summary('Generate LLM instructions for endpoints')
|
238
|
+
.ok(T.hash({
|
239
|
+
"instructions" => T.string,
|
240
|
+
"metadata" => T.hash({
|
241
|
+
"endpoint" => T.string,
|
242
|
+
"purpose" => T.string,
|
243
|
+
"generated_at" => T.datetime
|
244
|
+
})
|
245
|
+
}))
|
246
|
+
.build
|
247
|
+
) do |inputs|
|
248
|
+
generator = RapiTapir::AI::LLMInstruction::Generator.new
|
249
|
+
endpoint = find_endpoint(inputs[:endpoint_id])
|
250
|
+
|
251
|
+
instructions = generator.generate_instructions(
|
252
|
+
endpoint: endpoint,
|
253
|
+
purpose: inputs[:purpose].to_sym
|
254
|
+
)
|
255
|
+
|
256
|
+
{
|
257
|
+
instructions: instructions,
|
258
|
+
metadata: {
|
259
|
+
endpoint: endpoint.summary || endpoint.path,
|
260
|
+
purpose: inputs[:purpose],
|
261
|
+
generated_at: Time.now
|
262
|
+
}
|
263
|
+
}
|
264
|
+
end
|
265
|
+
|
266
|
+
# Export Model Context Protocol (MCP) for AI agents
|
267
|
+
endpoint(
|
268
|
+
GET('/mcp/export')
|
269
|
+
.summary('Export API for AI agent consumption')
|
270
|
+
.description('Generates MCP-compatible JSON for AI agents and development tools')
|
271
|
+
.tags('AI', 'MCP', 'Developer Tools')
|
272
|
+
.ok(T.hash({
|
273
|
+
"service" => T.hash({
|
274
|
+
"name" => T.string,
|
275
|
+
"version" => T.string,
|
276
|
+
"description" => T.string
|
277
|
+
}),
|
278
|
+
"endpoints" => T.array(T.hash({
|
279
|
+
"id" => T.string,
|
280
|
+
"method" => T.string,
|
281
|
+
"path" => T.string,
|
282
|
+
"summary" => T.string,
|
283
|
+
"input_schema" => T.hash({}),
|
284
|
+
"output_schema" => T.hash({})
|
285
|
+
})),
|
286
|
+
"generated_at" => T.datetime
|
287
|
+
}))
|
288
|
+
.build
|
289
|
+
) do
|
290
|
+
exporter = RapiTapir::AI::MCP::Exporter.new(rapitapir_endpoints)
|
291
|
+
JSON.parse(exporter.export_json(pretty: true))
|
292
|
+
end
|
293
|
+
|
294
|
+
run! if __FILE__ == $0
|
295
|
+
end
|
296
|
+
```
|
297
|
+
|
298
|
+
### ๐ OAuth2 + Auth0 Integration
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
class SecureAPI < SinatraRapiTapir
|
302
|
+
rapitapir do
|
303
|
+
info(title: 'Secure API with Auth0', version: '2.0.0')
|
304
|
+
|
305
|
+
# Configure Auth0 OAuth2
|
306
|
+
oauth2_auth0 :auth0,
|
307
|
+
domain: ENV['AUTH0_DOMAIN'],
|
308
|
+
audience: ENV['AUTH0_AUDIENCE'],
|
309
|
+
realm: 'API Access'
|
310
|
+
|
311
|
+
production_defaults! # Security headers, rate limiting, etc.
|
312
|
+
end
|
313
|
+
|
314
|
+
# Public endpoints
|
315
|
+
endpoint(
|
316
|
+
GET('/public/status')
|
317
|
+
.summary('Public API status')
|
318
|
+
.ok(T.hash({
|
319
|
+
"status" => T.string,
|
320
|
+
"version" => T.string,
|
321
|
+
"authenticated" => T.boolean
|
322
|
+
}))
|
323
|
+
.build
|
324
|
+
) do
|
325
|
+
{
|
326
|
+
status: 'operational',
|
327
|
+
version: '2.0.0',
|
328
|
+
authenticated: authenticated?
|
329
|
+
}
|
330
|
+
end
|
331
|
+
|
332
|
+
# Protected endpoints with scope requirements
|
333
|
+
endpoint(
|
334
|
+
GET('/users/profile')
|
335
|
+
.summary('Get current user profile')
|
336
|
+
.description('Requires valid Auth0 token with read:profile scope')
|
337
|
+
.bearer_auth(scopes: ['read:profile'])
|
338
|
+
.tags('Users', 'Profile')
|
339
|
+
.ok(T.hash({
|
340
|
+
"user" => T.hash({
|
341
|
+
"id" => T.string,
|
342
|
+
"email" => T.string,
|
343
|
+
"name" => T.string,
|
344
|
+
"roles" => T.array(T.string),
|
345
|
+
"permissions" => T.array(T.string)
|
346
|
+
}),
|
347
|
+
"metadata" => T.hash({
|
348
|
+
"last_login" => T.datetime,
|
349
|
+
"login_count" => T.integer
|
350
|
+
})
|
351
|
+
}))
|
352
|
+
.error_response(401, T.hash({ "error" => T.string }), description: 'Unauthorized')
|
353
|
+
.error_response(403, T.hash({ "error" => T.string }), description: 'Insufficient permissions')
|
354
|
+
.build
|
355
|
+
) do |inputs|
|
356
|
+
# Current user automatically available from Auth0 context
|
357
|
+
user_info = current_auth_context[:user_info]
|
358
|
+
|
359
|
+
{
|
360
|
+
user: {
|
361
|
+
id: user_info['sub'],
|
362
|
+
email: user_info['email'],
|
363
|
+
name: user_info['name'],
|
364
|
+
roles: user_info['roles'] || [],
|
365
|
+
permissions: current_auth_context[:permissions] || []
|
366
|
+
},
|
367
|
+
metadata: {
|
368
|
+
last_login: Time.parse(user_info['updated_at']),
|
369
|
+
login_count: user_info['logins_count'] || 0
|
370
|
+
}
|
371
|
+
}
|
372
|
+
end
|
373
|
+
|
374
|
+
# Admin-only endpoints
|
375
|
+
endpoint(
|
376
|
+
DELETE('/admin/users/:user_id')
|
377
|
+
.summary('Delete user (admin only)')
|
378
|
+
.path_param(:user_id, T.string, description: 'User ID to delete')
|
379
|
+
.bearer_auth(scopes: ['delete:users', 'admin'])
|
380
|
+
.tags('Admin', 'Users')
|
381
|
+
.ok(T.hash({ "message" => T.string, "deleted_user_id" => T.string }))
|
382
|
+
.error_response(401, T.hash({ "error" => T.string }))
|
383
|
+
.error_response(403, T.hash({ "error" => T.string }))
|
384
|
+
.error_response(404, T.hash({ "error" => T.string }))
|
385
|
+
.build
|
386
|
+
) do |inputs|
|
387
|
+
# Verify admin scope
|
388
|
+
require_scope!('admin')
|
389
|
+
|
390
|
+
user_id = inputs[:user_id]
|
391
|
+
deleted_user = UserService.delete(user_id)
|
392
|
+
|
393
|
+
halt 404, { error: 'User not found' }.to_json unless deleted_user
|
394
|
+
|
395
|
+
{
|
396
|
+
message: 'User successfully deleted',
|
397
|
+
deleted_user_id: user_id
|
398
|
+
}
|
399
|
+
end
|
400
|
+
|
401
|
+
# Scope-based middleware protection
|
402
|
+
protect_with_oauth2 scopes: ['api:access'] do
|
403
|
+
# All endpoints in this block require the api:access scope
|
404
|
+
|
405
|
+
endpoint(
|
406
|
+
GET('/protected/data')
|
407
|
+
.summary('Get protected data')
|
408
|
+
.ok(T.array(T.hash({ "id" => T.integer, "data" => T.string })))
|
409
|
+
.build
|
410
|
+
) { ProtectedData.all.map(&:to_h) }
|
411
|
+
end
|
412
|
+
|
413
|
+
run! if __FILE__ == $0
|
414
|
+
end
|
415
|
+
```
|
416
|
+
|
417
|
+
### ๐ Production Observability (OpenTelemetry + Honeycomb)
|
418
|
+
|
419
|
+
```ruby
|
420
|
+
class MonitoredAPI < SinatraRapiTapir
|
421
|
+
rapitapir do
|
422
|
+
info(title: 'Production API with Full Observability', version: '2.0.0')
|
423
|
+
|
424
|
+
# Configure comprehensive observability
|
425
|
+
enable_observability do |config|
|
426
|
+
# Health checks with custom checks
|
427
|
+
config.health_checks.enable(path: '/health')
|
428
|
+
config.health_checks.add_check('database') { DatabaseHealthCheck.new }
|
429
|
+
config.health_checks.add_check('redis') { RedisHealthCheck.new }
|
430
|
+
config.health_checks.add_check('external_api') { ExternalAPIHealthCheck.new }
|
431
|
+
|
432
|
+
# OpenTelemetry tracing
|
433
|
+
config.tracing.enable_opentelemetry(
|
434
|
+
service_name: 'book-api',
|
435
|
+
service_version: '2.0.0',
|
436
|
+
environment: ENV['RAILS_ENV'] || 'development',
|
437
|
+
exporters: [:otlp, :honeycomb]
|
438
|
+
)
|
439
|
+
|
440
|
+
# Prometheus metrics
|
441
|
+
config.metrics.enable_prometheus(
|
442
|
+
namespace: 'book_api',
|
443
|
+
default_labels: {
|
444
|
+
service: 'book-api',
|
445
|
+
version: '2.0.0',
|
446
|
+
environment: ENV['RAILS_ENV'] || 'development'
|
447
|
+
}
|
448
|
+
)
|
449
|
+
|
450
|
+
# Structured logging
|
451
|
+
config.logging.enable_structured(
|
452
|
+
level: ENV['LOG_LEVEL'] || 'info',
|
453
|
+
format: :json,
|
454
|
+
additional_fields: {
|
455
|
+
service: 'book-api',
|
456
|
+
version: '2.0.0'
|
457
|
+
}
|
458
|
+
)
|
459
|
+
end
|
460
|
+
|
461
|
+
production_defaults!
|
462
|
+
end
|
463
|
+
|
464
|
+
# Monitored endpoint with custom metrics and tracing
|
465
|
+
endpoint(
|
466
|
+
GET('/books/:id')
|
467
|
+
.path_param(:id, T.integer, description: 'Book ID')
|
468
|
+
.query(:include, T.optional(T.array(T.string)), description: 'Related data to include')
|
469
|
+
.summary('Get book with full observability')
|
470
|
+
.description('Demonstrates comprehensive monitoring, tracing, and metrics')
|
471
|
+
.tags('Books', 'Monitoring')
|
472
|
+
.ok(BOOK_SCHEMA)
|
473
|
+
.error_response(404, T.hash({ "error" => T.string, "book_id" => T.integer }))
|
474
|
+
.error_response(500, T.hash({ "error" => T.string, "trace_id" => T.string }))
|
475
|
+
.with_metrics('book_requests', labels: { operation: 'get_by_id' })
|
476
|
+
.with_tracing('fetch_book', tags: { book_operation: 'get' })
|
477
|
+
.build
|
478
|
+
) do |inputs|
|
479
|
+
book_id = inputs[:id]
|
480
|
+
includes = inputs[:include] || []
|
481
|
+
|
482
|
+
# Custom span for database operation
|
483
|
+
OpenTelemetry.tracer.in_span('database.book.find', attributes: { 'book.id' => book_id }) do |span|
|
484
|
+
start_time = Time.now
|
485
|
+
|
486
|
+
begin
|
487
|
+
book = Book.find(book_id)
|
488
|
+
|
489
|
+
# Record custom metrics
|
490
|
+
Prometheus.increment('book_lookups_total', labels: { found: 'true' })
|
491
|
+
Prometheus.observe('book_lookup_duration_seconds', Time.now - start_time)
|
492
|
+
|
493
|
+
# Add trace attributes
|
494
|
+
span.set_attribute('book.title', book.title)
|
495
|
+
span.set_attribute('book.author', book.author)
|
496
|
+
span.set_attribute('includes.count', includes.length)
|
497
|
+
|
498
|
+
halt 404, { error: 'Book not found', book_id: book_id }.to_json unless book
|
499
|
+
|
500
|
+
# Handle includes with additional tracing
|
501
|
+
if includes.any?
|
502
|
+
OpenTelemetry.tracer.in_span('book.load_includes') do |include_span|
|
503
|
+
include_span.set_attribute('includes', includes.join(','))
|
504
|
+
book = book.with_includes(includes)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# Log structured data
|
509
|
+
logger.info('Book retrieved successfully', {
|
510
|
+
book_id: book_id,
|
511
|
+
includes: includes,
|
512
|
+
duration_ms: ((Time.now - start_time) * 1000).round(2),
|
513
|
+
trace_id: span.context.trace_id.unpack1('H*')
|
514
|
+
})
|
515
|
+
|
516
|
+
book.to_h
|
517
|
+
|
518
|
+
rescue StandardError => e
|
519
|
+
# Record error metrics
|
520
|
+
Prometheus.increment('book_lookup_errors_total', labels: { error_type: e.class.name })
|
521
|
+
|
522
|
+
# Add error to span
|
523
|
+
span.record_exception(e)
|
524
|
+
span.status = OpenTelemetry::Trace::Status.error('Book lookup failed')
|
525
|
+
|
526
|
+
# Log error with context
|
527
|
+
logger.error('Book lookup failed', {
|
528
|
+
book_id: book_id,
|
529
|
+
error: e.message,
|
530
|
+
error_class: e.class.name,
|
531
|
+
trace_id: span.context.trace_id.unpack1('H*')
|
532
|
+
})
|
533
|
+
|
534
|
+
halt 500, {
|
535
|
+
error: 'Internal server error',
|
536
|
+
trace_id: span.context.trace_id.unpack1('H*')
|
537
|
+
}.to_json
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
# Performance monitoring endpoint
|
543
|
+
endpoint(
|
544
|
+
GET('/monitoring/performance')
|
545
|
+
.summary('Get API performance metrics')
|
546
|
+
.description('Returns real-time performance and health metrics')
|
547
|
+
.tags('Monitoring', 'Performance')
|
548
|
+
.bearer_auth(scopes: ['monitoring:read'])
|
549
|
+
.ok(T.hash({
|
550
|
+
"metrics" => T.hash({
|
551
|
+
"request_rate" => T.float,
|
552
|
+
"error_rate" => T.float,
|
553
|
+
"average_response_time" => T.float,
|
554
|
+
"p95_response_time" => T.float
|
555
|
+
}),
|
556
|
+
"health" => T.hash({
|
557
|
+
"overall_status" => T.string,
|
558
|
+
"checks" => T.hash({})
|
559
|
+
}),
|
560
|
+
"trace_sample" => T.optional(T.hash({
|
561
|
+
"trace_id" => T.string,
|
562
|
+
"span_count" => T.integer,
|
563
|
+
"duration_ms" => T.float
|
564
|
+
}))
|
565
|
+
}))
|
566
|
+
.build
|
567
|
+
) do
|
568
|
+
{
|
569
|
+
metrics: {
|
570
|
+
request_rate: MetricsCollector.request_rate_per_second,
|
571
|
+
error_rate: MetricsCollector.error_rate_percentage,
|
572
|
+
average_response_time: MetricsCollector.average_response_time_ms,
|
573
|
+
p95_response_time: MetricsCollector.p95_response_time_ms
|
574
|
+
},
|
575
|
+
health: {
|
576
|
+
overall_status: HealthChecker.overall_status,
|
577
|
+
checks: HealthChecker.detailed_status
|
578
|
+
},
|
579
|
+
trace_sample: TracingCollector.recent_trace_sample
|
580
|
+
}
|
581
|
+
end
|
582
|
+
|
583
|
+
run! if __FILE__ == $0
|
584
|
+
end
|
585
|
+
```
|
586
|
+
|
587
|
+
### ๐ ๏ธ CLI Development Toolkit
|
588
|
+
|
589
|
+
RapiTapir includes a comprehensive CLI for all development tasks:
|
590
|
+
|
591
|
+
```bash
|
592
|
+
# Generate and validate
|
593
|
+
rapitapir generate openapi --output api-spec.json
|
594
|
+
rapitapir generate client typescript --output client/api.ts
|
595
|
+
rapitapir generate docs markdown --output API.md
|
596
|
+
rapitapir validate endpoints --file my_api.rb
|
597
|
+
|
598
|
+
# AI-powered features
|
599
|
+
rapitapir llm generate --endpoint-id "get_books" --purpose validation
|
600
|
+
rapitapir llm export --format instructions --output ai-prompts/
|
601
|
+
rapitapir mcp export --output mcp-context.json
|
602
|
+
|
603
|
+
# Development server
|
604
|
+
rapitapir serve --port 3000 --docs-path /documentation
|
605
|
+
rapitapir docs --serve --port 8080
|
606
|
+
|
607
|
+
# Project scaffolding
|
608
|
+
rapitapir new my-api --template sinatra-ai
|
609
|
+
rapitapir generate scaffold books --with-auth --with-ai
|
610
|
+
```
|
124
611
|
|
125
612
|
## ๐๏ธ Core Features
|
126
613
|
|
127
|
-
###
|
614
|
+
### โจ SinatraRapiTapir Base Class
|
128
615
|
|
129
|
-
|
616
|
+
The cleanest way to create APIs with zero boilerplate:
|
130
617
|
|
131
618
|
```ruby
|
132
619
|
require 'rapitapir'
|
133
620
|
|
134
621
|
class MyAPI < SinatraRapiTapir
|
135
622
|
rapitapir do
|
136
|
-
info(title: 'My API', version: '
|
137
|
-
development_defaults! # Auto CORS, docs, health checks
|
623
|
+
info(title: 'My API', version: '2.0.0')
|
624
|
+
development_defaults! # Auto CORS, docs, health checks, metrics
|
138
625
|
end
|
139
626
|
|
140
|
-
# Enhanced HTTP verb DSL
|
627
|
+
# Enhanced HTTP verb DSL + T shortcut automatically available
|
141
628
|
endpoint(
|
142
|
-
GET('/
|
143
|
-
.summary('List all
|
144
|
-
.
|
145
|
-
.
|
629
|
+
GET('/items')
|
630
|
+
.summary('List all items')
|
631
|
+
.query(:filter, T.optional(T.string), description: 'Filter criteria')
|
632
|
+
.ok(T.array(T.hash({ "id" => T.integer, "name" => T.string })))
|
146
633
|
.build
|
147
|
-
) {
|
634
|
+
) { |inputs| Item.filtered(inputs[:filter]).map(&:to_h) }
|
148
635
|
end
|
149
636
|
```
|
150
637
|
|
151
|
-
### Type-Safe API Design
|
638
|
+
### ๐ Type-Safe API Design
|
152
639
|
|
153
|
-
Define
|
640
|
+
Define schemas once, use everywhere with runtime validation:
|
154
641
|
|
155
642
|
```ruby
|
156
|
-
# T shortcut
|
643
|
+
# T shortcut available globally - no imports needed!
|
157
644
|
USER_SCHEMA = T.hash({
|
158
645
|
"id" => T.integer,
|
159
646
|
"name" => T.string(min_length: 1, max_length: 100),
|
160
647
|
"email" => T.email,
|
161
|
-
"age" => T.optional(T.integer(
|
162
|
-
"
|
163
|
-
"
|
164
|
-
"
|
165
|
-
|
648
|
+
"age" => T.optional(T.integer(minimum: 0, maximum: 150)),
|
649
|
+
"preferences" => T.optional(T.hash({
|
650
|
+
"theme" => T.string(enum: %w[light dark auto]),
|
651
|
+
"notifications" => T.boolean,
|
652
|
+
"languages" => T.array(T.string)
|
653
|
+
})),
|
654
|
+
"metadata" => T.optional(T.hash({})) # Free-form metadata
|
655
|
+
})
|
656
|
+
|
657
|
+
# Auto-derive schemas from existing data
|
658
|
+
INFERRED_SCHEMA = T.from_hash({
|
659
|
+
id: 1,
|
660
|
+
name: "John Doe",
|
661
|
+
active: true,
|
662
|
+
tags: ["admin", "power-user"]
|
166
663
|
})
|
167
664
|
```
|
168
665
|
|
169
|
-
###
|
666
|
+
### ๐ Enhanced HTTP Verb DSL
|
170
667
|
|
171
|
-
|
668
|
+
Fluent, chainable endpoint definitions:
|
172
669
|
|
173
670
|
```ruby
|
174
|
-
#
|
671
|
+
# All HTTP verbs available with full type safety
|
175
672
|
endpoint(
|
176
|
-
|
177
|
-
.summary('
|
178
|
-
.
|
179
|
-
.
|
180
|
-
.
|
181
|
-
.
|
182
|
-
.
|
673
|
+
POST('/users')
|
674
|
+
.summary('Create a new user')
|
675
|
+
.description('Creates a user with validation and returns the created resource')
|
676
|
+
.tags('Users', 'CRUD')
|
677
|
+
.body(USER_SCHEMA, description: 'User data to create')
|
678
|
+
.header('X-Request-ID', T.optional(T.string), description: 'Request tracking ID')
|
679
|
+
.ok(USER_SCHEMA, description: 'Successfully created user')
|
680
|
+
.error_response(400, T.hash({
|
183
681
|
"error" => T.string,
|
184
|
-
"
|
682
|
+
"validation_errors" => T.array(T.hash({
|
185
683
|
"field" => T.string,
|
186
|
-
"message" => T.string
|
684
|
+
"message" => T.string,
|
685
|
+
"code" => T.string
|
187
686
|
}))
|
188
|
-
}))
|
687
|
+
}), description: 'Validation failed')
|
688
|
+
.error_response(409, T.hash({ "error" => T.string }), description: 'User already exists')
|
189
689
|
.build
|
190
690
|
) do |inputs|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
691
|
+
begin
|
692
|
+
user = UserService.create(inputs[:body])
|
693
|
+
status 201
|
694
|
+
user.to_h
|
695
|
+
rescue ValidationError => e
|
696
|
+
halt 400, {
|
697
|
+
error: 'Validation failed',
|
698
|
+
validation_errors: e.details
|
699
|
+
}.to_json
|
700
|
+
rescue ConflictError => e
|
701
|
+
halt 409, { error: e.message }.to_json
|
197
702
|
end
|
198
|
-
|
199
|
-
user.to_h
|
200
703
|
end
|
201
704
|
```
|
202
705
|
|
203
|
-
### RESTful Resource Builder
|
706
|
+
### ๐ญ RESTful Resource Builder
|
204
707
|
|
205
|
-
|
708
|
+
Complete CRUD APIs with minimal code:
|
206
709
|
|
207
710
|
```ruby
|
208
|
-
# Enhanced resource builder with custom validations and relationships
|
209
711
|
api_resource '/users', schema: USER_SCHEMA do
|
210
|
-
|
211
|
-
|
212
|
-
|
712
|
+
# Configure resource-level settings
|
713
|
+
configure do
|
714
|
+
pagination default_limit: 25, max_limit: 100
|
715
|
+
filtering allow: [:name, :email, :active]
|
716
|
+
sorting allow: [:name, :created_at, :updated_at]
|
717
|
+
end
|
718
|
+
|
719
|
+
crud except: [:destroy] do # Exclude dangerous operations
|
720
|
+
index do |inputs|
|
213
721
|
users = User.all
|
214
|
-
users = users.where(active: true) if
|
215
|
-
users.
|
722
|
+
users = users.where(active: true) if inputs[:filter_active]
|
723
|
+
users = users.search(inputs[:search]) if inputs[:search]
|
724
|
+
|
725
|
+
paginated_response(
|
726
|
+
data: users.map(&:to_h),
|
727
|
+
page: inputs[:page] || 1,
|
728
|
+
per_page: inputs[:per_page] || 25,
|
729
|
+
total: users.count
|
730
|
+
)
|
216
731
|
end
|
217
732
|
|
218
|
-
show { |inputs| User.find(inputs[:id]) }
|
733
|
+
show { |inputs| User.find(inputs[:id])&.to_h || halt(404) }
|
219
734
|
|
220
735
|
create do |inputs|
|
221
|
-
user = User.create(inputs[:body])
|
736
|
+
user = User.create!(inputs[:body])
|
222
737
|
status 201
|
738
|
+
location "/users/#{user.id}"
|
223
739
|
user.to_h
|
224
740
|
end
|
225
741
|
|
226
|
-
update
|
227
|
-
|
742
|
+
update do |inputs|
|
743
|
+
user = User.find(inputs[:id]) || halt(404)
|
744
|
+
user.update!(inputs[:body])
|
745
|
+
user.to_h
|
746
|
+
end
|
228
747
|
end
|
229
748
|
|
230
|
-
#
|
231
|
-
custom :
|
232
|
-
User.
|
749
|
+
# Custom endpoints with full inheritance of resource configuration
|
750
|
+
custom :post, ':id/avatar' do |inputs|
|
751
|
+
user = User.find(inputs[:id]) || halt(404)
|
752
|
+
avatar = user.update_avatar(inputs[:body][:avatar_data])
|
753
|
+
{ avatar_url: avatar.url, updated_at: avatar.updated_at }
|
233
754
|
end
|
234
755
|
|
235
|
-
custom :
|
236
|
-
|
237
|
-
|
238
|
-
{
|
756
|
+
custom :get, 'search/advanced' do |inputs|
|
757
|
+
# Advanced search with multiple criteria
|
758
|
+
results = UserSearchService.advanced_search(inputs)
|
759
|
+
{ users: results.map(&:to_h), search_metadata: results.metadata }
|
239
760
|
end
|
240
761
|
end
|
241
762
|
```
|
242
763
|
|
243
|
-
### Automatic
|
764
|
+
### ๐ Automatic Documentation
|
244
765
|
|
245
|
-
Your API documentation is always up-to-date
|
766
|
+
Your API documentation is always up-to-date:
|
246
767
|
|
247
|
-
- **Interactive Swagger UI
|
248
|
-
- **Complete OpenAPI 3.0 specification
|
249
|
-
- **TypeScript
|
250
|
-
- **Markdown
|
768
|
+
- **Interactive Swagger UI**: Try endpoints directly from the browser
|
769
|
+
- **Complete OpenAPI 3.0**: Full specification with schemas, examples, security
|
770
|
+
- **TypeScript clients**: Auto-generated for frontend teams
|
771
|
+
- **Markdown docs**: Perfect for wikis and README files
|
772
|
+
- **CLI generation**: `rapitapir generate docs --format html`
|
251
773
|
|
252
774
|
## ๐ง Framework Integration
|
253
775
|
|
254
776
|
### Sinatra (Recommended)
|
255
777
|
|
256
|
-
**Option 1:
|
778
|
+
**Option 1: SinatraRapiTapir Base Class**
|
257
779
|
```ruby
|
258
780
|
require 'rapitapir'
|
259
781
|
|
260
782
|
class MyAPI < SinatraRapiTapir
|
261
783
|
rapitapir do
|
262
|
-
info(title: 'My API', version: '
|
784
|
+
info(title: 'My API', version: '2.0.0')
|
263
785
|
development_defaults!
|
264
786
|
end
|
265
|
-
# Enhanced HTTP verb DSL automatically available
|
787
|
+
# Enhanced HTTP verb DSL + T shortcut automatically available
|
266
788
|
end
|
267
789
|
```
|
268
790
|
|
269
|
-
**Option 2: Manual Extension
|
791
|
+
**Option 2: Manual Extension**
|
270
792
|
```ruby
|
271
793
|
require 'rapitapir/sinatra/extension'
|
272
794
|
|
273
795
|
class MyAPI < Sinatra::Base
|
274
796
|
register RapiTapir::Sinatra::Extension
|
275
|
-
# Use
|
797
|
+
# Use full DSL manually...
|
798
|
+
end
|
799
|
+
```
|
800
|
+
|
801
|
+
### Rails Integration
|
802
|
+
|
803
|
+
```ruby
|
804
|
+
# app/controllers/api/base_controller.rb
|
805
|
+
class Api::BaseController < ApplicationController
|
806
|
+
include RapiTapir::Server::Rails::ControllerBase
|
807
|
+
|
808
|
+
rapitapir do
|
809
|
+
info(title: 'Rails API', version: '1.0.0')
|
810
|
+
bearer_auth :jwt
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
# app/controllers/api/users_controller.rb
|
815
|
+
class Api::UsersController < Api::BaseController
|
816
|
+
endpoint(
|
817
|
+
GET('/users')
|
818
|
+
.summary('List users')
|
819
|
+
.ok(T.array(USER_SCHEMA))
|
820
|
+
.build
|
821
|
+
) { User.all.map(&:to_h) }
|
276
822
|
end
|
277
823
|
```
|
278
824
|
|
@@ -282,204 +828,742 @@ end
|
|
282
828
|
require 'rapitapir/server/rack_adapter'
|
283
829
|
|
284
830
|
class MyRackApp
|
285
|
-
|
286
|
-
|
831
|
+
include RapiTapir::Server::RackAdapter
|
832
|
+
|
833
|
+
def initialize
|
834
|
+
register_endpoints
|
835
|
+
end
|
836
|
+
|
837
|
+
private
|
838
|
+
|
839
|
+
def register_endpoints
|
840
|
+
endpoint(GET('/status').ok(T.hash({ "status" => T.string })).build) do
|
841
|
+
{ status: 'ok' }
|
842
|
+
end
|
843
|
+
end
|
844
|
+
end
|
845
|
+
```
|
846
|
+
|
847
|
+
## โ๏ธ Serverless Deployment
|
848
|
+
|
849
|
+
RapiTapir APIs can be deployed as serverless functions across all major cloud providers with zero configuration changes. The same SinatraRapiTapir code runs seamlessly on AWS Lambda, Google Cloud Functions, Azure Functions, and Vercel.
|
850
|
+
|
851
|
+
### ๐ AWS Lambda
|
852
|
+
|
853
|
+
```ruby
|
854
|
+
require 'rapitapir'
|
855
|
+
|
856
|
+
class ServerlessAPI < SinatraRapiTapir
|
857
|
+
rapitapir do
|
858
|
+
info(title: 'Serverless API', version: '1.0.0')
|
859
|
+
development_defaults!
|
287
860
|
end
|
861
|
+
|
862
|
+
endpoint(GET('/hello').query(:name, T.string).ok(T.hash({"message" => T.string})).build) do |inputs|
|
863
|
+
{ message: "Hello from Lambda, #{inputs[:name]}!" }
|
864
|
+
end
|
865
|
+
end
|
866
|
+
|
867
|
+
# Lambda handler
|
868
|
+
def lambda_handler(event:, context:)
|
869
|
+
rack_env = build_rack_env_from_api_gateway(event, context)
|
870
|
+
app = ServerlessAPI.new
|
871
|
+
status, headers, body = app.call(rack_env)
|
872
|
+
build_api_gateway_response(status, headers, body)
|
873
|
+
end
|
874
|
+
```
|
875
|
+
|
876
|
+
**Deploy with AWS SAM**:
|
877
|
+
```bash
|
878
|
+
sam build && sam deploy
|
879
|
+
```
|
880
|
+
|
881
|
+
### โ๏ธ Google Cloud Functions
|
882
|
+
|
883
|
+
```ruby
|
884
|
+
require 'functions_framework'
|
885
|
+
|
886
|
+
FunctionsFramework.http('my_api') do |request|
|
887
|
+
rack_env = build_rack_env_from_cloud_functions(request)
|
888
|
+
app = ServerlessAPI.new
|
889
|
+
app.call(rack_env)
|
288
890
|
end
|
289
891
|
```
|
290
892
|
|
291
|
-
|
893
|
+
**Deploy**:
|
894
|
+
```bash
|
895
|
+
gcloud functions deploy my-api --runtime ruby32 --trigger-http
|
896
|
+
```
|
897
|
+
|
898
|
+
### ๐ Vercel Edge Functions
|
292
899
|
|
293
900
|
```ruby
|
294
|
-
|
295
|
-
|
901
|
+
def handler(request:, response:)
|
902
|
+
rack_env = build_rack_env_from_vercel(request)
|
903
|
+
app = ServerlessAPI.new
|
904
|
+
status, headers, body = app.call(rack_env)
|
905
|
+
|
906
|
+
response.status = status
|
907
|
+
headers.each { |k, v| response[k] = v }
|
908
|
+
response.write(body.join)
|
909
|
+
end
|
910
|
+
```
|
911
|
+
|
912
|
+
**Deploy**:
|
913
|
+
```bash
|
914
|
+
vercel --prod
|
296
915
|
```
|
297
916
|
|
917
|
+
### Key Benefits
|
918
|
+
|
919
|
+
- **๐ Zero Cold Start Impact**: Optimized for fast function startup
|
920
|
+
- **๐ฐ Cost Effective**: Pay only for actual requests
|
921
|
+
- **๐ Global Scale**: Deploy to edge locations worldwide
|
922
|
+
- **๐ Built-in Security**: Leverage cloud provider security
|
923
|
+
- **๐ Native Monitoring**: Integrate with cloud monitoring
|
924
|
+
|
925
|
+
### Complete Examples
|
926
|
+
|
927
|
+
See [examples/serverless/](examples/serverless/) for production-ready examples including:
|
928
|
+
|
929
|
+
- **AWS Lambda + API Gateway**: Full SAM template with DynamoDB
|
930
|
+
- **Google Cloud Functions**: With Firestore integration
|
931
|
+
- **Azure Functions**: With Cosmos DB and Service Bus
|
932
|
+
- **Vercel Edge**: With global edge caching
|
933
|
+
- **Multi-cloud deployment**: GitHub Actions workflows
|
934
|
+
|
298
935
|
## ๐ก๏ธ Production Features
|
299
936
|
|
300
|
-
### Authentication & Authorization
|
937
|
+
### ๐ Authentication & Authorization
|
301
938
|
|
302
939
|
```ruby
|
303
|
-
# Bearer token authentication with enhanced syntax
|
304
940
|
class SecureAPI < SinatraRapiTapir
|
305
941
|
rapitapir do
|
306
|
-
info(title: 'Secure API', version: '
|
942
|
+
info(title: 'Secure API', version: '2.0.0')
|
943
|
+
|
944
|
+
# Multiple auth schemes supported
|
307
945
|
bearer_auth :api_key, realm: 'API'
|
946
|
+
oauth2_auth0 :auth0, domain: ENV['AUTH0_DOMAIN'], audience: ENV['AUTH0_AUDIENCE']
|
947
|
+
basic_auth :admin, realm: 'Admin Panel'
|
948
|
+
|
949
|
+
production_defaults! # Security headers, rate limiting, HTTPS enforcement
|
950
|
+
end
|
951
|
+
|
952
|
+
# Scope-based protection
|
953
|
+
protect_with_oauth2 scopes: ['api:read'] do
|
954
|
+
endpoint(GET('/protected/data').ok(T.array(T.hash({}))).build) { ProtectedData.all }
|
955
|
+
end
|
956
|
+
|
957
|
+
# Fine-grained permissions
|
958
|
+
endpoint(
|
959
|
+
DELETE('/admin/users/:id')
|
960
|
+
.path_param(:id, T.integer)
|
961
|
+
.bearer_auth(scopes: ['admin', 'users:delete'])
|
962
|
+
.ok(T.hash({ "message" => T.string }))
|
963
|
+
.build
|
964
|
+
) do |inputs|
|
965
|
+
require_scope!('admin') # Additional runtime check
|
966
|
+
UserService.delete(inputs[:id])
|
967
|
+
{ message: 'User deleted successfully' }
|
968
|
+
end
|
969
|
+
end
|
970
|
+
```
|
971
|
+
|
972
|
+
### ๐ Observability & Monitoring
|
973
|
+
|
974
|
+
```ruby
|
975
|
+
class MonitoredAPI < SinatraRapiTapir
|
976
|
+
rapitapir do
|
977
|
+
info(title: 'Production API', version: '2.0.0')
|
978
|
+
|
979
|
+
enable_observability do |config|
|
980
|
+
# Health checks with custom probes
|
981
|
+
config.health_checks.enable(path: '/health')
|
982
|
+
config.health_checks.add_check('database') { Database.healthy? }
|
983
|
+
config.health_checks.add_check('redis') { Redis.current.ping == 'PONG' }
|
984
|
+
|
985
|
+
# OpenTelemetry integration
|
986
|
+
config.tracing.enable_opentelemetry(
|
987
|
+
service_name: 'my-api',
|
988
|
+
exporters: [:honeycomb, :jaeger],
|
989
|
+
sample_rate: 0.1
|
990
|
+
)
|
991
|
+
|
992
|
+
# Prometheus metrics
|
993
|
+
config.metrics.enable_prometheus(
|
994
|
+
namespace: 'my_api',
|
995
|
+
path: '/metrics'
|
996
|
+
)
|
997
|
+
|
998
|
+
# Structured logging
|
999
|
+
config.logging.enable_structured(format: :json)
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
# Endpoints with observability
|
1004
|
+
endpoint(
|
1005
|
+
GET('/monitored-endpoint')
|
1006
|
+
.with_metrics('endpoint_requests', labels: { operation: 'get' })
|
1007
|
+
.with_tracing('fetch_data')
|
1008
|
+
.ok(T.hash({}))
|
1009
|
+
.build
|
1010
|
+
) do
|
1011
|
+
# Custom spans and metrics automatically collected
|
1012
|
+
{ data: 'response' }
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
```
|
1016
|
+
|
1017
|
+
### ๐ Security Middleware
|
1018
|
+
|
1019
|
+
Built-in security features for production deployments:
|
1020
|
+
|
1021
|
+
```ruby
|
1022
|
+
# Automatic security middleware
|
1023
|
+
use RapiTapir::Server::Middleware::SecurityHeaders
|
1024
|
+
use RapiTapir::Server::Middleware::RateLimit, requests_per_minute: 100
|
1025
|
+
use RapiTapir::Server::Middleware::CORS, origins: ['https://myapp.com']
|
1026
|
+
use RapiTapir::Server::Middleware::RequestValidation
|
1027
|
+
```
|
1028
|
+
|
1029
|
+
## ๐ค AI Integration Features
|
1030
|
+
|
1031
|
+
### LLM Instruction Generation
|
1032
|
+
|
1033
|
+
Generate context-aware instructions for any endpoint:
|
1034
|
+
|
1035
|
+
```ruby
|
1036
|
+
# Generate instructions for different AI purposes
|
1037
|
+
generator = RapiTapir::AI::LLMInstruction::Generator.new
|
1038
|
+
|
1039
|
+
# Validation instructions
|
1040
|
+
validation_prompt = generator.generate_instructions(
|
1041
|
+
endpoint: my_endpoint,
|
1042
|
+
purpose: :validation
|
1043
|
+
)
|
1044
|
+
|
1045
|
+
# Documentation instructions
|
1046
|
+
docs_prompt = generator.generate_instructions(
|
1047
|
+
endpoint: my_endpoint,
|
1048
|
+
purpose: :documentation
|
1049
|
+
)
|
1050
|
+
|
1051
|
+
# Test generation instructions
|
1052
|
+
test_prompt = generator.generate_instructions(
|
1053
|
+
endpoint: my_endpoint,
|
1054
|
+
purpose: :testing
|
1055
|
+
)
|
1056
|
+
```
|
1057
|
+
|
1058
|
+
### RAG (Retrieval-Augmented Generation)
|
1059
|
+
|
1060
|
+
Enable semantic search and context-aware responses:
|
1061
|
+
|
1062
|
+
```ruby
|
1063
|
+
endpoint(
|
1064
|
+
GET('/books/semantic-search')
|
1065
|
+
.query(:query, T.string)
|
1066
|
+
.enable_rag(
|
1067
|
+
retrieval_backend: :memory, # or :elasticsearch, :postgresql
|
1068
|
+
llm_provider: :openai,
|
1069
|
+
context_window: 4000
|
1070
|
+
)
|
1071
|
+
.ok(T.hash({
|
1072
|
+
"results" => T.array(BOOK_SCHEMA),
|
1073
|
+
"context" => T.string,
|
1074
|
+
"confidence" => T.float
|
1075
|
+
}))
|
1076
|
+
.build
|
1077
|
+
) do |inputs|
|
1078
|
+
# RAG context automatically injected
|
1079
|
+
rag_enhanced_search(inputs[:query], context: rag_context)
|
1080
|
+
end
|
1081
|
+
```
|
1082
|
+
|
1083
|
+
### Model Context Protocol (MCP)
|
1084
|
+
|
1085
|
+
Export your API for AI agent consumption:
|
1086
|
+
|
1087
|
+
```ruby
|
1088
|
+
# Export MCP-compatible JSON
|
1089
|
+
exporter = RapiTapir::AI::MCP::Exporter.new(rapitapir_endpoints)
|
1090
|
+
mcp_json = exporter.export_json(pretty: true)
|
1091
|
+
|
1092
|
+
# CLI export
|
1093
|
+
# rapitapir mcp export --output api-context.json
|
1094
|
+
```
|
1095
|
+
|
1096
|
+
## ๐จ Complete Examples
|
1097
|
+
|
1098
|
+
### ๐ Library Management System
|
1099
|
+
|
1100
|
+
A comprehensive example showcasing all features:
|
1101
|
+
|
1102
|
+
```ruby
|
1103
|
+
require 'rapitapir'
|
1104
|
+
|
1105
|
+
class LibraryAPI < SinatraRapiTapir
|
1106
|
+
rapitapir do
|
1107
|
+
info(
|
1108
|
+
title: 'Library Management System',
|
1109
|
+
description: 'Complete library API with AI, auth, and observability',
|
1110
|
+
version: '2.0.0',
|
1111
|
+
contact: { name: 'API Team', email: 'api@library.com' }
|
1112
|
+
)
|
1113
|
+
|
1114
|
+
# Auth configuration
|
1115
|
+
oauth2_auth0 :auth0,
|
1116
|
+
domain: ENV['AUTH0_DOMAIN'],
|
1117
|
+
audience: ENV['AUTH0_AUDIENCE']
|
1118
|
+
|
1119
|
+
# Observability
|
1120
|
+
enable_observability do |config|
|
1121
|
+
config.health_checks.enable
|
1122
|
+
config.tracing.enable_opentelemetry(service_name: 'library-api')
|
1123
|
+
config.metrics.enable_prometheus
|
1124
|
+
end
|
1125
|
+
|
308
1126
|
production_defaults!
|
309
1127
|
end
|
310
1128
|
|
311
|
-
#
|
1129
|
+
# Schemas
|
1130
|
+
BOOK_SCHEMA = T.hash({
|
1131
|
+
"id" => T.integer,
|
1132
|
+
"isbn" => T.string(pattern: /^\d{13}$/),
|
1133
|
+
"title" => T.string(min_length: 1, max_length: 500),
|
1134
|
+
"authors" => T.array(T.string),
|
1135
|
+
"published_date" => T.date,
|
1136
|
+
"genres" => T.array(T.string),
|
1137
|
+
"available_copies" => T.integer(minimum: 0),
|
1138
|
+
"total_copies" => T.integer(minimum: 1),
|
1139
|
+
"metadata" => T.optional(T.hash({}))
|
1140
|
+
})
|
1141
|
+
|
1142
|
+
MEMBER_SCHEMA = T.hash({
|
1143
|
+
"id" => T.integer,
|
1144
|
+
"email" => T.email,
|
1145
|
+
"name" => T.string(min_length: 1),
|
1146
|
+
"member_since" => T.date,
|
1147
|
+
"active" => T.boolean,
|
1148
|
+
"borrowed_books" => T.array(T.integer)
|
1149
|
+
})
|
1150
|
+
|
1151
|
+
# Public endpoints
|
312
1152
|
endpoint(
|
313
|
-
GET('/
|
314
|
-
.
|
315
|
-
.
|
316
|
-
.
|
317
|
-
.
|
1153
|
+
GET('/books/search')
|
1154
|
+
.query(:q, T.string(min_length: 1), description: 'Search query')
|
1155
|
+
.query(:ai_enhanced, T.optional(T.boolean), description: 'Use AI semantic search')
|
1156
|
+
.summary('Search books with optional AI enhancement')
|
1157
|
+
.tags('Books', 'Search')
|
318
1158
|
.ok(T.hash({
|
319
|
-
"
|
320
|
-
"
|
321
|
-
|
322
|
-
"per_page" => T.integer,
|
323
|
-
"total" => T.integer,
|
324
|
-
"pages" => T.integer
|
325
|
-
})
|
1159
|
+
"books" => T.array(BOOK_SCHEMA),
|
1160
|
+
"total" => T.integer,
|
1161
|
+
"ai_enhanced" => T.boolean
|
326
1162
|
}))
|
327
|
-
.
|
328
|
-
.
|
1163
|
+
.enable_rag
|
1164
|
+
.enable_mcp
|
329
1165
|
.build
|
330
1166
|
) do |inputs|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
users = User.paginate(page: page, per_page: per_page)
|
1167
|
+
if inputs[:ai_enhanced]
|
1168
|
+
results = AIBookSearch.semantic_search(inputs[:q], context: rag_context)
|
1169
|
+
else
|
1170
|
+
results = Book.search(inputs[:q])
|
1171
|
+
end
|
337
1172
|
|
338
1173
|
{
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
per_page: per_page,
|
343
|
-
total: users.total_count,
|
344
|
-
pages: users.total_pages
|
345
|
-
}
|
1174
|
+
books: results.map(&:to_h),
|
1175
|
+
total: results.count,
|
1176
|
+
ai_enhanced: !!inputs[:ai_enhanced]
|
346
1177
|
}
|
347
1178
|
end
|
1179
|
+
|
1180
|
+
# Protected member endpoints
|
1181
|
+
protect_with_oauth2 scopes: ['library:read'] do
|
1182
|
+
api_resource '/books', schema: BOOK_SCHEMA do
|
1183
|
+
crud only: [:index, :show] do
|
1184
|
+
index { Book.available.map(&:to_h) }
|
1185
|
+
show { |inputs| Book.find(inputs[:id])&.to_h || halt(404) }
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
custom :post, ':id/reserve' do |inputs|
|
1189
|
+
book = Book.find(inputs[:id]) || halt(404)
|
1190
|
+
member = current_member
|
1191
|
+
|
1192
|
+
reservation = ReservationService.create(book: book, member: member)
|
1193
|
+
{ reservation_id: reservation.id, expires_at: reservation.expires_at }
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
endpoint(
|
1198
|
+
GET('/members/me/profile')
|
1199
|
+
.summary('Get current member profile')
|
1200
|
+
.bearer_auth(scopes: ['profile:read'])
|
1201
|
+
.ok(MEMBER_SCHEMA)
|
1202
|
+
.build
|
1203
|
+
) { current_member.to_h }
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
# Admin endpoints
|
1207
|
+
protect_with_oauth2 scopes: ['library:admin'] do
|
1208
|
+
api_resource '/admin/books', schema: BOOK_SCHEMA do
|
1209
|
+
crud do
|
1210
|
+
index { Book.all.map(&:to_h) }
|
1211
|
+
show { |inputs| Book.find(inputs[:id])&.to_h || halt(404) }
|
1212
|
+
create { |inputs| Book.create!(inputs[:body]).to_h }
|
1213
|
+
update { |inputs| Book.update!(inputs[:id], inputs[:body]).to_h }
|
1214
|
+
destroy { |inputs| Book.destroy(inputs[:id]); status 204 }
|
1215
|
+
end
|
1216
|
+
end
|
1217
|
+
|
1218
|
+
# AI-powered book recommendations
|
1219
|
+
endpoint(
|
1220
|
+
POST('/admin/ai/recommend-acquisitions')
|
1221
|
+
.body(T.hash({
|
1222
|
+
"budget" => T.float(minimum: 0),
|
1223
|
+
"categories" => T.optional(T.array(T.string)),
|
1224
|
+
"member_preferences" => T.optional(T.boolean)
|
1225
|
+
}))
|
1226
|
+
.summary('Get AI-powered book acquisition recommendations')
|
1227
|
+
.tags('Admin', 'AI', 'Recommendations')
|
1228
|
+
.ok(T.hash({
|
1229
|
+
"recommendations" => T.array(T.hash({
|
1230
|
+
"title" => T.string,
|
1231
|
+
"author" => T.string,
|
1232
|
+
"estimated_cost" => T.float,
|
1233
|
+
"demand_score" => T.float,
|
1234
|
+
"reasoning" => T.string
|
1235
|
+
})),
|
1236
|
+
"total_estimated_cost" => T.float,
|
1237
|
+
"confidence" => T.float
|
1238
|
+
}))
|
1239
|
+
.enable_llm_instructions(purpose: :completion)
|
1240
|
+
.build
|
1241
|
+
) do |inputs|
|
1242
|
+
recommendations = AIAcquisitionService.recommend(
|
1243
|
+
budget: inputs[:budget],
|
1244
|
+
categories: inputs[:categories],
|
1245
|
+
consider_member_preferences: inputs[:member_preferences],
|
1246
|
+
context: library_context
|
1247
|
+
)
|
1248
|
+
|
1249
|
+
{
|
1250
|
+
recommendations: recommendations[:items],
|
1251
|
+
total_estimated_cost: recommendations[:total_cost],
|
1252
|
+
confidence: recommendations[:confidence_score]
|
1253
|
+
}
|
1254
|
+
end
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
run! if __FILE__ == $0
|
348
1258
|
end
|
349
1259
|
```
|
350
1260
|
|
351
|
-
###
|
1261
|
+
### ๐ฅ Healthcare API Example
|
352
1262
|
|
353
1263
|
```ruby
|
354
|
-
class
|
1264
|
+
class HealthcareAPI < SinatraRapiTapir
|
355
1265
|
rapitapir do
|
356
|
-
info(
|
357
|
-
|
358
|
-
|
1266
|
+
info(
|
1267
|
+
title: 'Healthcare Management API',
|
1268
|
+
description: 'HIPAA-compliant healthcare API with AI diagnostics',
|
1269
|
+
version: '2.0.0'
|
1270
|
+
)
|
1271
|
+
|
1272
|
+
# Strict security for healthcare
|
1273
|
+
bearer_auth :jwt, realm: 'Healthcare'
|
1274
|
+
|
1275
|
+
enable_observability do |config|
|
1276
|
+
config.health_checks.enable
|
1277
|
+
config.tracing.enable_opentelemetry(
|
1278
|
+
service_name: 'healthcare-api',
|
1279
|
+
compliance_mode: :hipaa
|
1280
|
+
)
|
1281
|
+
config.logging.enable_structured(
|
1282
|
+
format: :json,
|
1283
|
+
exclude_fields: [:ssn, :medical_record_number] # PII protection
|
1284
|
+
)
|
1285
|
+
end
|
1286
|
+
|
359
1287
|
production_defaults!
|
360
1288
|
end
|
361
1289
|
|
362
|
-
|
1290
|
+
PATIENT_SCHEMA = T.hash({
|
1291
|
+
"id" => T.string, # UUID for privacy
|
1292
|
+
"name" => T.hash({
|
1293
|
+
"first" => T.string,
|
1294
|
+
"last" => T.string
|
1295
|
+
}),
|
1296
|
+
"date_of_birth" => T.date,
|
1297
|
+
"medical_record_number" => T.string,
|
1298
|
+
"insurance" => T.optional(T.hash({}))
|
1299
|
+
})
|
1300
|
+
|
1301
|
+
# AI-powered diagnostic assistance
|
363
1302
|
endpoint(
|
364
|
-
|
365
|
-
.
|
366
|
-
|
367
|
-
|
368
|
-
|
1303
|
+
POST('/diagnostics/analyze')
|
1304
|
+
.body(T.hash({
|
1305
|
+
"patient_id" => T.string,
|
1306
|
+
"symptoms" => T.array(T.string),
|
1307
|
+
"vital_signs" => T.hash({
|
1308
|
+
"temperature" => T.optional(T.float),
|
1309
|
+
"blood_pressure" => T.optional(T.string),
|
1310
|
+
"heart_rate" => T.optional(T.integer)
|
1311
|
+
}),
|
1312
|
+
"medical_history" => T.optional(T.array(T.string))
|
1313
|
+
}))
|
1314
|
+
.summary('AI-assisted diagnostic analysis')
|
1315
|
+
.bearer_auth(scopes: ['diagnostics:read', 'ai:analyze'])
|
1316
|
+
.tags('Diagnostics', 'AI')
|
369
1317
|
.ok(T.hash({
|
370
|
-
"
|
371
|
-
"
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
"
|
377
|
-
"
|
378
|
-
})
|
1318
|
+
"analysis" => T.hash({
|
1319
|
+
"suggested_conditions" => T.array(T.hash({
|
1320
|
+
"condition" => T.string,
|
1321
|
+
"confidence" => T.float,
|
1322
|
+
"reasoning" => T.string
|
1323
|
+
})),
|
1324
|
+
"recommended_tests" => T.array(T.string),
|
1325
|
+
"urgency_level" => T.string(enum: %w[low medium high critical])
|
1326
|
+
}),
|
1327
|
+
"disclaimer" => T.string,
|
1328
|
+
"generated_at" => T.datetime
|
379
1329
|
}))
|
1330
|
+
.enable_llm_instructions(purpose: :analysis)
|
380
1331
|
.build
|
381
1332
|
) do |inputs|
|
382
|
-
#
|
383
|
-
|
1333
|
+
# Verify provider permissions
|
1334
|
+
require_scope!('diagnostics:read')
|
1335
|
+
|
1336
|
+
# AI diagnostic analysis with medical context
|
1337
|
+
analysis = MedicalAI.analyze_symptoms(
|
1338
|
+
patient_id: inputs[:patient_id],
|
1339
|
+
symptoms: inputs[:symptoms],
|
1340
|
+
vital_signs: inputs[:vital_signs],
|
1341
|
+
medical_history: inputs[:medical_history],
|
1342
|
+
provider_context: current_provider_context
|
1343
|
+
)
|
384
1344
|
|
385
1345
|
{
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
filtered: inputs[:filter].present?
|
390
|
-
}
|
1346
|
+
analysis: analysis,
|
1347
|
+
disclaimer: "This analysis is for informational purposes only and should not replace professional medical judgment.",
|
1348
|
+
generated_at: Time.now
|
391
1349
|
}
|
392
1350
|
end
|
1351
|
+
|
1352
|
+
run! if __FILE__ == $0
|
393
1353
|
end
|
394
1354
|
```
|
395
1355
|
|
396
|
-
|
1356
|
+
## ๐ Documentation
|
397
1357
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
1358
|
+
### Core Guides
|
1359
|
+
|
1360
|
+
- **[Getting Started Guide](examples/working_simple_example.rb)** - Your first RapiTapir API in 5 minutes
|
1361
|
+
- **[SinatraRapiTapir Base Class](docs/sinatra_rapitapir.md)** - Zero-boilerplate API creation
|
1362
|
+
- **[Enhanced HTTP DSL](docs/endpoint-definition.md)** - Complete endpoint definition guide
|
1363
|
+
- **[Type System & T Shortcut](docs/type_shortcuts.md)** - All available types and validations
|
1364
|
+
- **[Resource Builder](docs/RAILS_INTEGRATION_IMPLEMENTATION.md)** - RESTful CRUD with minimal code
|
1365
|
+
|
1366
|
+
### Advanced Features
|
1367
|
+
|
1368
|
+
- **[AI Integration](docs/auto-derivation.md)** - LLM instructions, RAG pipelines, MCP export
|
1369
|
+
- **[Authentication & Security](examples/authentication_example.rb)** - OAuth2, JWT, scopes, and Auth0
|
1370
|
+
- **[Observability](docs/observability.md)** - OpenTelemetry, health checks, metrics
|
1371
|
+
- **[CLI Toolkit](docs/blueprint.md)** - Complete command-line development workflow
|
1372
|
+
|
1373
|
+
### Framework Integration
|
1374
|
+
|
1375
|
+
- **[Sinatra Extension](docs/SINATRA_EXTENSION.md)** - Detailed Sinatra integration guide
|
1376
|
+
- **[Rails Integration](docs/RAILS_INTEGRATION_IMPLEMENTATION.md)** - Controller-based Rails APIs
|
1377
|
+
- **[Rack Applications](docs/implementation-status.md)** - Direct Rack integration
|
1378
|
+
|
1379
|
+
### Examples & Templates
|
1380
|
+
|
1381
|
+
Explore our comprehensive examples directory:
|
1382
|
+
|
1383
|
+
- **[Hello World](examples/hello_world.rb)** - 30-second minimal example
|
1384
|
+
- **[Enterprise API](examples/enterprise_rapitapir_api.rb)** - Production-ready with all features
|
1385
|
+
- **[Serverless Deployment](examples/serverless/)** - AWS Lambda, Google Cloud Functions, Azure Functions, Vercel
|
1386
|
+
- **[OAuth2 + Auth0](examples/oauth2/)** - Complete authentication examples
|
1387
|
+
- **[AI-Powered APIs](examples/auto_derivation_ruby_friendly.rb)** - LLM and RAG integration
|
1388
|
+
- **[Observability Setup](examples/observability/)** - Monitoring and health checks
|
1389
|
+
- **[CLI Examples](examples/cli/)** - Command-line toolkit usage
|
1390
|
+
|
1391
|
+
## ๐ ๏ธ CLI Development Toolkit
|
1392
|
+
|
1393
|
+
RapiTapir includes a powerful CLI for streamlined development:
|
1394
|
+
|
1395
|
+
### Code Generation
|
1396
|
+
```bash
|
1397
|
+
# Generate OpenAPI specifications
|
1398
|
+
rapitapir generate openapi --output api-spec.json --format json
|
1399
|
+
rapitapir generate openapi --output api-spec.yaml --format yaml
|
1400
|
+
|
1401
|
+
# Generate TypeScript clients
|
1402
|
+
rapitapir generate client typescript --output client/api.ts
|
1403
|
+
rapitapir generate client python --output client/api.py
|
1404
|
+
|
1405
|
+
# Generate documentation
|
1406
|
+
rapitapir generate docs html --output docs/api.html
|
1407
|
+
rapitapir generate docs markdown --output API.md
|
403
1408
|
```
|
404
1409
|
|
405
|
-
|
1410
|
+
### AI-Powered Features
|
1411
|
+
```bash
|
1412
|
+
# Generate LLM instructions for specific endpoints
|
1413
|
+
rapitapir llm generate --endpoint-id "get_users" --purpose validation
|
1414
|
+
rapitapir llm generate --endpoint-id "create_book" --purpose testing
|
406
1415
|
|
407
|
-
|
1416
|
+
# Export LLM instructions for all endpoints
|
1417
|
+
rapitapir llm export --format instructions --output ai-prompts/
|
408
1418
|
|
409
|
-
|
410
|
-
|
411
|
-
- **[Enterprise API](examples/enterprise_rapitapir_api.rb)** - Production-ready example with auth
|
412
|
-
- **[Authentication](examples/authentication_example.rb)** - Bearer token and scope-based auth
|
413
|
-
- **[Observability](examples/observability/)** - Health checks, metrics, and tracing
|
1419
|
+
# Test LLM instruction generation
|
1420
|
+
rapitapir llm test --endpoint-file my_api.rb
|
414
1421
|
|
415
|
-
|
1422
|
+
# Export Model Context Protocol for AI agents
|
1423
|
+
rapitapir mcp export --output mcp-context.json --format compact
|
1424
|
+
```
|
416
1425
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
1426
|
+
### Development & Validation
|
1427
|
+
```bash
|
1428
|
+
# Validate endpoint definitions
|
1429
|
+
rapitapir validate endpoints --file my_api.rb
|
1430
|
+
rapitapir validate openapi --file api-spec.json
|
1431
|
+
|
1432
|
+
# Serve documentation and specs
|
1433
|
+
rapitapir serve --port 3000 --docs-path /documentation
|
1434
|
+
rapitapir docs --serve --port 8080 --watch
|
1435
|
+
|
1436
|
+
# Project scaffolding (coming soon)
|
1437
|
+
rapitapir new my-api --template sinatra-ai
|
1438
|
+
rapitapir generate scaffold books --with-auth --with-observability
|
1439
|
+
```
|
424
1440
|
|
425
|
-
|
1441
|
+
### Installation & Setup
|
426
1442
|
|
427
|
-
|
1443
|
+
```bash
|
1444
|
+
# Install the gem
|
1445
|
+
gem install rapitapir
|
1446
|
+
|
1447
|
+
# Or add to Gemfile
|
1448
|
+
echo 'gem "rapitapir"' >> Gemfile
|
1449
|
+
bundle install
|
428
1450
|
|
1451
|
+
# Verify installation
|
1452
|
+
rapitapir --version
|
1453
|
+
rapitapir --help
|
1454
|
+
```
|
1455
|
+
|
1456
|
+
## ๐งช Testing & Quality
|
1457
|
+
|
1458
|
+
RapiTapir includes comprehensive testing utilities and maintains high code quality:
|
1459
|
+
|
1460
|
+
### Test Your APIs
|
429
1461
|
```ruby
|
430
|
-
# Validate
|
1462
|
+
# Validate endpoint definitions
|
431
1463
|
RapiTapir::CLI::Validator.new(endpoints).validate
|
432
1464
|
|
433
|
-
# Generate test fixtures
|
434
|
-
RapiTapir::Testing.
|
1465
|
+
# Generate test fixtures from schemas
|
1466
|
+
test_user = RapiTapir::Testing.generate_fixture(USER_SCHEMA)
|
1467
|
+
test_data = RapiTapir::Testing.generate_fixtures(BOOK_SCHEMA, count: 10)
|
1468
|
+
|
1469
|
+
# Test endpoint types and validation
|
1470
|
+
endpoint.validate!({ name: "test", age: 25 }) # Returns validated data
|
435
1471
|
```
|
436
1472
|
|
437
|
-
|
1473
|
+
### Quality Metrics
|
1474
|
+
- **643 tests passing** with comprehensive coverage
|
1475
|
+
- **67.67% line coverage** across the entire codebase
|
1476
|
+
- **RuboCop compliance** with zero style violations
|
1477
|
+
- **Zero security vulnerabilities** in dependencies
|
1478
|
+
- **Continuous integration** with GitHub Actions
|
438
1479
|
|
1480
|
+
### Run Tests Locally
|
439
1481
|
```bash
|
1482
|
+
# Clone the repository
|
1483
|
+
git clone https://github.com/riccardomerolla/rapitapir.git
|
1484
|
+
cd rapitapir
|
1485
|
+
|
1486
|
+
# Install dependencies
|
1487
|
+
bundle install
|
1488
|
+
|
1489
|
+
# Run the test suite
|
440
1490
|
bundle exec rspec
|
1491
|
+
|
1492
|
+
# Check code style
|
1493
|
+
rubocop
|
1494
|
+
|
1495
|
+
# View coverage report
|
1496
|
+
open coverage/index.html
|
441
1497
|
```
|
442
1498
|
|
443
1499
|
## ๐ค Contributing
|
444
1500
|
|
445
|
-
We love contributions!
|
446
|
-
|
447
|
-
### Development Setup
|
1501
|
+
We love contributions! RapiTapir thrives on community input and collaboration.
|
448
1502
|
|
1503
|
+
### Quick Start Contributing
|
449
1504
|
```bash
|
450
1505
|
git clone https://github.com/riccardomerolla/rapitapir.git
|
451
|
-
cd
|
1506
|
+
cd rapitapir
|
452
1507
|
bundle install
|
453
|
-
bundle exec rspec
|
1508
|
+
bundle exec rspec # Ensure all tests pass
|
454
1509
|
```
|
455
1510
|
|
456
|
-
###
|
1511
|
+
### Ways to Contribute
|
1512
|
+
- **๐ Bug Reports**: [Open an issue](https://github.com/riccardomerolla/rapitapir/issues/new)
|
1513
|
+
- **๐ก Feature Requests**: [Start a discussion](https://github.com/riccardomerolla/rapitapir/discussions)
|
1514
|
+
- **๐ Documentation**: Improve guides, examples, and API docs
|
1515
|
+
- **๐งช Tests**: Add test coverage for edge cases
|
1516
|
+
- **๐จ Examples**: Create real-world API examples
|
1517
|
+
- **๐ Integrations**: Build plugins for other frameworks
|
1518
|
+
|
1519
|
+
### Development Roadmap
|
457
1520
|
|
458
|
-
|
459
|
-
-
|
460
|
-
-
|
461
|
-
-
|
1521
|
+
**Current Focus (v2.1)**:
|
1522
|
+
- Enhanced AI features and LLM provider support
|
1523
|
+
- Advanced authentication patterns
|
1524
|
+
- Performance optimizations and caching
|
1525
|
+
- Extended CLI functionality
|
462
1526
|
|
463
|
-
|
1527
|
+
**Upcoming (v3.0)**:
|
1528
|
+
- GraphQL integration alongside REST
|
1529
|
+
- gRPC support for high-performance APIs
|
1530
|
+
- Advanced plugin ecosystem
|
1531
|
+
- Multi-language client generation
|
464
1532
|
|
465
|
-
|
1533
|
+
See our [Contributing Guide](CONTRIBUTING.md) for detailed guidelines.
|
466
1534
|
|
467
|
-
## ๐โโ๏ธ Support
|
1535
|
+
## ๐โโ๏ธ Support & Community
|
468
1536
|
|
1537
|
+
### Get Help
|
469
1538
|
- **๐ Bug Reports**: [GitHub Issues](https://github.com/riccardomerolla/rapitapir/issues)
|
470
|
-
-
|
471
|
-
- **๐ง
|
1539
|
+
- **๏ฟฝ Questions & Discussions**: [GitHub Discussions](https://github.com/riccardomerolla/rapitapir/discussions)
|
1540
|
+
- **๐ง Direct Contact**: riccardo.merolla@gmail.com
|
1541
|
+
- **๐ Documentation**: Comprehensive guides in the [docs/](docs/) directory
|
1542
|
+
|
1543
|
+
### Community
|
1544
|
+
- **โญ Star the Project**: Show your support on [GitHub](https://github.com/riccardomerolla/rapitapir)
|
1545
|
+
- **๐ Share**: Help others discover RapiTapir
|
1546
|
+
- **๐ค Contribute**: Join the growing community of contributors
|
472
1547
|
|
473
1548
|
---
|
474
1549
|
|
475
|
-
|
1550
|
+
## ๐ License
|
476
1551
|
|
1552
|
+
RapiTapir is released under the [MIT License](LICENSE). Use it freely in personal and commercial projects.
|
477
1553
|
|
478
1554
|
## ๐ Acknowledgments
|
479
1555
|
|
480
|
-
|
481
|
-
|
1556
|
+
RapiTapir is inspired by excellent projects in the API development space:
|
1557
|
+
|
1558
|
+
- **[Scala Tapir](https://github.com/softwaremill/tapir)** - Type-safe endpoint definitions that inspired our DSL
|
1559
|
+
- **[FastAPI](https://fastapi.tiangolo.com/)** - Automatic documentation and validation patterns
|
1560
|
+
- **[Ruby on Rails](https://rubyonrails.org/)** - Convention over configuration philosophy
|
1561
|
+
- **[Sinatra](http://sinatrarb.com/)** - Minimalist web framework elegance
|
1562
|
+
|
1563
|
+
Special thanks to the Ruby and Sinatra communities for their ongoing support and feedback.
|
482
1564
|
|
483
1565
|
---
|
484
1566
|
|
485
|
-
**RapiTapir
|
1567
|
+
**RapiTapir ๐ฆ** - *APIs so fast, clean, and intelligent, they practically run wild!* โก๏ฟฝ
|
1568
|
+
|
1569
|
+
**Built with โค๏ธ for the Ruby community**
|