Some checks failed
Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com> Reviewed-on: #16
135 lines
4.1 KiB
Rust
135 lines
4.1 KiB
Rust
use std::{ops::Deref, sync::Arc};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Cheap-to-clone handle to per-session user data.
|
|
#[derive(Debug, Clone)]
|
|
pub struct UserState(Arc<UserStateInner>);
|
|
|
|
impl Deref for UserState {
|
|
type Target = UserStateInner;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl From<UserStateInner> for UserState {
|
|
fn from(value: UserStateInner) -> Self {
|
|
Self(Arc::new(value))
|
|
}
|
|
}
|
|
|
|
/// Per-session user data stored in the tower-sessions session store.
|
|
///
|
|
/// Persisted across requests for the lifetime of the session.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct UserStateInner {
|
|
/// Subject identifier from Keycloak (unique user ID).
|
|
pub sub: String,
|
|
/// OAuth2 access token.
|
|
pub access_token: String,
|
|
/// OAuth2 refresh token.
|
|
pub refresh_token: String,
|
|
/// Basic user profile.
|
|
pub user: User,
|
|
}
|
|
|
|
/// Basic user profile stored alongside the session.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct User {
|
|
/// Email address.
|
|
pub email: String,
|
|
/// Display name (preferred_username or full name from Keycloak).
|
|
pub name: String,
|
|
/// Avatar / profile picture URL.
|
|
pub avatar_url: String,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn user_state_inner_default_has_empty_strings() {
|
|
let inner = UserStateInner::default();
|
|
assert_eq!(inner.sub, "");
|
|
assert_eq!(inner.access_token, "");
|
|
assert_eq!(inner.refresh_token, "");
|
|
assert_eq!(inner.user.email, "");
|
|
assert_eq!(inner.user.name, "");
|
|
assert_eq!(inner.user.avatar_url, "");
|
|
}
|
|
|
|
#[test]
|
|
fn user_default_has_empty_strings() {
|
|
let user = User::default();
|
|
assert_eq!(user.email, "");
|
|
assert_eq!(user.name, "");
|
|
assert_eq!(user.avatar_url, "");
|
|
}
|
|
|
|
#[test]
|
|
fn user_state_inner_serde_round_trip() {
|
|
let inner = UserStateInner {
|
|
sub: "user-123".into(),
|
|
access_token: "tok-abc".into(),
|
|
refresh_token: "ref-xyz".into(),
|
|
user: User {
|
|
email: "a@b.com".into(),
|
|
name: "Alice".into(),
|
|
avatar_url: "https://img.example.com/a.png".into(),
|
|
},
|
|
};
|
|
let json = serde_json::to_string(&inner).expect("serialize UserStateInner");
|
|
let back: UserStateInner = serde_json::from_str(&json).expect("deserialize UserStateInner");
|
|
assert_eq!(inner.sub, back.sub);
|
|
assert_eq!(inner.access_token, back.access_token);
|
|
assert_eq!(inner.refresh_token, back.refresh_token);
|
|
assert_eq!(inner.user.email, back.user.email);
|
|
assert_eq!(inner.user.name, back.user.name);
|
|
assert_eq!(inner.user.avatar_url, back.user.avatar_url);
|
|
}
|
|
|
|
#[test]
|
|
fn user_state_from_inner_and_deref() {
|
|
let inner = UserStateInner {
|
|
sub: "sub-1".into(),
|
|
access_token: "at".into(),
|
|
refresh_token: "rt".into(),
|
|
user: User {
|
|
email: "e@e.com".into(),
|
|
name: "Eve".into(),
|
|
avatar_url: "".into(),
|
|
},
|
|
};
|
|
let state = UserState::from(inner);
|
|
// Deref should give access to inner fields
|
|
assert_eq!(state.sub, "sub-1");
|
|
assert_eq!(state.user.name, "Eve");
|
|
}
|
|
|
|
#[test]
|
|
fn user_serde_round_trip() {
|
|
let user = User {
|
|
email: "bob@test.com".into(),
|
|
name: "Bob".into(),
|
|
avatar_url: "https://avatars.io/bob".into(),
|
|
};
|
|
let json = serde_json::to_string(&user).expect("serialize User");
|
|
let back: User = serde_json::from_str(&json).expect("deserialize User");
|
|
assert_eq!(user.email, back.email);
|
|
assert_eq!(user.name, back.name);
|
|
assert_eq!(user.avatar_url, back.avatar_url);
|
|
}
|
|
|
|
#[test]
|
|
fn user_state_clone_is_cheap() {
|
|
let inner = UserStateInner::default();
|
|
let state = UserState::from(inner);
|
|
let cloned = state.clone();
|
|
// Both point to the same Arc allocation
|
|
assert_eq!(state.sub, cloned.sub);
|
|
}
|
|
}
|