shield-axum 0.3.1

Axum integration for Shield.
Documentation
use axum::Json;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use shield::{ConfigurationError, EmailAddress, ShieldError, User};

#[cfg(feature = "utoipa")]
use crate::error::ErrorBody;
use crate::{RouteError, extract::UserRequired};

#[derive(Deserialize, Serialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "utoipa", schema(as = User))]
#[serde(rename_all = "camelCase")]
pub struct UserBody {
    id: String,
    name: Option<String>,
    email_addresses: Vec<EmailAddress>,
    additional: Value,
}

impl UserBody {
    async fn new<U: User>(user: U) -> Result<Self, ShieldError> {
        let email_addresses = user.email_addresses().await?;

        Ok(Self {
            id: user.id(),
            name: user.name(),
            email_addresses,
            additional: serde_json::to_value(user.additional()).map_err(|err| {
                ConfigurationError::Invalid(format!(
                    "additional user data is not serializable: {err}"
                ))
            })?,
        })
    }
}

#[cfg_attr(
    feature = "utoipa",
    utoipa::path(
        get,
        path = "/user",
        operation_id = "getCurrentUser",
        summary = "Get current user",
        description = "Get the current user account.",
        tags = ["auth"],
        responses(
            (status = OK, description = "The current user account.", body = UserBody),
            (status = UNAUTHORIZED, description = "No account signed in.", body = ErrorBody),
            (status = INTERNAL_SERVER_ERROR, description = "Internal server error.", body = ErrorBody),
        )
    )
)]
pub async fn user<U: User>(
    UserRequired(user): UserRequired<U>,
) -> Result<Json<UserBody>, RouteError> {
    Ok(Json(UserBody::new(user).await?))
}