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
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}