Skip to main content

hypercall_db/traits/
usernames.rs

1//! Username persistence traits (async).
2//!
3//! Used by `UsernameService` for username CRUD.
4//! Redis caching and validation stay in the service layer.
5
6use anyhow::Result;
7use async_trait::async_trait;
8use chrono::{DateTime, Utc};
9
10use crate::UsernameRecord;
11
12/// Read-only username queries.
13#[async_trait]
14pub trait UsernameReader: Send + Sync {
15    /// Look up the username for a wallet (case-insensitive wallet match).
16    async fn get_username_by_wallet(&self, wallet: &str) -> Result<Option<UsernameRecord>>;
17
18    /// Reverse lookup: find the wallet that owns a username (case-insensitive).
19    async fn get_username_by_name(&self, username: &str) -> Result<Option<UsernameRecord>>;
20}
21
22/// Errors from username write operations that callers can pattern-match on.
23#[derive(Debug)]
24pub enum UsernameWriteError {
25    /// The requested username is already taken by another wallet.
26    UniqueViolation,
27    /// Any other internal / database error.
28    Internal(anyhow::Error),
29}
30
31impl std::fmt::Display for UsernameWriteError {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            Self::UniqueViolation => write!(f, "username already taken"),
35            Self::Internal(e) => write!(f, "{e}"),
36        }
37    }
38}
39
40impl std::error::Error for UsernameWriteError {
41    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
42        match self {
43            Self::UniqueViolation => None,
44            Self::Internal(e) => Some(e.as_ref()),
45        }
46    }
47}
48
49impl From<anyhow::Error> for UsernameWriteError {
50    fn from(e: anyhow::Error) -> Self {
51        Self::Internal(e)
52    }
53}
54
55/// Write operations for usernames.
56#[async_trait]
57pub trait UsernameWriter: UsernameReader {
58    /// Set (insert or update) the username for a wallet.
59    /// Returns the final row and the previous username (if any) on success.
60    /// Returns `UsernameWriteError::UniqueViolation` when the username is taken.
61    async fn set_username(
62        &self,
63        wallet: &str,
64        username: &str,
65        now: DateTime<Utc>,
66    ) -> std::result::Result<(UsernameRecord, Option<String>), UsernameWriteError>;
67
68    /// Delete the username for a wallet.
69    /// Returns the deleted row if one existed.
70    async fn delete_username(&self, wallet: &str) -> Result<Option<UsernameRecord>>;
71}