rapitapir 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +94 -0
- data/CLEANUP_SUMMARY.md +155 -0
- data/CONTRIBUTING.md +280 -0
- data/LICENSE +21 -0
- data/README.md +485 -0
- data/debug_hash.rb +20 -0
- data/docs/EXTENSION_COMPARISON.md +388 -0
- data/docs/SINATRA_EXTENSION.md +467 -0
- data/docs/archive/PHASE_1_2_COMPLETE.md +77 -0
- data/docs/archive/PHASE_1_3_COMPLETE.md +152 -0
- data/docs/archive/PHASE_2_1_OBSERVABILITY_COMPLETED.md +203 -0
- data/docs/archive/PHASE_2_SUMMARY.md +209 -0
- data/docs/archive/REFACTORING_SUMMARY.md +184 -0
- data/docs/archive/phase_1_3_plan.md +136 -0
- data/docs/archive/sinatra_extension_summary.md +188 -0
- data/docs/archive/sinatra_working_solution.md +113 -0
- data/docs/archive/typescript-client-generator-summary.md +259 -0
- data/docs/auto-derivation.md +146 -0
- data/docs/blueprint.md +1091 -0
- data/docs/endpoint-definition.md +211 -0
- data/docs/github_pages_fix.md +52 -0
- data/docs/github_pages_setup.md +49 -0
- data/docs/implementation-status.md +357 -0
- data/docs/observability.md +647 -0
- data/docs/phase3-plan.md +108 -0
- data/docs/sinatra_rapitapir.md +87 -0
- data/docs/type_shortcuts.md +146 -0
- data/examples/README_ENTERPRISE.md +202 -0
- data/examples/authentication_example.rb +192 -0
- data/examples/auto_derivation_ruby_friendly.rb +163 -0
- data/examples/cli/user_api_endpoints.rb +56 -0
- data/examples/client/typescript_client_example.rb +102 -0
- data/examples/client/user-api-client.ts +193 -0
- data/examples/demo_api.rb +41 -0
- data/examples/docs/documentation_example.rb +112 -0
- data/examples/docs/user-api-docs.html +789 -0
- data/examples/docs/user-api-docs.md +403 -0
- data/examples/enhanced_auto_derivation_test.rb +83 -0
- data/examples/enterprise_extension_demo.rb +417 -0
- data/examples/enterprise_rapitapir_api.rb +662 -0
- data/examples/getting_started_extension.rb +218 -0
- data/examples/hello_world.rb +74 -0
- data/examples/oauth2/.env.example +19 -0
- data/examples/oauth2/README.md +205 -0
- data/examples/oauth2/generic_oauth2_api.rb +226 -0
- data/examples/oauth2/get_token.rb +72 -0
- data/examples/oauth2/songs_api_with_auth0.rb +320 -0
- data/examples/oauth2/test_api.sh +16 -0
- data/examples/oauth2/test_songs_api.sh +110 -0
- data/examples/observability/.env.example +35 -0
- data/examples/observability/README.md +230 -0
- data/examples/observability/README_HONEYCOMB.md +332 -0
- data/examples/observability/advanced_setup.rb +384 -0
- data/examples/observability/basic_setup.rb +192 -0
- data/examples/observability/complete_test.rb +121 -0
- data/examples/observability/honeycomb_example.rb +523 -0
- data/examples/observability/honeycomb_rapitapir_clean.rb +488 -0
- data/examples/observability/honeycomb_rapitapir_example.rb +523 -0
- data/examples/observability/honeycomb_working_example.rb +489 -0
- data/examples/observability/quick_test.rb +78 -0
- data/examples/observability/simple_test.rb +14 -0
- data/examples/observability/test_honeycomb_demo.rb +354 -0
- data/examples/observability/test_live_honeycomb.rb +111 -0
- data/examples/observability/test_validation.rb +78 -0
- data/examples/observability/test_working_validation.rb +66 -0
- data/examples/openapi/user_api_schema.rb +132 -0
- data/examples/production_ready_example.rb +105 -0
- data/examples/rails/users_controller.rb +146 -0
- data/examples/readme/basic_sinatra_example.rb +128 -0
- data/examples/server/user_api.rb +179 -0
- data/examples/simple_auto_derivation_demo.rb +44 -0
- data/examples/simple_demo_api.rb +18 -0
- data/examples/sinatra/user_app.rb +127 -0
- data/examples/t_shortcut_demo.rb +59 -0
- data/examples/user_api.rb +190 -0
- data/examples/working_getting_started.rb +184 -0
- data/examples/working_simple_example.rb +195 -0
- data/lib/rapitapir/auth/configuration.rb +129 -0
- data/lib/rapitapir/auth/context.rb +122 -0
- data/lib/rapitapir/auth/errors.rb +104 -0
- data/lib/rapitapir/auth/middleware.rb +324 -0
- data/lib/rapitapir/auth/oauth2.rb +350 -0
- data/lib/rapitapir/auth/schemes.rb +420 -0
- data/lib/rapitapir/auth.rb +113 -0
- data/lib/rapitapir/cli/command.rb +535 -0
- data/lib/rapitapir/cli/server.rb +243 -0
- data/lib/rapitapir/cli/validator.rb +373 -0
- data/lib/rapitapir/client/generator_base.rb +272 -0
- data/lib/rapitapir/client/typescript_generator.rb +350 -0
- data/lib/rapitapir/core/endpoint.rb +158 -0
- data/lib/rapitapir/core/enhanced_endpoint.rb +235 -0
- data/lib/rapitapir/core/input.rb +182 -0
- data/lib/rapitapir/core/output.rb +164 -0
- data/lib/rapitapir/core/request.rb +19 -0
- data/lib/rapitapir/core/response.rb +17 -0
- data/lib/rapitapir/docs/html_generator.rb +780 -0
- data/lib/rapitapir/docs/markdown_generator.rb +464 -0
- data/lib/rapitapir/dsl/endpoint_dsl.rb +116 -0
- data/lib/rapitapir/dsl/enhanced_endpoint_dsl.rb +62 -0
- data/lib/rapitapir/dsl/enhanced_input.rb +73 -0
- data/lib/rapitapir/dsl/enhanced_output.rb +63 -0
- data/lib/rapitapir/dsl/enhanced_structures.rb +393 -0
- data/lib/rapitapir/dsl/fluent_dsl.rb +72 -0
- data/lib/rapitapir/dsl/fluent_endpoint_builder.rb +316 -0
- data/lib/rapitapir/dsl/http_verbs.rb +77 -0
- data/lib/rapitapir/dsl/input_methods.rb +47 -0
- data/lib/rapitapir/dsl/observability_methods.rb +81 -0
- data/lib/rapitapir/dsl/output_methods.rb +43 -0
- data/lib/rapitapir/dsl/type_resolution.rb +43 -0
- data/lib/rapitapir/observability/configuration.rb +108 -0
- data/lib/rapitapir/observability/health_check.rb +236 -0
- data/lib/rapitapir/observability/logging.rb +270 -0
- data/lib/rapitapir/observability/metrics.rb +203 -0
- data/lib/rapitapir/observability/middleware.rb +243 -0
- data/lib/rapitapir/observability/tracing.rb +143 -0
- data/lib/rapitapir/observability.rb +28 -0
- data/lib/rapitapir/openapi/schema_generator.rb +403 -0
- data/lib/rapitapir/schema.rb +136 -0
- data/lib/rapitapir/server/enhanced_rack_adapter.rb +379 -0
- data/lib/rapitapir/server/middleware.rb +120 -0
- data/lib/rapitapir/server/path_matcher.rb +45 -0
- data/lib/rapitapir/server/rack_adapter.rb +215 -0
- data/lib/rapitapir/server/rails_adapter.rb +17 -0
- data/lib/rapitapir/server/rails_adapter_class.rb +53 -0
- data/lib/rapitapir/server/rails_controller.rb +72 -0
- data/lib/rapitapir/server/rails_input_processor.rb +73 -0
- data/lib/rapitapir/server/rails_response_handler.rb +29 -0
- data/lib/rapitapir/server/sinatra_adapter.rb +200 -0
- data/lib/rapitapir/server/sinatra_integration.rb +93 -0
- data/lib/rapitapir/sinatra/configuration.rb +91 -0
- data/lib/rapitapir/sinatra/extension.rb +214 -0
- data/lib/rapitapir/sinatra/oauth2_helpers.rb +236 -0
- data/lib/rapitapir/sinatra/resource_builder.rb +152 -0
- data/lib/rapitapir/sinatra/swagger_ui_generator.rb +166 -0
- data/lib/rapitapir/sinatra_rapitapir.rb +40 -0
- data/lib/rapitapir/types/array.rb +163 -0
- data/lib/rapitapir/types/auto_derivation.rb +265 -0
- data/lib/rapitapir/types/base.rb +146 -0
- data/lib/rapitapir/types/boolean.rb +46 -0
- data/lib/rapitapir/types/date.rb +92 -0
- data/lib/rapitapir/types/datetime.rb +98 -0
- data/lib/rapitapir/types/email.rb +32 -0
- data/lib/rapitapir/types/float.rb +134 -0
- data/lib/rapitapir/types/hash.rb +161 -0
- data/lib/rapitapir/types/integer.rb +143 -0
- data/lib/rapitapir/types/object.rb +156 -0
- data/lib/rapitapir/types/optional.rb +65 -0
- data/lib/rapitapir/types/string.rb +185 -0
- data/lib/rapitapir/types/uuid.rb +32 -0
- data/lib/rapitapir/types.rb +155 -0
- data/lib/rapitapir/version.rb +5 -0
- data/lib/rapitapir.rb +173 -0
- data/rapitapir.gemspec +66 -0
- metadata +387 -0
data/README.md
ADDED
@@ -0,0 +1,485 @@
|
|
1
|
+
# RapiTapir## ๐ What's New
|
2
|
+
|
3
|
+
- **โจ - **๐ Type Shortcuts**: Global `T.string`, `T.integer`, etc. (automatically available!)
|
4
|
+
- **๐ GitHub Pages**: Modern documentation deployment with GitHub Actionsean Base Class**: `class MyAPI < SinatraRapiTapir` - the simplest way to create APIs
|
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/)
|
13
|
+
[](Gemfile)
|
14
|
+
[](LICENSE)
|
15
|
+
|
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 client generation.
|
17
|
+
|
18
|
+
## ๐ What's New
|
19
|
+
|
20
|
+
- **โจ Clean Base Class**: `class MyAPI < SinatraRapiTapir` - the simplest way to create APIs
|
21
|
+
- **๐ฏ Enhanced HTTP DSL**: Built-in GET, POST, PUT, DELETE methods with fluent chaining
|
22
|
+
- **๐ง Zero Boilerplate**: Automatic extension registration and feature setup
|
23
|
+
- **๏ฟฝ Type Shortcuts**: Use `T.string` instead of `RapiTapir::Types.string` for cleaner code
|
24
|
+
- **๏ฟฝ๐ GitHub Pages Ready**: Modern documentation deployment with GitHub Actions
|
25
|
+
- **๐งช Comprehensive Tests**: 470 tests passing with 70% coverage
|
26
|
+
|
27
|
+
## โจ Why RapiTapir?
|
28
|
+
|
29
|
+
- **๐ Type Safety**: Strong typing for inputs and outputs with runtime validation
|
30
|
+
- **๐ Auto Documentation**: OpenAPI 3.0 specs generated automatically from your code
|
31
|
+
- **๐ Framework Agnostic**: Works with Sinatra, Rails, and any Rack-based framework
|
32
|
+
- **๐ก๏ธ Production Ready**: Built-in security, observability, and authentication features
|
33
|
+
- **๐ Ruby Native**: Designed specifically for Ruby developers who love clean, readable code
|
34
|
+
- **๐ง Zero Config**: Get started in minutes with sensible defaults
|
35
|
+
- **โจ Clean Syntax**: Elegant base class: `class MyAPI < SinatraRapiTapir`
|
36
|
+
- **๐ฏ Enhanced DSL**: Built-in HTTP verb methods (GET, POST, PUT, etc.)
|
37
|
+
- **๏ฟฝ Type Shortcuts**: Clean type syntax with `T.string`, `T.integer`, etc.
|
38
|
+
- **๏ฟฝ๐ GitHub Pages**: Modern documentation deployment with GitHub Actions
|
39
|
+
|
40
|
+
## ๐ Quick Start
|
41
|
+
|
42
|
+
### Installation
|
43
|
+
|
44
|
+
Add to your Gemfile:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
gem 'rapitapir'
|
48
|
+
```
|
49
|
+
|
50
|
+
### Basic Sinatra Example
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'rapitapir' # Only one require needed!
|
54
|
+
|
55
|
+
class BookAPI < SinatraRapiTapir
|
56
|
+
# Configure API information
|
57
|
+
rapitapir do
|
58
|
+
info(
|
59
|
+
title: 'Book API',
|
60
|
+
description: 'A simple book management API',
|
61
|
+
version: '1.0.0'
|
62
|
+
)
|
63
|
+
development_defaults! # Auto CORS, docs, health checks
|
64
|
+
end
|
65
|
+
|
66
|
+
# Define your data schema with T shortcut (globally available!)
|
67
|
+
BOOK_SCHEMA = T.hash({
|
68
|
+
"id" => T.integer,
|
69
|
+
"title" => T.string(min_length: 1, max_length: 255),
|
70
|
+
"author" => T.string(min_length: 1),
|
71
|
+
"published" => T.boolean,
|
72
|
+
"isbn" => T.optional(T.string),
|
73
|
+
"pages" => T.optional(T.integer(minimum: 1))
|
74
|
+
})
|
75
|
+
|
76
|
+
# Define endpoints with the elegant resource DSL and enhanced HTTP verbs
|
77
|
+
api_resource '/books', schema: BOOK_SCHEMA do
|
78
|
+
crud do
|
79
|
+
index { Book.all }
|
80
|
+
|
81
|
+
show do |inputs|
|
82
|
+
Book.find(inputs[:id]) || halt(404, { error: 'Book not found' }.to_json)
|
83
|
+
end
|
84
|
+
|
85
|
+
create do |inputs|
|
86
|
+
Book.create(inputs[:body])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Custom endpoint using enhanced DSL
|
91
|
+
custom :get, 'featured' do
|
92
|
+
Book.where(featured: true)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Alternative endpoint definition using enhanced HTTP verb DSL
|
97
|
+
endpoint(
|
98
|
+
GET('/books/search')
|
99
|
+
.query(:q, T.string(min_length: 1), description: 'Search query')
|
100
|
+
.query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)), description: 'Results limit')
|
101
|
+
.summary('Search books')
|
102
|
+
.description('Search books by title or author')
|
103
|
+
.tags('Search')
|
104
|
+
.ok(T.array(BOOK_SCHEMA))
|
105
|
+
.bad_request(T.hash({ "error" => T.string }), description: 'Invalid search parameters')
|
106
|
+
.build
|
107
|
+
) do |inputs|
|
108
|
+
query = inputs[:q]
|
109
|
+
limit = inputs[:limit] || 20
|
110
|
+
|
111
|
+
books = Book.search(query).limit(limit)
|
112
|
+
books.map(&:to_h)
|
113
|
+
end
|
114
|
+
|
115
|
+
run! if __FILE__ == $0
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
Start your server and visit:
|
120
|
+
- **๐ Interactive Documentation**: `http://localhost:4567/docs`
|
121
|
+
- **๐ OpenAPI Specification**: `http://localhost:4567/openapi.json`
|
122
|
+
|
123
|
+
That's it! You now have a fully documented, type-safe API with interactive documentation.
|
124
|
+
|
125
|
+
## ๐๏ธ Core Features
|
126
|
+
|
127
|
+
### Clean Base Class Syntax
|
128
|
+
|
129
|
+
Create APIs with the cleanest possible syntax:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
require 'rapitapir'
|
133
|
+
|
134
|
+
class MyAPI < SinatraRapiTapir
|
135
|
+
rapitapir do
|
136
|
+
info(title: 'My API', version: '1.0.0')
|
137
|
+
development_defaults! # Auto CORS, docs, health checks
|
138
|
+
end
|
139
|
+
|
140
|
+
# Enhanced HTTP verb DSL automatically available + T shortcut for types
|
141
|
+
endpoint(
|
142
|
+
GET('/books')
|
143
|
+
.summary('List all books')
|
144
|
+
.ok(T.array(BOOK_SCHEMA))
|
145
|
+
.error_response(500, T.hash({ "error" => T.string }))
|
146
|
+
.build
|
147
|
+
) { Book.all }
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
### Type-Safe API Design
|
152
|
+
|
153
|
+
Define your data schemas once and use them everywhere:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
# T shortcut is automatically available - no setup needed!
|
157
|
+
USER_SCHEMA = T.hash({
|
158
|
+
"id" => T.integer,
|
159
|
+
"name" => T.string(min_length: 1, max_length: 100),
|
160
|
+
"email" => T.email,
|
161
|
+
"age" => T.optional(T.integer(min: 0, max: 150)),
|
162
|
+
"profile" => T.optional(T.hash({
|
163
|
+
"bio" => T.string(max_length: 500),
|
164
|
+
"avatar_url" => T.string(format: :url)
|
165
|
+
}))
|
166
|
+
})
|
167
|
+
```
|
168
|
+
|
169
|
+
### Fluent Endpoint Definition
|
170
|
+
|
171
|
+
Create endpoints with a clean, readable DSL:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
# Using the enhanced HTTP verb DSL with T shortcut
|
175
|
+
endpoint(
|
176
|
+
GET('/users/:id')
|
177
|
+
.summary('Get user by ID')
|
178
|
+
.path_param(:id, T.integer(minimum: 1))
|
179
|
+
.query(:include, T.optional(T.array(T.string)), description: 'Related data to include')
|
180
|
+
.ok(USER_SCHEMA)
|
181
|
+
.error_response(404, T.hash({ "error" => T.string }), description: 'User not found')
|
182
|
+
.error_response(422, T.hash({
|
183
|
+
"error" => T.string,
|
184
|
+
"details" => T.array(T.hash({
|
185
|
+
"field" => T.string,
|
186
|
+
"message" => T.string
|
187
|
+
}))
|
188
|
+
}))
|
189
|
+
.build
|
190
|
+
) do |inputs|
|
191
|
+
user = User.find(inputs[:id])
|
192
|
+
halt 404, { error: 'User not found' }.to_json unless user
|
193
|
+
|
194
|
+
# Handle optional includes
|
195
|
+
if inputs[:include]&.include?('profile')
|
196
|
+
user = user.with_profile
|
197
|
+
end
|
198
|
+
|
199
|
+
user.to_h
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
### RESTful Resource Builder
|
204
|
+
|
205
|
+
Build complete CRUD APIs with minimal code:
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
# Enhanced resource builder with custom validations and relationships
|
209
|
+
api_resource '/users', schema: USER_SCHEMA do
|
210
|
+
crud do
|
211
|
+
index do
|
212
|
+
# Automatic pagination and filtering
|
213
|
+
users = User.all
|
214
|
+
users = users.where(active: true) if params[:active] == 'true'
|
215
|
+
users.limit(params[:limit] || 50)
|
216
|
+
end
|
217
|
+
|
218
|
+
show { |inputs| User.find(inputs[:id]) }
|
219
|
+
|
220
|
+
create do |inputs|
|
221
|
+
user = User.create(inputs[:body])
|
222
|
+
status 201
|
223
|
+
user.to_h
|
224
|
+
end
|
225
|
+
|
226
|
+
update { |inputs| User.update(inputs[:id], inputs[:body]) }
|
227
|
+
destroy { |inputs| User.delete(inputs[:id]); status 204 }
|
228
|
+
end
|
229
|
+
|
230
|
+
# Add custom endpoints with full type safety
|
231
|
+
custom :get, 'active' do
|
232
|
+
User.where(active: true).map(&:to_h)
|
233
|
+
end
|
234
|
+
|
235
|
+
custom :post, ':id/avatar' do |inputs|
|
236
|
+
user = User.find(inputs[:id])
|
237
|
+
user.update_avatar(inputs[:body][:avatar_data])
|
238
|
+
{ success: true }
|
239
|
+
end
|
240
|
+
end
|
241
|
+
```
|
242
|
+
|
243
|
+
### Automatic OpenAPI Documentation
|
244
|
+
|
245
|
+
Your API documentation is always up-to-date because it's generated from your actual code:
|
246
|
+
|
247
|
+
- **Interactive Swagger UI** with try-it-out functionality
|
248
|
+
- **Complete OpenAPI 3.0 specification** with schemas, examples, and security
|
249
|
+
- **TypeScript client generation** for frontend teams
|
250
|
+
- **Markdown documentation** for wikis and READMEs
|
251
|
+
|
252
|
+
## ๐ง Framework Integration
|
253
|
+
|
254
|
+
### Sinatra (Recommended)
|
255
|
+
|
256
|
+
**Option 1: Clean Base Class (Recommended)**
|
257
|
+
```ruby
|
258
|
+
require 'rapitapir'
|
259
|
+
|
260
|
+
class MyAPI < SinatraRapiTapir
|
261
|
+
rapitapir do
|
262
|
+
info(title: 'My API', version: '1.0.0')
|
263
|
+
development_defaults!
|
264
|
+
end
|
265
|
+
# Enhanced HTTP verb DSL automatically available
|
266
|
+
end
|
267
|
+
```
|
268
|
+
|
269
|
+
**Option 2: Manual Extension Registration**
|
270
|
+
```ruby
|
271
|
+
require 'rapitapir/sinatra/extension'
|
272
|
+
|
273
|
+
class MyAPI < Sinatra::Base
|
274
|
+
register RapiTapir::Sinatra::Extension
|
275
|
+
# Use the full DSL...
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
### Rack Applications
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
require 'rapitapir/server/rack_adapter'
|
283
|
+
|
284
|
+
class MyRackApp
|
285
|
+
def call(env)
|
286
|
+
# Manual integration with Rack
|
287
|
+
end
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
### Rails Support
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
# In your Rails controller
|
295
|
+
include RapiTapir::Rails::Controller
|
296
|
+
```
|
297
|
+
|
298
|
+
## ๐ก๏ธ Production Features
|
299
|
+
|
300
|
+
### Authentication & Authorization
|
301
|
+
|
302
|
+
```ruby
|
303
|
+
# Bearer token authentication with enhanced syntax
|
304
|
+
class SecureAPI < SinatraRapiTapir
|
305
|
+
rapitapir do
|
306
|
+
info(title: 'Secure API', version: '1.0.0')
|
307
|
+
bearer_auth :api_key, realm: 'API'
|
308
|
+
production_defaults!
|
309
|
+
end
|
310
|
+
|
311
|
+
# Protected endpoint with scope-based authorization
|
312
|
+
endpoint(
|
313
|
+
GET('/admin/users')
|
314
|
+
.summary('List all users (admin only)')
|
315
|
+
.bearer_auth(scopes: ['admin'])
|
316
|
+
.query(:page, T.optional(T.integer(minimum: 1)), description: 'Page number')
|
317
|
+
.query(:per_page, T.optional(T.integer(minimum: 1, maximum: 100)), description: 'Items per page')
|
318
|
+
.ok(T.hash({
|
319
|
+
"users" => T.array(USER_SCHEMA),
|
320
|
+
"pagination" => T.hash({
|
321
|
+
"page" => T.integer,
|
322
|
+
"per_page" => T.integer,
|
323
|
+
"total" => T.integer,
|
324
|
+
"pages" => T.integer
|
325
|
+
})
|
326
|
+
}))
|
327
|
+
.error_response(401, T.hash({ "error" => T.string }), description: 'Unauthorized')
|
328
|
+
.error_response(403, T.hash({ "error" => T.string }), description: 'Insufficient permissions')
|
329
|
+
.build
|
330
|
+
) do |inputs|
|
331
|
+
require_scope!('admin')
|
332
|
+
|
333
|
+
page = inputs[:page] || 1
|
334
|
+
per_page = inputs[:per_page] || 20
|
335
|
+
|
336
|
+
users = User.paginate(page: page, per_page: per_page)
|
337
|
+
|
338
|
+
{
|
339
|
+
users: users.map(&:to_h),
|
340
|
+
pagination: {
|
341
|
+
page: page,
|
342
|
+
per_page: per_page,
|
343
|
+
total: users.total_count,
|
344
|
+
pages: users.total_pages
|
345
|
+
}
|
346
|
+
}
|
347
|
+
end
|
348
|
+
end
|
349
|
+
```
|
350
|
+
|
351
|
+
### Observability
|
352
|
+
|
353
|
+
```ruby
|
354
|
+
class MonitoredAPI < SinatraRapiTapir
|
355
|
+
rapitapir do
|
356
|
+
info(title: 'Monitored API', version: '1.0.0')
|
357
|
+
enable_health_checks path: '/health'
|
358
|
+
enable_metrics
|
359
|
+
production_defaults!
|
360
|
+
end
|
361
|
+
|
362
|
+
# Endpoint with metrics and tracing
|
363
|
+
endpoint(
|
364
|
+
GET('/api/data')
|
365
|
+
.summary('Get data with monitoring')
|
366
|
+
.with_metrics('api_data_requests')
|
367
|
+
.with_tracing('fetch_api_data')
|
368
|
+
.query(:filter, T.optional(T.string), description: 'Data filter')
|
369
|
+
.ok(T.hash({
|
370
|
+
"data" => T.array(T.hash({
|
371
|
+
"id" => T.integer,
|
372
|
+
"value" => T.string,
|
373
|
+
"timestamp" => T.datetime
|
374
|
+
})),
|
375
|
+
"metadata" => T.hash({
|
376
|
+
"total" => T.integer,
|
377
|
+
"filtered" => T.boolean
|
378
|
+
})
|
379
|
+
}))
|
380
|
+
.build
|
381
|
+
) do |inputs|
|
382
|
+
# Your endpoint code with automatic metrics collection
|
383
|
+
data = DataService.fetch(filter: inputs[:filter])
|
384
|
+
|
385
|
+
{
|
386
|
+
data: data.map(&:to_h),
|
387
|
+
metadata: {
|
388
|
+
total: data.count,
|
389
|
+
filtered: inputs[:filter].present?
|
390
|
+
}
|
391
|
+
}
|
392
|
+
end
|
393
|
+
end
|
394
|
+
```
|
395
|
+
|
396
|
+
### Security Middleware
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
# Built-in security features
|
400
|
+
use RapiTapir::Server::Middleware::CORS
|
401
|
+
use RapiTapir::Server::Middleware::RateLimit, requests_per_minute: 100
|
402
|
+
use RapiTapir::Server::Middleware::SecurityHeaders
|
403
|
+
```
|
404
|
+
|
405
|
+
## ๐จ Examples
|
406
|
+
|
407
|
+
Explore our comprehensive examples:
|
408
|
+
|
409
|
+
- **[Hello World](examples/hello_world.rb)** - Minimal API with SinatraRapiTapir base class
|
410
|
+
- **[Getting Started](examples/getting_started_extension.rb)** - Complete bookstore API with CRUD operations
|
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
|
414
|
+
|
415
|
+
## ๐ Documentation
|
416
|
+
|
417
|
+
- **[API Reference](docs/endpoint-definition.md)** - Complete endpoint definition guide
|
418
|
+
- **[SinatraRapiTapir Base Class](docs/sinatra_rapitapir.md)** - Clean inheritance syntax guide
|
419
|
+
- **[Sinatra Extension](docs/SINATRA_EXTENSION.md)** - Detailed Sinatra integration
|
420
|
+
- **[Type System](docs/types.md)** - All available types and validations (use `T.` shortcut!)
|
421
|
+
- **[Authentication](docs/authentication.md)** - Security and auth patterns
|
422
|
+
- **[Observability](docs/observability.md)** - Monitoring and health checks
|
423
|
+
- **[GitHub Pages Setup](docs/github_pages_setup.md)** - Documentation deployment guide
|
424
|
+
|
425
|
+
## ๐งช Testing
|
426
|
+
|
427
|
+
RapiTapir includes comprehensive testing utilities:
|
428
|
+
|
429
|
+
```ruby
|
430
|
+
# Validate your endpoint definitions
|
431
|
+
RapiTapir::CLI::Validator.new(endpoints).validate
|
432
|
+
|
433
|
+
# Generate test fixtures
|
434
|
+
RapiTapir::Testing.generate_fixtures(USER_SCHEMA)
|
435
|
+
```
|
436
|
+
|
437
|
+
Run the test suite:
|
438
|
+
|
439
|
+
```bash
|
440
|
+
bundle exec rspec
|
441
|
+
```
|
442
|
+
|
443
|
+
## ๐ค Contributing
|
444
|
+
|
445
|
+
We love contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
446
|
+
|
447
|
+
### Development Setup
|
448
|
+
|
449
|
+
```bash
|
450
|
+
git clone https://github.com/riccardomerolla/rapitapir.git
|
451
|
+
cd ruby-tapir
|
452
|
+
bundle install
|
453
|
+
bundle exec rspec
|
454
|
+
```
|
455
|
+
|
456
|
+
### Roadmap
|
457
|
+
|
458
|
+
- **Phase 4**: Advanced client generation (Python, Go, etc.)
|
459
|
+
- **Phase 5**: GraphQL integration
|
460
|
+
- **Phase 6**: gRPC support
|
461
|
+
- **Community**: Plugin ecosystem
|
462
|
+
|
463
|
+
## ๏ฟฝ License
|
464
|
+
|
465
|
+
RapiTapir is released under the [MIT License](LICENSE).
|
466
|
+
|
467
|
+
## ๐โโ๏ธ Support
|
468
|
+
|
469
|
+
- **๐ Bug Reports**: [GitHub Issues](https://github.com/riccardomerolla/rapitapir/issues)
|
470
|
+
- **๐ก Feature Requests**: [GitHub Discussions](https://github.com/riccardomerolla/rapitapir/discussions)
|
471
|
+
- **๐ง Email**: riccardo.merolla@gmail.com
|
472
|
+
|
473
|
+
---
|
474
|
+
|
475
|
+
**Built with โค๏ธ for the Ruby and Sinatra community**
|
476
|
+
|
477
|
+
|
478
|
+
## ๐ Acknowledgments
|
479
|
+
|
480
|
+
Inspired by:
|
481
|
+
- [Scala Tapir](https://github.com/softwaremill/tapir) - Type-safe endpoints
|
482
|
+
|
483
|
+
---
|
484
|
+
|
485
|
+
**RapiTapir** - APIs so fast and clean, they practically run wild! ๐ฆโก
|
data/debug_hash.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Test === operator behavior
|
5
|
+
puts '=== Testing === operator ==='
|
6
|
+
puts "Hash === Hash: #{Hash.is_a?(Hash)}"
|
7
|
+
puts "Hash === {}: #{{}.is_a?(Hash)}"
|
8
|
+
puts "Array === Array: #{Array.is_a?(Array)}"
|
9
|
+
puts "Array === []: #{[].is_a?(Array)}"
|
10
|
+
|
11
|
+
# The issue is that case uses ===, and Class === Class is false
|
12
|
+
# but Class === instance is true
|
13
|
+
|
14
|
+
puts "\n=== Testing workaround ==="
|
15
|
+
case Hash
|
16
|
+
when ->(t) { t == Hash }
|
17
|
+
puts 'Lambda workaround works for Hash'
|
18
|
+
else
|
19
|
+
puts 'Lambda workaround failed for Hash'
|
20
|
+
end
|