Skip to main content

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