1use modkit_security::SecurityContext;
24
25#[derive(Debug)]
35pub struct Secured<'a, C> {
36 client: &'a C,
37 ctx: &'a SecurityContext,
38}
39
40impl<'a, C> Secured<'a, C> {
41 #[must_use]
54 pub fn new(client: &'a C, ctx: &'a SecurityContext) -> Self {
55 Self { client, ctx }
56 }
57
58 #[must_use]
66 pub fn client(&self) -> &'a C {
67 self.client
68 }
69
70 #[must_use]
79 pub fn ctx(&self) -> &'a SecurityContext {
80 self.ctx
81 }
82
83 #[must_use]
100 pub fn query<S: crate::odata::Schema>(&self) -> crate::odata::QueryBuilder<S> {
101 crate::odata::QueryBuilder::new()
102 }
103}
104
105impl<C> Clone for Secured<'_, C> {
106 fn clone(&self) -> Self {
107 *self
108 }
109}
110
111impl<C> Copy for Secured<'_, C> {}
112
113pub trait WithSecurityContext {
126 fn security_ctx<'a>(&'a self, ctx: &'a SecurityContext) -> Secured<'a, Self>
143 where
144 Self: Sized;
145}
146
147impl<T> WithSecurityContext for T {
148 fn security_ctx<'a>(&'a self, ctx: &'a SecurityContext) -> Secured<'a, Self>
149 where
150 Self: Sized,
151 {
152 Secured::new(self, ctx)
153 }
154}
155
156#[cfg(test)]
157#[cfg_attr(coverage_nightly, coverage(off))]
158mod tests {
159 use super::*;
160 use uuid::{Uuid, uuid};
161
162 const TEST_TENANT_ID: Uuid = uuid!("00000000-0000-0000-0000-000000000001");
164
165 struct MockClient {
166 name: String,
167 }
168
169 impl MockClient {
170 fn new(name: &str) -> Self {
171 Self {
172 name: name.to_owned(),
173 }
174 }
175
176 fn get_name(&self) -> &str {
177 &self.name
178 }
179 }
180
181 #[test]
182 fn test_secured_new() {
183 let client = MockClient::new("test-client");
184 let ctx = SecurityContext::builder().tenant_id(TEST_TENANT_ID).build();
185
186 let secured = Secured::new(&client, &ctx);
187
188 assert_eq!(secured.client().get_name(), "test-client");
189 assert_eq!(secured.ctx().tenant_id(), ctx.tenant_id());
190 }
191
192 #[test]
193 fn test_secured_getters() {
194 let client = MockClient::new("test-client");
195 let tenant_id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
196 let ctx = SecurityContext::builder().tenant_id(tenant_id).build();
197
198 let secured = Secured::new(&client, &ctx);
199
200 let client_ref = secured.client();
201 assert_eq!(client_ref.get_name(), "test-client");
202
203 let ctx_ref = secured.ctx();
204 assert_eq!(ctx_ref.tenant_id(), tenant_id);
205 }
206
207 #[test]
208 fn test_with_security_context_trait() {
209 let client = MockClient::new("test-client");
210 let ctx = SecurityContext::builder().tenant_id(TEST_TENANT_ID).build();
211
212 let secured = client.security_ctx(&ctx);
213
214 assert_eq!(secured.client().get_name(), "test-client");
215 assert_eq!(secured.ctx().tenant_id(), ctx.tenant_id());
216 }
217
218 #[test]
219 fn test_secured_clone() {
220 let client = MockClient::new("test-client");
221 let ctx = SecurityContext::builder().tenant_id(TEST_TENANT_ID).build();
222
223 let secured1 = client.security_ctx(&ctx);
224 let secured2 = secured1;
225
226 assert_eq!(secured1.client().get_name(), secured2.client().get_name());
227 assert_eq!(secured1.ctx().tenant_id(), secured2.ctx().tenant_id());
228 }
229
230 #[test]
231 fn test_secured_copy() {
232 let client = MockClient::new("test-client");
233 let ctx = SecurityContext::builder().tenant_id(TEST_TENANT_ID).build();
234
235 let secured1 = client.security_ctx(&ctx);
236 let secured2 = secured1;
237
238 assert_eq!(secured1.client().get_name(), secured2.client().get_name());
239 assert_eq!(secured1.ctx().tenant_id(), secured2.ctx().tenant_id());
240 }
241
242 #[test]
243 fn test_secured_with_anonymous_context() {
244 let client = MockClient::new("test-client");
245 let ctx = SecurityContext::anonymous();
246
247 let secured = client.security_ctx(&ctx);
248
249 assert_eq!(secured.client().get_name(), "test-client");
250 assert_eq!(secured.ctx().tenant_id(), Uuid::default());
251 assert_eq!(secured.ctx().subject_id(), Uuid::default());
252 }
253
254 #[test]
255 fn test_secured_with_custom_context() {
256 let client = MockClient::new("test-client");
257 let tenant_id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
258 let subject_id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440001").unwrap();
259
260 let ctx = SecurityContext::builder()
261 .tenant_id(tenant_id)
262 .subject_id(subject_id)
263 .subject_type("user")
264 .build();
265
266 let secured = client.security_ctx(&ctx);
267
268 assert_eq!(secured.ctx().tenant_id(), tenant_id);
269 assert_eq!(secured.ctx().subject_id(), subject_id);
270 }
271
272 #[test]
273 fn test_secured_zero_allocation() {
274 let client = MockClient::new("test-client");
275 let ctx = SecurityContext::builder().tenant_id(TEST_TENANT_ID).build();
276
277 let secured = client.security_ctx(&ctx);
278
279 assert_eq!(
280 std::mem::size_of_val(&secured),
281 std::mem::size_of::<&MockClient>() + std::mem::size_of::<&SecurityContext>()
282 );
283 }
284
285 #[test]
286 fn test_multiple_clients_with_same_context() {
287 let client1 = MockClient::new("client-1");
288 let client2 = MockClient::new("client-2");
289 let ctx = SecurityContext::builder().tenant_id(TEST_TENANT_ID).build();
290
291 let secured1 = client1.security_ctx(&ctx);
292 let secured2 = client2.security_ctx(&ctx);
293
294 assert_eq!(secured1.client().get_name(), "client-1");
295 assert_eq!(secured2.client().get_name(), "client-2");
296 assert_eq!(secured1.ctx().tenant_id(), secured2.ctx().tenant_id());
297 }
298
299 #[test]
300 fn test_secured_preserves_lifetimes() {
301 let client = MockClient::new("test-client");
302 let ctx = SecurityContext::builder().tenant_id(TEST_TENANT_ID).build();
303
304 let secured = client.security_ctx(&ctx);
305
306 assert_eq!(secured.client().get_name(), "test-client");
307 assert_eq!(secured.ctx().tenant_id(), ctx.tenant_id());
308 }
309
310 #[test]
311 fn test_secured_query_builder() {
312 use crate::odata::Schema;
313
314 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
315 enum TestField {
316 #[allow(dead_code)]
317 Name,
318 }
319
320 struct TestSchema;
321
322 impl Schema for TestSchema {
323 type Field = TestField;
324
325 fn field_name(field: Self::Field) -> &'static str {
326 match field {
327 TestField::Name => "name",
328 }
329 }
330 }
331
332 let client = MockClient::new("test-client");
333 let ctx = SecurityContext::builder().tenant_id(TEST_TENANT_ID).build();
334
335 let secured = client.security_ctx(&ctx);
336 let query_builder = secured.query::<TestSchema>();
337
338 let query = query_builder.page_size(50).build();
339 assert_eq!(query.limit, Some(50));
340 }
341}