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/SINATRA_EXTENSION.md
CHANGED
@@ -1,467 +1,518 @@
|
|
1
|
-
# RapiTapir Sinatra
|
1
|
+
# RapiTapir Sinatra Integration
|
2
2
|
|
3
3
|
**Zero-boilerplate, enterprise-grade API development with RapiTapir and Sinatra**
|
4
4
|
|
5
|
-
|
5
|
+
RapiTapir provides two elegant ways to integrate with Sinatra: the recommended **SinatraRapiTapir base class** for maximum simplicity, and the **manual extension registration** for advanced customization.
|
6
6
|
|
7
|
-
## 🚀 Quick Start
|
7
|
+
## 🚀 Quick Start (Recommended)
|
8
|
+
|
9
|
+
### Option 1: SinatraRapiTapir Base Class
|
10
|
+
|
11
|
+
The simplest and cleanest way to create APIs:
|
8
12
|
|
9
13
|
```ruby
|
10
|
-
require 'sinatra/base'
|
11
14
|
require 'rapitapir'
|
12
|
-
require 'rapitapir/sinatra/extension'
|
13
|
-
|
14
|
-
class MyAPI < Sinatra::Base
|
15
|
-
register RapiTapir::Sinatra::Extension
|
16
15
|
|
16
|
+
class MyAPI < SinatraRapiTapir
|
17
17
|
rapitapir do
|
18
|
-
info
|
19
|
-
development_defaults!
|
18
|
+
info(title: 'My API', version: '2.0.0')
|
19
|
+
development_defaults! # Auto CORS, docs, health checks
|
20
20
|
end
|
21
21
|
|
22
|
-
#
|
22
|
+
# T shortcut and HTTP verbs automatically available
|
23
23
|
endpoint(
|
24
|
-
|
25
|
-
.
|
24
|
+
GET('/hello')
|
25
|
+
.query(:name, T.string, description: 'Your name')
|
26
|
+
.ok(T.hash({ "message" => T.string }))
|
26
27
|
.build
|
27
|
-
) { { message:
|
28
|
+
) { |inputs| { message: "Hello, #{inputs[:name]}!" } }
|
29
|
+
|
30
|
+
run! if __FILE__ == $0
|
28
31
|
end
|
29
32
|
```
|
30
33
|
|
31
|
-
|
34
|
+
### Option 2: Manual Extension Registration
|
32
35
|
|
33
|
-
|
34
|
-
- [Installation](#installation)
|
35
|
-
- [Configuration](#configuration)
|
36
|
-
- [Endpoint Definition](#endpoint-definition)
|
37
|
-
- [RESTful Resources](#restful-resources)
|
38
|
-
- [Authentication](#authentication)
|
39
|
-
- [Middleware](#middleware)
|
40
|
-
- [Documentation](#documentation)
|
41
|
-
- [Examples](#examples)
|
42
|
-
- [Architecture](#architecture)
|
43
|
-
|
44
|
-
## ✨ Features
|
45
|
-
|
46
|
-
### Zero Boilerplate
|
47
|
-
- **One-line configuration**: Set up authentication, middleware, and documentation with minimal code
|
48
|
-
- **Smart defaults**: Production and development presets that just work
|
49
|
-
- **Automatic registration**: Endpoints are automatically registered with proper validation
|
50
|
-
|
51
|
-
### Enterprise-Ready
|
52
|
-
- **Built-in authentication**: Bearer token, API key, and custom schemes
|
53
|
-
- **Production middleware**: CORS, rate limiting, security headers
|
54
|
-
- **Auto-generated OpenAPI**: Beautiful Swagger UI documentation
|
55
|
-
- **Type-safe validation**: Automatic request/response validation
|
56
|
-
|
57
|
-
### Developer Experience
|
58
|
-
- **RESTful resource builder**: Create full CRUD APIs with a single block
|
59
|
-
- **Helpful error messages**: Clear validation and authentication errors
|
60
|
-
- **Hot reloading**: Development-friendly configuration
|
61
|
-
- **Extensive examples**: Get started quickly with working code
|
62
|
-
|
63
|
-
## 🔧 Installation
|
64
|
-
|
65
|
-
Add to your Gemfile:
|
36
|
+
For advanced customization and existing Sinatra applications:
|
66
37
|
|
67
38
|
```ruby
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
Or install directly:
|
72
|
-
|
73
|
-
```bash
|
74
|
-
gem install rapitapir
|
75
|
-
```
|
76
|
-
|
77
|
-
## ⚙️ Configuration
|
78
|
-
|
79
|
-
### Basic Configuration
|
39
|
+
require 'sinatra/base'
|
40
|
+
require 'rapitapir/sinatra/extension'
|
80
41
|
|
81
|
-
```ruby
|
82
42
|
class MyAPI < Sinatra::Base
|
83
43
|
register RapiTapir::Sinatra::Extension
|
84
44
|
|
85
45
|
rapitapir do
|
86
|
-
|
87
|
-
|
88
|
-
title: 'My API',
|
89
|
-
description: 'A fantastic API',
|
90
|
-
version: '1.0.0',
|
91
|
-
contact: { email: 'support@example.com' }
|
92
|
-
)
|
93
|
-
|
94
|
-
# Server configuration
|
95
|
-
server url: 'http://localhost:4567', description: 'Development'
|
96
|
-
server url: 'https://api.example.com', description: 'Production'
|
97
|
-
|
98
|
-
# Enable documentation
|
99
|
-
enable_docs path: '/docs', openapi_path: '/openapi.json'
|
46
|
+
info(title: 'My API', version: '2.0.0')
|
47
|
+
development_defaults!
|
100
48
|
end
|
49
|
+
|
50
|
+
endpoint(
|
51
|
+
GET('/hello')
|
52
|
+
.query(:name, T.string)
|
53
|
+
.ok(T.hash({ "message" => T.string }))
|
54
|
+
.build
|
55
|
+
) { |inputs| { message: "Hello, #{inputs[:name]}!" } }
|
101
56
|
end
|
102
57
|
```
|
103
58
|
|
104
|
-
|
59
|
+
## 📋 Key Features
|
105
60
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
production_defaults! # Secure defaults for production
|
112
|
-
end
|
113
|
-
end
|
114
|
-
```
|
61
|
+
### ✨ Zero Configuration
|
62
|
+
- **SinatraRapiTapir base class**: Inherit and start building APIs immediately
|
63
|
+
- **T shortcut**: Use `T.string` instead of `RapiTapir::Types.string` everywhere
|
64
|
+
- **HTTP verbs**: `GET()`, `POST()`, `PUT()`, `DELETE()` methods built-in
|
65
|
+
- **Smart defaults**: Production and development presets that just work
|
115
66
|
|
116
|
-
|
67
|
+
### 🏭 Enterprise Ready
|
68
|
+
- **Built-in authentication**: OAuth2, Bearer token, API key, and custom schemes
|
69
|
+
- **Production middleware**: CORS, rate limiting, security headers, request validation
|
70
|
+
- **Auto-generated documentation**: Interactive Swagger UI with try-it-out functionality
|
71
|
+
- **Type-safe validation**: Automatic request/response validation with helpful errors
|
117
72
|
|
118
|
-
###
|
73
|
+
### 🤖 AI Integration
|
74
|
+
- **LLM instruction generation**: Auto-generate AI prompts for your endpoints
|
75
|
+
- **RAG pipelines**: Retrieval-augmented generation for enhanced responses
|
76
|
+
- **MCP export**: Model Context Protocol for AI agent consumption
|
77
|
+
- **CLI toolkit**: Complete command-line development workflow
|
78
|
+
|
79
|
+
## 📖 Configuration Options
|
119
80
|
|
81
|
+
### API Information
|
120
82
|
```ruby
|
121
83
|
rapitapir do
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
84
|
+
info(
|
85
|
+
title: 'My API',
|
86
|
+
description: 'A comprehensive API for my application',
|
87
|
+
version: '2.0.0',
|
88
|
+
contact: {
|
89
|
+
name: 'API Team',
|
90
|
+
email: 'api@example.com',
|
91
|
+
url: 'https://example.com/support'
|
92
|
+
},
|
93
|
+
license: {
|
94
|
+
name: 'MIT',
|
95
|
+
url: 'https://opensource.org/licenses/MIT'
|
96
|
+
}
|
97
|
+
)
|
98
|
+
|
99
|
+
# Server environments
|
100
|
+
server(url: 'http://localhost:4567', description: 'Development')
|
101
|
+
server(url: 'https://staging-api.example.com', description: 'Staging')
|
102
|
+
server(url: 'https://api.example.com', description: 'Production')
|
134
103
|
end
|
135
104
|
```
|
136
105
|
|
137
|
-
###
|
106
|
+
### Development vs Production Defaults
|
138
107
|
|
139
108
|
```ruby
|
109
|
+
# Development mode (automatic CORS, docs, health checks)
|
140
110
|
rapitapir do
|
141
|
-
|
142
|
-
location: 'header',
|
143
|
-
name: 'X-API-Key',
|
144
|
-
key_validator: proc do |key|
|
145
|
-
# Validate API key
|
146
|
-
end
|
147
|
-
}
|
111
|
+
development_defaults!
|
148
112
|
end
|
149
|
-
```
|
150
113
|
|
151
|
-
|
114
|
+
# Production mode (security headers, rate limiting, metrics)
|
115
|
+
rapitapir do
|
116
|
+
production_defaults!
|
117
|
+
end
|
152
118
|
|
153
|
-
|
119
|
+
# Custom configuration
|
154
120
|
rapitapir do
|
155
|
-
|
121
|
+
enable_docs(path: '/documentation', openapi_path: '/spec.json')
|
122
|
+
enable_cors(origins: ['https://myapp.com'], methods: %w[GET POST])
|
123
|
+
enable_health_checks(path: '/health')
|
124
|
+
enable_metrics(path: '/metrics')
|
156
125
|
end
|
157
126
|
```
|
158
127
|
|
159
|
-
##
|
128
|
+
## 🏗️ Endpoint Definition
|
160
129
|
|
161
|
-
###
|
130
|
+
### Modern HTTP Verb DSL
|
162
131
|
|
163
132
|
```ruby
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
)
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
133
|
+
class BookAPI < SinatraRapiTapir
|
134
|
+
# Basic endpoint
|
135
|
+
endpoint(
|
136
|
+
GET('/books')
|
137
|
+
.summary('List all books')
|
138
|
+
.query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)), description: 'Number of books to return')
|
139
|
+
.query(:genre, T.optional(T.string), description: 'Filter by genre')
|
140
|
+
.ok(T.array(T.hash({
|
141
|
+
"id" => T.integer,
|
142
|
+
"title" => T.string,
|
143
|
+
"author" => T.string,
|
144
|
+
"genre" => T.string
|
145
|
+
})))
|
146
|
+
.build
|
147
|
+
) do |inputs|
|
148
|
+
books = Book.all
|
149
|
+
books = books.where(genre: inputs[:genre]) if inputs[:genre]
|
150
|
+
books = books.limit(inputs[:limit] || 50)
|
151
|
+
books.map(&:to_h)
|
152
|
+
end
|
175
153
|
|
176
|
-
|
177
|
-
endpoint(
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
154
|
+
# POST with body validation
|
155
|
+
endpoint(
|
156
|
+
POST('/books')
|
157
|
+
.summary('Create a new book')
|
158
|
+
.body(T.hash({
|
159
|
+
"title" => T.string(min_length: 1, max_length: 500),
|
160
|
+
"author" => T.string(min_length: 1),
|
161
|
+
"genre" => T.string,
|
162
|
+
"isbn" => T.optional(T.string(pattern: /^\d{13}$/)),
|
163
|
+
"pages" => T.optional(T.integer(minimum: 1))
|
164
|
+
}), description: 'Book data')
|
165
|
+
.ok(T.hash({
|
166
|
+
"id" => T.integer,
|
167
|
+
"title" => T.string,
|
168
|
+
"author" => T.string,
|
169
|
+
"created_at" => T.datetime
|
170
|
+
}), description: 'Created book')
|
171
|
+
.error_response(400, T.hash({ "error" => T.string, "details" => T.array(T.string) }))
|
172
|
+
.build
|
173
|
+
) do |inputs|
|
174
|
+
begin
|
175
|
+
book = Book.create!(inputs[:body])
|
176
|
+
status 201
|
177
|
+
{
|
178
|
+
id: book.id,
|
179
|
+
title: book.title,
|
180
|
+
author: book.author,
|
181
|
+
created_at: book.created_at
|
182
|
+
}
|
183
|
+
rescue ValidationError => e
|
184
|
+
halt 400, {
|
185
|
+
error: 'Validation failed',
|
186
|
+
details: e.messages
|
187
|
+
}.to_json
|
188
|
+
end
|
189
|
+
end
|
186
190
|
end
|
187
191
|
```
|
188
192
|
|
189
|
-
|
193
|
+
### RESTful Resource Builder
|
190
194
|
|
191
|
-
|
195
|
+
Create complete CRUD APIs with minimal code:
|
192
196
|
|
193
197
|
```ruby
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
198
|
+
class UserAPI < SinatraRapiTapir
|
199
|
+
USER_SCHEMA = T.hash({
|
200
|
+
"id" => T.integer,
|
201
|
+
"name" => T.string(min_length: 1, max_length: 100),
|
202
|
+
"email" => T.email,
|
203
|
+
"active" => T.boolean,
|
204
|
+
"created_at" => T.datetime,
|
205
|
+
"updated_at" => T.datetime
|
206
|
+
})
|
207
|
+
|
208
|
+
api_resource '/users', schema: USER_SCHEMA do
|
209
|
+
crud do
|
210
|
+
index do |inputs|
|
211
|
+
users = User.all
|
212
|
+
users = users.where(active: true) if inputs[:active] == 'true'
|
213
|
+
users.limit(inputs[:limit] || 50).map(&:to_h)
|
214
|
+
end
|
215
|
+
|
216
|
+
show { |inputs| User.find(inputs[:id])&.to_h || halt(404) }
|
217
|
+
|
218
|
+
create do |inputs|
|
219
|
+
user = User.create!(inputs[:body])
|
220
|
+
status 201
|
221
|
+
user.to_h
|
222
|
+
end
|
223
|
+
|
224
|
+
update do |inputs|
|
225
|
+
user = User.find(inputs[:id]) || halt(404)
|
226
|
+
user.update!(inputs[:body])
|
227
|
+
user.to_h
|
228
|
+
end
|
229
|
+
|
230
|
+
destroy do |inputs|
|
231
|
+
user = User.find(inputs[:id]) || halt(404)
|
232
|
+
user.destroy!
|
233
|
+
status 204
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Custom endpoints
|
238
|
+
custom :post, ':id/activate' do |inputs|
|
239
|
+
user = User.find(inputs[:id]) || halt(404)
|
240
|
+
user.update!(active: true)
|
241
|
+
{ message: 'User activated', user: user.to_h }
|
242
|
+
end
|
202
243
|
end
|
203
244
|
end
|
204
245
|
```
|
205
246
|
|
206
|
-
|
247
|
+
## 🔐 Authentication
|
248
|
+
|
249
|
+
### OAuth2 + Auth0 Integration
|
207
250
|
|
208
251
|
```ruby
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
252
|
+
class SecureAPI < SinatraRapiTapir
|
253
|
+
rapitapir do
|
254
|
+
info(title: 'Secure API', version: '2.0.0')
|
255
|
+
|
256
|
+
# Configure Auth0 OAuth2
|
257
|
+
oauth2_auth0 :auth0,
|
258
|
+
domain: ENV['AUTH0_DOMAIN'],
|
259
|
+
audience: ENV['AUTH0_AUDIENCE'],
|
260
|
+
realm: 'API Access'
|
261
|
+
|
262
|
+
production_defaults!
|
215
263
|
end
|
216
|
-
end
|
217
|
-
```
|
218
|
-
|
219
|
-
### Custom Endpoints
|
220
264
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
265
|
+
# Protected endpoint with scopes
|
266
|
+
endpoint(
|
267
|
+
GET('/profile')
|
268
|
+
.summary('Get user profile')
|
269
|
+
.bearer_auth(scopes: ['read:profile'])
|
270
|
+
.ok(T.hash({
|
271
|
+
"user" => T.hash({
|
272
|
+
"id" => T.string,
|
273
|
+
"email" => T.string,
|
274
|
+
"name" => T.string
|
275
|
+
})
|
276
|
+
}))
|
277
|
+
.build
|
278
|
+
) do
|
279
|
+
user_info = current_auth_context[:user_info]
|
280
|
+
{
|
281
|
+
user: {
|
282
|
+
id: user_info['sub'],
|
283
|
+
email: user_info['email'],
|
284
|
+
name: user_info['name']
|
285
|
+
}
|
232
286
|
}
|
233
|
-
) do |inputs|
|
234
|
-
Task.where(status: inputs[:status])
|
235
287
|
end
|
236
|
-
end
|
237
|
-
```
|
238
|
-
|
239
|
-
### Different Scopes for Operations
|
240
288
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
show(scopes: ['read']) { |inputs| User.find(inputs[:id]) }
|
246
|
-
create(scopes: ['write']) { |inputs| User.create(inputs[:body]) }
|
247
|
-
update(scopes: ['write']) { |inputs| User.update(inputs[:id], inputs[:body]) }
|
248
|
-
destroy(scopes: ['admin']) { |inputs| User.delete(inputs[:id]) }
|
289
|
+
# Scope-based protection for multiple endpoints
|
290
|
+
protect_with_oauth2 scopes: ['api:read'] do
|
291
|
+
endpoint(GET('/protected').ok(T.hash({})).build) { { data: 'protected' } }
|
292
|
+
endpoint(GET('/also-protected').ok(T.hash({})).build) { { data: 'also protected' } }
|
249
293
|
end
|
250
294
|
end
|
251
295
|
```
|
252
296
|
|
253
|
-
|
297
|
+
### Bearer Token Authentication
|
254
298
|
|
255
299
|
```ruby
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
# Check specific scope
|
261
|
-
require_scope!('admin')
|
262
|
-
|
263
|
-
# Get current user
|
264
|
-
user = current_user
|
265
|
-
|
266
|
-
# Check if authenticated (non-throwing)
|
267
|
-
if authenticated?
|
268
|
-
# User is logged in
|
300
|
+
class TokenAPI < SinatraRapiTapir
|
301
|
+
rapitapir do
|
302
|
+
bearer_auth :api_key, realm: 'API'
|
303
|
+
production_defaults!
|
269
304
|
end
|
270
305
|
|
271
|
-
|
272
|
-
|
273
|
-
|
306
|
+
endpoint(
|
307
|
+
GET('/secure-data')
|
308
|
+
.bearer_auth
|
309
|
+
.ok(T.array(T.hash({})))
|
310
|
+
.build
|
311
|
+
) do
|
312
|
+
# current_auth_context available here
|
313
|
+
SecureData.for_user(current_auth_context[:user_id])
|
274
314
|
end
|
275
315
|
end
|
276
316
|
```
|
277
317
|
|
278
|
-
##
|
318
|
+
## 🤖 AI Integration
|
279
319
|
|
280
|
-
###
|
320
|
+
### LLM Instructions and RAG
|
281
321
|
|
282
322
|
```ruby
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
allow_credentials: true
|
289
|
-
)
|
290
|
-
end
|
291
|
-
```
|
292
|
-
|
293
|
-
### Rate Limiting
|
294
|
-
|
295
|
-
```ruby
|
296
|
-
rapitapir do
|
297
|
-
rate_limiting(
|
298
|
-
requests_per_minute: 60,
|
299
|
-
requests_per_hour: 1000
|
300
|
-
)
|
301
|
-
end
|
302
|
-
```
|
323
|
+
class AIEnhancedAPI < SinatraRapiTapir
|
324
|
+
rapitapir do
|
325
|
+
info(title: 'AI-Enhanced API', version: '2.0.0')
|
326
|
+
development_defaults!
|
327
|
+
end
|
303
328
|
|
304
|
-
|
329
|
+
# AI-powered search with RAG
|
330
|
+
endpoint(
|
331
|
+
GET('/books/ai-search')
|
332
|
+
.query(:query, T.string, description: 'Natural language search query')
|
333
|
+
.summary('AI-powered semantic book search')
|
334
|
+
.tags('AI', 'Search')
|
335
|
+
.ok(T.hash({
|
336
|
+
"books" => T.array(BOOK_SCHEMA),
|
337
|
+
"reasoning" => T.string,
|
338
|
+
"confidence" => T.float
|
339
|
+
}))
|
340
|
+
.enable_rag(
|
341
|
+
retrieval_backend: :memory,
|
342
|
+
llm_provider: :openai
|
343
|
+
)
|
344
|
+
.enable_mcp # Export for AI agents
|
345
|
+
.build
|
346
|
+
) do |inputs|
|
347
|
+
# RAG context automatically injected
|
348
|
+
results = AIBookSearch.semantic_search(
|
349
|
+
query: inputs[:query],
|
350
|
+
context: rag_context
|
351
|
+
)
|
352
|
+
|
353
|
+
{
|
354
|
+
books: results[:books],
|
355
|
+
reasoning: results[:explanation],
|
356
|
+
confidence: results[:confidence_score]
|
357
|
+
}
|
358
|
+
end
|
305
359
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
360
|
+
# Generate LLM instructions for endpoints
|
361
|
+
endpoint(
|
362
|
+
GET('/ai/instructions/:endpoint_id')
|
363
|
+
.path_param(:endpoint_id, T.string)
|
364
|
+
.query(:purpose, T.string(enum: %w[validation transformation analysis documentation testing completion]))
|
365
|
+
.summary('Generate LLM instructions for an endpoint')
|
366
|
+
.ok(T.hash({
|
367
|
+
"instructions" => T.string,
|
368
|
+
"metadata" => T.hash({})
|
369
|
+
}))
|
370
|
+
.build
|
371
|
+
) do |inputs|
|
372
|
+
generator = RapiTapir::AI::LLMInstruction::Generator.new
|
373
|
+
endpoint = find_endpoint(inputs[:endpoint_id])
|
374
|
+
|
375
|
+
instructions = generator.generate_instructions(
|
376
|
+
endpoint: endpoint,
|
377
|
+
purpose: inputs[:purpose].to_sym
|
378
|
+
)
|
379
|
+
|
380
|
+
{
|
381
|
+
instructions: instructions,
|
382
|
+
metadata: {
|
383
|
+
endpoint_id: inputs[:endpoint_id],
|
384
|
+
purpose: inputs[:purpose],
|
385
|
+
generated_at: Time.now
|
386
|
+
}
|
387
|
+
}
|
388
|
+
end
|
312
389
|
end
|
313
390
|
```
|
314
391
|
|
315
|
-
##
|
316
|
-
|
317
|
-
### Automatic OpenAPI Generation
|
318
|
-
|
319
|
-
The extension automatically generates OpenAPI 3.0 specifications from your endpoint definitions:
|
320
|
-
|
321
|
-
- **Swagger UI**: Beautiful, interactive documentation
|
322
|
-
- **Type validation**: Schemas automatically derived from RapiTapir types
|
323
|
-
- **Authentication schemes**: Security requirements automatically added
|
324
|
-
- **Request/response examples**: Generated from your schemas
|
392
|
+
## 📊 Observability
|
325
393
|
|
326
|
-
###
|
394
|
+
### Comprehensive Monitoring
|
327
395
|
|
328
396
|
```ruby
|
329
|
-
|
330
|
-
|
331
|
-
title: '
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
397
|
+
class MonitoredAPI < SinatraRapiTapir
|
398
|
+
rapitapir do
|
399
|
+
info(title: 'Monitored API', version: '2.0.0')
|
400
|
+
|
401
|
+
enable_observability do |config|
|
402
|
+
# Health checks
|
403
|
+
config.health_checks.enable(path: '/health')
|
404
|
+
config.health_checks.add_check('database') { Database.healthy? }
|
405
|
+
config.health_checks.add_check('redis') { Redis.current.ping == 'PONG' }
|
406
|
+
|
407
|
+
# OpenTelemetry tracing
|
408
|
+
config.tracing.enable_opentelemetry(
|
409
|
+
service_name: 'my-api',
|
410
|
+
exporters: [:honeycomb, :jaeger]
|
411
|
+
)
|
412
|
+
|
413
|
+
# Prometheus metrics
|
414
|
+
config.metrics.enable_prometheus(namespace: 'my_api')
|
415
|
+
|
416
|
+
# Structured logging
|
417
|
+
config.logging.enable_structured(format: :json)
|
418
|
+
end
|
419
|
+
|
420
|
+
production_defaults!
|
421
|
+
end
|
422
|
+
|
423
|
+
# Endpoint with observability features
|
424
|
+
endpoint(
|
425
|
+
GET('/monitored-endpoint')
|
426
|
+
.with_metrics('endpoint_requests', labels: { operation: 'get' })
|
427
|
+
.with_tracing('fetch_data')
|
428
|
+
.ok(T.hash({ "data" => T.string }))
|
429
|
+
.build
|
430
|
+
) do
|
431
|
+
# Automatic metrics and tracing
|
432
|
+
{ data: 'monitored response' }
|
433
|
+
end
|
344
434
|
end
|
345
435
|
```
|
346
436
|
|
347
|
-
##
|
437
|
+
## 🧪 Testing
|
348
438
|
|
349
|
-
###
|
439
|
+
### Test Your Endpoints
|
350
440
|
|
351
441
|
```ruby
|
352
|
-
require '
|
353
|
-
require 'rapitapir/sinatra/extension'
|
442
|
+
require 'rack/test'
|
354
443
|
|
355
|
-
|
356
|
-
|
444
|
+
RSpec.describe MyAPI do
|
445
|
+
include Rack::Test::Methods
|
357
446
|
|
358
|
-
|
359
|
-
|
360
|
-
development_defaults!
|
361
|
-
public_paths '/books'
|
447
|
+
def app
|
448
|
+
MyAPI
|
362
449
|
end
|
363
450
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
451
|
+
it 'returns hello message' do
|
452
|
+
get '/hello?name=World'
|
453
|
+
|
454
|
+
expect(last_response).to be_ok
|
455
|
+
expect(JSON.parse(last_response.body)).to eq({
|
456
|
+
'message' => 'Hello, World!'
|
457
|
+
})
|
458
|
+
end
|
369
459
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
update { |inputs| Book.update(inputs[:id], inputs[:body]) }
|
376
|
-
end
|
460
|
+
it 'validates required parameters' do
|
461
|
+
get '/hello'
|
462
|
+
|
463
|
+
expect(last_response.status).to eq(400)
|
464
|
+
expect(JSON.parse(last_response.body)).to include('error')
|
377
465
|
end
|
378
466
|
end
|
379
467
|
```
|
380
468
|
|
381
|
-
###
|
382
|
-
|
383
|
-
See `examples/enterprise_extension_demo.rb` for a comprehensive example with:
|
384
|
-
- Bearer token authentication
|
385
|
-
- Multiple resources
|
386
|
-
- Custom endpoints
|
387
|
-
- Admin-only operations
|
388
|
-
- Full middleware stack
|
389
|
-
|
390
|
-
## 🏛️ Architecture
|
469
|
+
### Validate Endpoint Definitions
|
391
470
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
- **Configuration**: Handles API configuration only
|
397
|
-
- **ResourceBuilder**: Creates RESTful endpoints only
|
398
|
-
- **SwaggerUIGenerator**: Generates documentation UI only
|
471
|
+
```ruby
|
472
|
+
# Validate all endpoints
|
473
|
+
validator = RapiTapir::CLI::Validator.new(MyAPI.rapitapir_endpoints)
|
474
|
+
result = validator.validate
|
399
475
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
- **Customizable**: Resource builder supports custom endpoints
|
476
|
+
puts "Validation passed: #{result}"
|
477
|
+
puts "Errors: #{validator.errors}" unless result
|
478
|
+
```
|
404
479
|
|
405
|
-
|
406
|
-
- **Endpoint compatibility**: Works with any RapiTapir endpoint
|
407
|
-
- **Authentication schemes**: All auth schemes follow same interface
|
480
|
+
## 🔗 Comparison: Base Class vs Extension
|
408
481
|
|
409
|
-
|
410
|
-
|
411
|
-
|
482
|
+
| Feature | SinatraRapiTapir | Manual Extension |
|
483
|
+
|---------|------------------|------------------|
|
484
|
+
| Setup complexity | Single inheritance | Manual registration |
|
485
|
+
| HTTP verb methods | ✅ Built-in | ✅ Available |
|
486
|
+
| T shortcut | ✅ Automatic | ✅ Available |
|
487
|
+
| Configuration | ✅ Simple | ✅ Full control |
|
488
|
+
| Sinatra features | ✅ All available | ✅ All available |
|
489
|
+
| Customization | Good | Excellent |
|
490
|
+
| Use case | New APIs, rapid prototyping | Existing apps, advanced needs |
|
412
491
|
|
413
|
-
|
414
|
-
- **Abstractions**: Depends on RapiTapir abstractions, not concrete implementations
|
415
|
-
- **Injection**: Authentication and validation logic is injected via procs
|
492
|
+
## 📚 Examples
|
416
493
|
|
417
|
-
|
494
|
+
### Complete RESTful API
|
495
|
+
See [examples/working_simple_example.rb](../examples/working_simple_example.rb)
|
418
496
|
|
419
|
-
|
497
|
+
### Authentication with Auth0
|
498
|
+
See [examples/oauth2/](../examples/oauth2/)
|
420
499
|
|
421
|
-
###
|
422
|
-
|
423
|
-
class MyAPI < Sinatra::Base
|
424
|
-
use SomeMiddleware
|
425
|
-
use SomeOtherMiddleware
|
426
|
-
|
427
|
-
get '/tasks' do
|
428
|
-
# Manual authentication
|
429
|
-
# Manual validation
|
430
|
-
# Manual response formatting
|
431
|
-
end
|
432
|
-
|
433
|
-
post '/tasks' do
|
434
|
-
# Repeat authentication, validation, etc.
|
435
|
-
end
|
436
|
-
|
437
|
-
# Repeat for every endpoint...
|
438
|
-
end
|
439
|
-
```
|
500
|
+
### AI-Powered Features
|
501
|
+
See [examples/auto_derivation_ruby_friendly.rb](../examples/auto_derivation_ruby_friendly.rb)
|
440
502
|
|
441
|
-
###
|
442
|
-
|
443
|
-
class MyAPI < Sinatra::Base
|
444
|
-
register RapiTapir::Sinatra::Extension
|
445
|
-
|
446
|
-
rapitapir do
|
447
|
-
bearer_auth { /* config */ }
|
448
|
-
development_defaults!
|
449
|
-
end
|
450
|
-
|
451
|
-
api_resource '/tasks', schema: TASK_SCHEMA do
|
452
|
-
crud { /* handlers */ }
|
453
|
-
end
|
454
|
-
end
|
455
|
-
```
|
503
|
+
### Production Observability
|
504
|
+
See [examples/observability/](../examples/observability/)
|
456
505
|
|
457
|
-
##
|
506
|
+
## 🎯 Best Practices
|
458
507
|
|
459
|
-
1.
|
460
|
-
2.
|
461
|
-
3.
|
462
|
-
4.
|
463
|
-
5.
|
508
|
+
1. **Use SinatraRapiTapir** for new projects - it's the simplest approach
|
509
|
+
2. **Leverage T shortcuts** everywhere for cleaner type definitions
|
510
|
+
3. **Use resource builders** for RESTful APIs to reduce boilerplate
|
511
|
+
4. **Enable observability** for production deployments
|
512
|
+
5. **Implement proper error handling** with meaningful error responses
|
513
|
+
6. **Document with examples** using the built-in OpenAPI generation
|
514
|
+
7. **Test endpoint validation** to ensure type safety works correctly
|
464
515
|
|
465
|
-
|
516
|
+
---
|
466
517
|
|
467
|
-
|
518
|
+
RapiTapir + Sinatra = **APIs so fast and clean, they practically run wild!** 🦙⚡
|