1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! End-to-end owned injection tests.
//!
//! Verifies the complete flow:
//! add_dbcontext (scoped) → #[inject] auto-registers + #[inject(owned)] on
//! bare DbContext field → get_owned::<Handler>() → handle(&mut self) →
//! self.ctx.set::<T>()
//!
//! This proves the framework is ready for the EFCore-aligned handler pattern
//! without Arc<Mutex> or interior mutability.
use rust_dix::*;
use rust_ef::db_context::DbContext;
use rust_ef::di::DbContextServiceCollectionExt as _;
use rust_ef::prelude::*;
use rust_ef_sqlite::DbContextOptionsBuilderExt as _;
use std::sync::Arc;
// ── Test entity ──
#[derive(Debug, Clone, EntityType)]
#[table("widgets")]
struct Widget {
#[primary_key]
#[auto_increment]
id: i32,
#[required]
name: String,
}
// ── Handler with owned DbContext (bare T field) ──
//
// #[inject] on the struct does two things:
// 1. Generates a constructor that resolves fields via DI
// 2. Registers the type via inventory for ServiceCollection::from_injected()
//
// The bare `ctx: DbContext` field MUST be marked `#[inject(owned)]` so the
// constructor resolves it via get_owned(), giving the handler exclusive
// &mut self access. Unmarked fields fall back to Default::default().
#[inject(scoped)]
struct CreateWidgetHandler {
#[inject(owned)]
ctx: DbContext, // bare T + #[inject(owned)] → get_owned()
}
impl CreateWidgetHandler {
async fn handle(&mut self, name: &str) -> EFResult<()> {
// In-memory SQLite: each owned DbContext gets a fresh database,
// so ensure_created() must be called before DML operations.
self.ctx.set::<Widget>();
self.ctx.ensure_created().await?;
let widget = Widget {
id: 0,
name: name.to_string(),
};
self.ctx.set::<Widget>().add(widget);
self.ctx.save_changes().await?;
Ok(())
}
}
// ── Handler with Arc<DbContext> (shared resolution, &self only) ──
#[inject(scoped)]
struct SharedContextHandler {
#[inject]
ctx: Arc<DbContext>, // Arc<T> + #[inject] → shared resolution via get()
}
impl SharedContextHandler {
// Arc<DbContext> only provides &self access — can call &self methods
// like ensure_created(), but NOT set::<T>() or save_changes() (&mut self).
async fn ensure_schema(&self) -> EFResult<()> {
self.ctx.ensure_created().await
}
}
fn build_provider() -> Arc<ServiceProvider> {
// rust-dix 0.6+: `build()` returns `Arc<ServiceProvider>` directly.
ServiceCollection::from_injected()
.add_dbcontext(|o| {
o.use_sqlite_in_memory();
})
.build()
.expect("build provider")
}
#[tokio::test]
async fn owned_handler_can_mutate_dbcontext() {
// End-to-end: #[inject(scoped)] + bare DbContext field + &mut self methods.
// Each get_owned() returns a fresh DbContext (fresh in-memory SQLite),
// so the handler calls ensure_created() itself before DML.
let provider = build_provider();
// Resolve handler via get_owned — #[inject(owned)] on the bare T field
// directs the constructor to call get_owned() for the DbContext field.
let mut handler: CreateWidgetHandler = provider.get_owned().expect("get_owned handler");
let result = handler.handle("test-widget").await;
assert!(
result.is_ok(),
"owned handler must be able to mutate DbContext"
);
}
#[tokio::test]
async fn owned_handler_gets_fresh_instance_each_call() {
// Each get_owned::<Handler>() creates a fresh Handler with a fresh DbContext.
let provider = build_provider();
let h1: CreateWidgetHandler = provider.get_owned().expect("get_owned h1");
let h2: CreateWidgetHandler = provider.get_owned().expect("get_owned h2");
let addr1 = &h1.ctx as *const _ as usize;
let addr2 = &h2.ctx as *const _ as usize;
assert_ne!(
addr1, addr2,
"each get_owned::<Handler>() must have a distinct DbContext"
);
}
#[tokio::test]
async fn shared_handler_uses_arc_resolution() {
// Arc<DbContext> field → #[inject] uses get() (shared Arc).
// Arc<DbContext> only supports &self methods (e.g. ensure_created).
let provider = build_provider();
// Need to register the DbSet first via an owned context
{
let mut ctx: DbContext = provider.get_owned().expect("get_owned ctx");
ctx.set::<Widget>();
}
let handler: SharedContextHandler = provider.get_owned().expect("get_owned handler");
let result = handler.ensure_schema().await;
assert!(
result.is_ok(),
"shared handler must work with &self methods"
);
}
#[tokio::test]
async fn keyed_owned_resolution_works() {
// Verify keyed owned resolution: get_keyed_owned::<DbContext>("key").
let provider: Arc<ServiceProvider> = ServiceCollection::new()
.add_dbcontext_keyed("primary", |o| {
o.use_sqlite_in_memory();
})
.build()
.unwrap();
let mut ctx1: DbContext = provider
.get_keyed_owned("primary")
.expect("get_keyed_owned ctx1");
let mut ctx2: DbContext = provider
.get_keyed_owned("primary")
.expect("get_keyed_owned ctx2");
let addr1 = &ctx1 as *const _ as usize;
let addr2 = &ctx2 as *const _ as usize;
assert_ne!(addr1, addr2, "keyed owned must return fresh instances");
// Verify the keyed context is usable
ctx1.set::<Widget>();
ctx1.ensure_created().await.unwrap();
ctx2.set::<Widget>();
ctx2.ensure_created().await.unwrap();
}