funapi 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/.claude/25-09-01-OPENAPI_IMPLEMENTATION.md +233 -0
- data/.claude/25-09-05-RESPONSE_SCHEMA.md +383 -0
- data/.claude/25-09-10-OPENAPI_PLAN.md +219 -0
- data/.claude/25-10-26-MIDDLEWARE_IMPLEMENTATION.md +230 -0
- data/.claude/25-10-26-MIDDLEWARE_PLAN.md +353 -0
- data/.claude/25-10-27-BACKGROUND_TASKS_ANALYSIS.md +325 -0
- data/.claude/25-10-27-DEPENDENCY_IMPLEMENTATION_SUMMARY.md +325 -0
- data/.claude/25-10-27-DEPENDENCY_INJECTION_PLAN.md +753 -0
- data/.claude/25-12-24-LIFECYCLE_HOOKS_PLAN.md +421 -0
- data/.claude/25-12-24-PUBLISHING_AND_DOGFOODING_PLAN.md +327 -0
- data/.claude/25-12-24-TEMPLATE_RENDERING_PLAN.md +704 -0
- data/.claude/DECISIONS.md +397 -0
- data/.claude/PROJECT_PLAN.md +80 -0
- data/.claude/TESTING_PLAN.md +285 -0
- data/.claude/TESTING_STATUS.md +157 -0
- data/.tool-versions +1 -0
- data/AGENTS.md +416 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +660 -0
- data/Rakefile +10 -0
- data/docs +8 -0
- data/docs-site/.gitignore +3 -0
- data/docs-site/Gemfile +9 -0
- data/docs-site/app.rb +138 -0
- data/docs-site/content/essential/handler.md +156 -0
- data/docs-site/content/essential/lifecycle.md +161 -0
- data/docs-site/content/essential/middleware.md +201 -0
- data/docs-site/content/essential/openapi.md +155 -0
- data/docs-site/content/essential/routing.md +123 -0
- data/docs-site/content/essential/validation.md +166 -0
- data/docs-site/content/getting-started/at-glance.md +82 -0
- data/docs-site/content/getting-started/key-concepts.md +150 -0
- data/docs-site/content/getting-started/quick-start.md +127 -0
- data/docs-site/content/index.md +81 -0
- data/docs-site/content/patterns/async-operations.md +137 -0
- data/docs-site/content/patterns/background-tasks.md +143 -0
- data/docs-site/content/patterns/database.md +175 -0
- data/docs-site/content/patterns/dependencies.md +141 -0
- data/docs-site/content/patterns/deployment.md +212 -0
- data/docs-site/content/patterns/error-handling.md +184 -0
- data/docs-site/content/patterns/response-schema.md +159 -0
- data/docs-site/content/patterns/templates.md +193 -0
- data/docs-site/content/patterns/testing.md +218 -0
- data/docs-site/mise.toml +2 -0
- data/docs-site/public/css/style.css +234 -0
- data/docs-site/templates/layouts/docs.html.erb +28 -0
- data/docs-site/templates/page.html.erb +3 -0
- data/docs-site/templates/partials/_nav.html.erb +19 -0
- data/examples/background_tasks_demo.rb +159 -0
- data/examples/demo_middleware.rb +55 -0
- data/examples/demo_openapi.rb +63 -0
- data/examples/dependency_block_demo.rb +150 -0
- data/examples/dependency_cleanup_demo.rb +146 -0
- data/examples/dependency_injection_demo.rb +200 -0
- data/examples/lifecycle_demo.rb +57 -0
- data/examples/middleware_demo.rb +74 -0
- data/examples/templates/layouts/application.html.erb +66 -0
- data/examples/templates/todos/_todo.html.erb +15 -0
- data/examples/templates/todos/index.html.erb +12 -0
- data/examples/templates_demo.rb +87 -0
- data/lib/funapi/application.rb +521 -0
- data/lib/funapi/async.rb +57 -0
- data/lib/funapi/background_tasks.rb +52 -0
- data/lib/funapi/config.rb +23 -0
- data/lib/funapi/database/sequel/fibered_connection_pool.rb +87 -0
- data/lib/funapi/dependency_wrapper.rb +66 -0
- data/lib/funapi/depends.rb +138 -0
- data/lib/funapi/exceptions.rb +72 -0
- data/lib/funapi/middleware/base.rb +13 -0
- data/lib/funapi/middleware/cors.rb +23 -0
- data/lib/funapi/middleware/request_logger.rb +32 -0
- data/lib/funapi/middleware/trusted_host.rb +34 -0
- data/lib/funapi/middleware.rb +4 -0
- data/lib/funapi/openapi/schema_converter.rb +85 -0
- data/lib/funapi/openapi/spec_generator.rb +179 -0
- data/lib/funapi/router.rb +43 -0
- data/lib/funapi/schema.rb +65 -0
- data/lib/funapi/server/falcon.rb +38 -0
- data/lib/funapi/template_response.rb +17 -0
- data/lib/funapi/templates.rb +111 -0
- data/lib/funapi/version.rb +5 -0
- data/lib/funapi.rb +14 -0
- data/sig/fun_api.rbs +499 -0
- metadata +220 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Dependency Injection Implementation Summary
|
|
2
|
+
|
|
3
|
+
## Date: 2024-10-27
|
|
4
|
+
|
|
5
|
+
## What We Built
|
|
6
|
+
|
|
7
|
+
A complete FastAPI-inspired dependency injection system for FunApi with a **Ruby-idiomatic block-based cleanup pattern**.
|
|
8
|
+
|
|
9
|
+
## Key Features Implemented
|
|
10
|
+
|
|
11
|
+
### 1. Dependency Registration with Cleanup
|
|
12
|
+
|
|
13
|
+
**Three supported patterns:**
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
# 1. Simple (no cleanup)
|
|
17
|
+
api.register(:config) { Config.load }
|
|
18
|
+
|
|
19
|
+
# 2. Tuple pattern (backward compatible)
|
|
20
|
+
api.register(:cache) do
|
|
21
|
+
[Cache.new, -> { Cache.shutdown }]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 3. Block with ensure (RECOMMENDED - most Ruby-like)
|
|
25
|
+
api.register(:db) do |provide|
|
|
26
|
+
conn = Database.connect
|
|
27
|
+
provide.call(conn)
|
|
28
|
+
ensure
|
|
29
|
+
conn.close
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Request-Scoped Caching
|
|
34
|
+
|
|
35
|
+
Dependencies resolved once per request, even if referenced multiple times:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
api.get '/dashboard',
|
|
39
|
+
depends: { db1: :db, db2: :db } do |input, req, task, db1:, db2:|
|
|
40
|
+
# db1 and db2 are the SAME instance
|
|
41
|
+
# Connection opened once, closed once
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. Automatic Cleanup
|
|
46
|
+
|
|
47
|
+
Cleanup runs in `ensure` block:
|
|
48
|
+
- ✅ Always runs after response sent
|
|
49
|
+
- ✅ Runs even if handler raises error
|
|
50
|
+
- ✅ Runs even if earlier cleanup fails
|
|
51
|
+
- ✅ Failures logged but don't break response
|
|
52
|
+
|
|
53
|
+
### 4. Nested Dependencies
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
api.register(:db) { |provide| ... }
|
|
57
|
+
|
|
58
|
+
def get_current_user
|
|
59
|
+
->(req:, db:) { authenticate(req, db) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
api.get '/profile',
|
|
63
|
+
depends: { user: FunApi::Depends(get_current_user, db: :db) }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 5. Multiple Dependency Styles
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
# Array syntax
|
|
70
|
+
api.get '/test', depends: [:db, :cache]
|
|
71
|
+
|
|
72
|
+
# Hash with symbols
|
|
73
|
+
api.get '/test', depends: { db: nil, cache: nil }
|
|
74
|
+
|
|
75
|
+
# Hash with inline lambdas
|
|
76
|
+
api.get '/test', depends: { value: -> { 42 } }
|
|
77
|
+
|
|
78
|
+
# Hash with callable classes
|
|
79
|
+
api.get '/test', depends: { page: Paginator.new(max: 50) }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Architecture
|
|
83
|
+
|
|
84
|
+
### Component Structure
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
lib/fun_api/
|
|
88
|
+
├── depends.rb # Depends wrapper for nested deps
|
|
89
|
+
├── dependency_wrapper.rb # Three wrapper types
|
|
90
|
+
│ ├── SimpleDependency # No cleanup
|
|
91
|
+
│ ├── ManagedDependency # Tuple pattern
|
|
92
|
+
│ └── BlockDependency # Block with ensure (Fiber-based)
|
|
93
|
+
└── application.rb # Container & resolution logic
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Fiber-Based Lifecycle
|
|
97
|
+
|
|
98
|
+
`BlockDependency` uses Ruby Fiber for resource lifecycle:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
class BlockDependency
|
|
102
|
+
def call
|
|
103
|
+
@fiber = Fiber.new do
|
|
104
|
+
@block.call(proc { |resource|
|
|
105
|
+
Fiber.yield resource # Provide to handler
|
|
106
|
+
})
|
|
107
|
+
end
|
|
108
|
+
@resource = @fiber.resume # Get resource
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def cleanup
|
|
112
|
+
@fiber.resume if @fiber.alive? # Trigger ensure block
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Resolution Flow
|
|
118
|
+
|
|
119
|
+
1. Route handler matched
|
|
120
|
+
2. Dependencies normalized (detect pattern type)
|
|
121
|
+
3. Dependencies resolved (with caching)
|
|
122
|
+
4. Handler called with injected kwargs
|
|
123
|
+
5. Response prepared
|
|
124
|
+
6. **Cleanup runs in ensure block**
|
|
125
|
+
|
|
126
|
+
## Test Coverage
|
|
127
|
+
|
|
128
|
+
**121 tests, 281 assertions, all passing**
|
|
129
|
+
|
|
130
|
+
Test files:
|
|
131
|
+
- `test/test_depends.rb` - Depends class unit tests (12 tests)
|
|
132
|
+
- `test/test_dependency_injection.rb` - Integration tests (12 tests)
|
|
133
|
+
- `test/test_dependency_cleanup.rb` - Cleanup behavior (7 tests)
|
|
134
|
+
- All existing tests still pass (90 tests)
|
|
135
|
+
|
|
136
|
+
## FastAPI Alignment
|
|
137
|
+
|
|
138
|
+
Our pattern matches FastAPI's dependencies with yield:
|
|
139
|
+
|
|
140
|
+
**Python:**
|
|
141
|
+
```python
|
|
142
|
+
def get_db():
|
|
143
|
+
db = SessionLocal()
|
|
144
|
+
try:
|
|
145
|
+
yield db
|
|
146
|
+
finally:
|
|
147
|
+
db.close()
|
|
148
|
+
|
|
149
|
+
@app.get("/users")
|
|
150
|
+
def read_users(db: Session = Depends(get_db)):
|
|
151
|
+
return db.query(User).all()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Ruby:**
|
|
155
|
+
```ruby
|
|
156
|
+
api.register(:db) do |provide|
|
|
157
|
+
db = Database.connect
|
|
158
|
+
provide.call(db)
|
|
159
|
+
ensure
|
|
160
|
+
db.close
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
api.get '/users', depends: [:db] do |input, req, task, db:|
|
|
164
|
+
[db.all_users, 200]
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Design Decisions
|
|
169
|
+
|
|
170
|
+
### Why Block-Based Pattern?
|
|
171
|
+
|
|
172
|
+
1. **Most idiomatic Ruby** - Matches patterns like:
|
|
173
|
+
- `File.open { |f| ... }`
|
|
174
|
+
- `Dir.chdir { ... }`
|
|
175
|
+
- `Mutex.synchronize { ... }`
|
|
176
|
+
|
|
177
|
+
2. **Safety** - `ensure` guarantees cleanup runs
|
|
178
|
+
|
|
179
|
+
3. **Can't forget** - Cleanup is built into the pattern
|
|
180
|
+
|
|
181
|
+
4. **Explicit lifecycle** - Clear setup → use → cleanup flow
|
|
182
|
+
|
|
183
|
+
5. **FastAPI parity** - Same concept as context managers
|
|
184
|
+
|
|
185
|
+
### Why Not Dry-System/Dry-Effects?
|
|
186
|
+
|
|
187
|
+
- Too heavy for FunApi's minimal philosophy
|
|
188
|
+
- Constructor injection vs parameter injection mismatch
|
|
189
|
+
- Adds significant complexity
|
|
190
|
+
- Our custom solution is simpler and more aligned
|
|
191
|
+
|
|
192
|
+
### Tuple Pattern Still Supported
|
|
193
|
+
|
|
194
|
+
For backward compatibility and simple cases:
|
|
195
|
+
```ruby
|
|
196
|
+
api.register(:cache) { [Cache.new, -> { Cache.shutdown }] }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Examples
|
|
200
|
+
|
|
201
|
+
Created three comprehensive examples:
|
|
202
|
+
1. `examples/dependency_injection_demo.rb` - Full auth example with nested deps
|
|
203
|
+
2. `examples/dependency_cleanup_demo.rb` - Shows cleanup lifecycle (tuple pattern)
|
|
204
|
+
3. `examples/dependency_block_demo.rb` - Shows new block pattern (RECOMMENDED)
|
|
205
|
+
|
|
206
|
+
## Usage Patterns
|
|
207
|
+
|
|
208
|
+
### Database Connection
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
api.register(:db) do |provide|
|
|
212
|
+
conn = Database.connect
|
|
213
|
+
puts "✅ Connection opened"
|
|
214
|
+
provide.call(conn)
|
|
215
|
+
ensure
|
|
216
|
+
conn&.close
|
|
217
|
+
puts "❌ Connection closed"
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### File Handles
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
api.register(:log_file) do |provide|
|
|
225
|
+
file = File.open('app.log', 'a')
|
|
226
|
+
provide.call(file)
|
|
227
|
+
ensure
|
|
228
|
+
file&.close
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Transactions
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
api.register(:transaction) do |provide|
|
|
236
|
+
tx = db.begin_transaction
|
|
237
|
+
begin
|
|
238
|
+
provide.call(tx)
|
|
239
|
+
tx.commit
|
|
240
|
+
rescue
|
|
241
|
+
tx.rollback
|
|
242
|
+
raise
|
|
243
|
+
ensure
|
|
244
|
+
tx.close
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Authentication (Nested)
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
api.register(:db) { |provide| ... }
|
|
253
|
+
|
|
254
|
+
def require_auth
|
|
255
|
+
->(req:, db:) {
|
|
256
|
+
token = req.env['HTTP_AUTHORIZATION']
|
|
257
|
+
user = db.find_user_by_token(token)
|
|
258
|
+
raise HTTPException.new(status_code: 401) unless user
|
|
259
|
+
user
|
|
260
|
+
}
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
api.get '/profile',
|
|
264
|
+
depends: { user: FunApi::Depends(require_auth, db: :db) }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Performance Considerations
|
|
268
|
+
|
|
269
|
+
- **Request-scoped caching** - Dependencies resolved once per request
|
|
270
|
+
- **No overhead without deps** - Routes without dependencies have no DI overhead
|
|
271
|
+
- **Fiber cost** - Minimal (Ruby Fibers are lightweight)
|
|
272
|
+
- **Cleanup always runs** - Even on errors, no resource leaks
|
|
273
|
+
|
|
274
|
+
## Future Enhancements
|
|
275
|
+
|
|
276
|
+
Could add later (not needed now):
|
|
277
|
+
- Global dependencies (apply to all routes)
|
|
278
|
+
- Dependency overrides for testing
|
|
279
|
+
- Async context managers (for async resources)
|
|
280
|
+
- Dependency graph visualization
|
|
281
|
+
|
|
282
|
+
## Migration Guide
|
|
283
|
+
|
|
284
|
+
For users upgrading from tuple pattern:
|
|
285
|
+
|
|
286
|
+
**Before:**
|
|
287
|
+
```ruby
|
|
288
|
+
api.register(:db) do
|
|
289
|
+
conn = Database.connect
|
|
290
|
+
[conn, -> { conn.close }]
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**After (recommended):**
|
|
295
|
+
```ruby
|
|
296
|
+
api.register(:db) do |provide|
|
|
297
|
+
conn = Database.connect
|
|
298
|
+
provide.call(conn)
|
|
299
|
+
ensure
|
|
300
|
+
conn.close
|
|
301
|
+
end
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Both patterns work! The block pattern is recommended for better Ruby idioms.
|
|
305
|
+
|
|
306
|
+
## Success Metrics
|
|
307
|
+
|
|
308
|
+
✅ All 121 tests passing
|
|
309
|
+
✅ Zero regressions
|
|
310
|
+
✅ FastAPI parity achieved
|
|
311
|
+
✅ Ruby-idiomatic API
|
|
312
|
+
✅ Production-ready
|
|
313
|
+
|
|
314
|
+
## References
|
|
315
|
+
|
|
316
|
+
- FastAPI Dependencies: https://fastapi.tiangolo.com/tutorial/dependencies/
|
|
317
|
+
- FastAPI with yield: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/
|
|
318
|
+
- Ruby Fiber documentation
|
|
319
|
+
- dry-rb ecosystem review
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
**Status:** ✅ Complete and production-ready
|
|
324
|
+
**Date:** October 27, 2024
|
|
325
|
+
**Tests:** 121 passing, 281 assertions
|