reinhardt_auth/repository.rs
1//! User repository abstraction
2//!
3//! Provides the [`UserRepository`] trait for retrieving user data from storage backends,
4//! shared across multiple authentication backends.
5
6// This module uses the deprecated User trait for backward compatibility.
7// UserRepository returns Box<dyn User> to preserve existing authentication APIs.
8#![allow(deprecated)]
9use crate::{SimpleUser, User};
10use async_trait::async_trait;
11use uuid::Uuid;
12
13/// User repository trait for authentication backends
14///
15/// Provides an abstraction for retrieving user data from various storage backends.
16///
17/// # Examples
18///
19/// ```
20/// use reinhardt_auth::{UserRepository, User};
21/// use async_trait::async_trait;
22///
23/// struct MyUserRepository;
24///
25/// #[async_trait]
26/// impl UserRepository for MyUserRepository {
27/// async fn get_user_by_id(&self, user_id: &str) -> Result<Option<Box<dyn User>>, String> {
28/// // Custom implementation
29/// Ok(None)
30/// }
31/// }
32/// ```
33#[async_trait]
34pub trait UserRepository: Send + Sync {
35 /// Get user by ID
36 ///
37 /// Returns `Ok(Some(user))` if found, `Ok(None)` if not found, or `Err` on error.
38 async fn get_user_by_id(&self, user_id: &str) -> Result<Option<Box<dyn User>>, String>;
39}
40
41/// Simple in-memory user repository
42///
43/// Creates [`SimpleUser`] instances on-the-fly without database access.
44/// Suitable for testing and development environments.
45///
46/// # Examples
47///
48/// ```
49/// use reinhardt_auth::{SimpleUserRepository, UserRepository};
50///
51/// #[tokio::main]
52/// async fn main() {
53/// let repo = SimpleUserRepository;
54/// let user = repo.get_user_by_id("user_123").await.unwrap();
55/// assert!(user.is_some());
56/// }
57/// ```
58pub struct SimpleUserRepository;
59
60#[async_trait]
61impl UserRepository for SimpleUserRepository {
62 async fn get_user_by_id(&self, user_id: &str) -> Result<Option<Box<dyn User>>, String> {
63 // Create a simple user object for development/testing purposes.
64 // NOTE: This implementation uses a deterministic UUID and an empty email because
65 // real user data is not available without a database connection.
66 // For production use, implement UserRepository with a real database backend.
67 Ok(Some(Box::new(SimpleUser {
68 id: Uuid::new_v5(&Uuid::NAMESPACE_URL, user_id.as_bytes()),
69 username: user_id.to_string(),
70 email: String::new(),
71 is_active: true,
72 is_admin: false,
73 is_staff: false,
74 is_superuser: false,
75 })))
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use rstest::rstest;
83
84 #[rstest]
85 #[tokio::test]
86 async fn test_simple_user_repo_returns_user() {
87 // Arrange
88 let repo = SimpleUserRepository;
89 let user_id = "test_user_42";
90
91 // Act
92 let result = repo.get_user_by_id(user_id).await;
93
94 // Assert
95 let user = result
96 .expect("get_user_by_id should not return Err")
97 .expect("get_user_by_id should return Some for any input");
98 assert_eq!(user.username(), user_id);
99 assert!(user.is_active());
100 assert!(user.is_authenticated());
101 }
102
103 #[rstest]
104 #[tokio::test]
105 async fn test_simple_user_repo_deterministic_uuid() {
106 // Arrange
107 let repo = SimpleUserRepository;
108 let user_id = "deterministic_id_input";
109 let expected_uuid = Uuid::new_v5(&Uuid::NAMESPACE_URL, user_id.as_bytes());
110
111 // Act
112 let first_result = repo.get_user_by_id(user_id).await;
113 let second_result = repo.get_user_by_id(user_id).await;
114
115 // Assert
116 let first_user = first_result.unwrap().unwrap();
117 let second_user = second_result.unwrap().unwrap();
118 assert_eq!(first_user.id(), expected_uuid.to_string());
119 assert_eq!(first_user.id(), second_user.id());
120 }
121}