systemprompt-api 0.64.0

Axum-based HTTP server and API gateway for systemprompt.io AI governance infrastructure. Exposes governed agents, MCP, A2A, and admin endpoints with rate limiting and RBAC.
Documentation
//! `From` impls mapping domain errors onto [`OAuthHttpError`], keeping the
//! variant-to-RFC-code mapping in one place so handlers use `?`.
//!
//! Copyright (c) systemprompt.io — Business Source License 1.1.
//! See <https://systemprompt.io> for licensing details.

use systemprompt_config::SecretsBootstrapError;
use systemprompt_identifiers::error::IdValidationError;
use systemprompt_models::errors::GlobalConfigError;
use systemprompt_oauth::{OauthError, OauthErrorKind};
use systemprompt_oauth_issuance::IssuanceError;
use systemprompt_traits::auth::AuthProviderError;

use super::OAuthHttpError;
use crate::routes::oauth::internal;

impl From<GlobalConfigError> for OAuthHttpError {
    fn from(err: GlobalConfigError) -> Self {
        Self::server_error("Configuration unavailable").with_source(err)
    }
}

impl From<IdValidationError> for OAuthHttpError {
    fn from(err: IdValidationError) -> Self {
        Self::invalid_request(err.to_string()).with_source(err)
    }
}

impl From<OauthError> for OAuthHttpError {
    fn from(err: OauthError) -> Self {
        match err.kind() {
            OauthErrorKind::InvalidClient => Self::invalid_client("Client authentication failed"),
            OauthErrorKind::InvalidClientMetadata => Self::invalid_client_metadata(err.to_string()),
            OauthErrorKind::InvalidGrant => Self::invalid_grant(err.to_string()),
            OauthErrorKind::InvalidToken => Self::invalid_token(err.to_string()),
            OauthErrorKind::InvalidRequest => Self::invalid_request(err.to_string()),
            OauthErrorKind::AccessDenied => Self::access_denied(err.to_string()),
            OauthErrorKind::UsernameUnavailable => Self::username_unavailable(
                "Username is already taken. Please choose a different username.",
            ),
            OauthErrorKind::EmailExists => {
                Self::email_exists("An account with this email already exists.")
            },
            OauthErrorKind::NotFound => Self::not_found(err.to_string()),
            OauthErrorKind::ExpiredChallenge => Self::expired_challenge(
                "The challenge has expired. Please start the ceremony again.",
            ),
            OauthErrorKind::InvalidCredential => Self::invalid_credential(
                "WebAuthn verification failed. Please ensure your authenticator and browser are \
                 compatible.",
            ),
            OauthErrorKind::AuthenticationFailed => Self::authentication_failed(
                "Authentication failed. Check the email address or register a passkey.",
            ),
            OauthErrorKind::ServerError => {
                Self::server_error("Authorization operation failed").with_source(err)
            },
        }
    }
}

impl From<AuthProviderError> for OAuthHttpError {
    fn from(err: AuthProviderError) -> Self {
        match err {
            e @ (AuthProviderError::InvalidCredentials | AuthProviderError::InvalidToken) => {
                Self::invalid_client(e.to_string())
            },
            e @ AuthProviderError::UserNotFound => Self::not_found(e.to_string()),
            e @ AuthProviderError::TokenExpired => Self::invalid_grant(e.to_string()),
            e @ AuthProviderError::InsufficientPermissions => Self::access_denied(e.to_string()),
            other => Self::server_error("Authentication provider failed").with_source(other),
        }
    }
}

impl From<SecretsBootstrapError> for OAuthHttpError {
    fn from(err: SecretsBootstrapError) -> Self {
        Self::server_error("Secrets unavailable").with_source(err)
    }
}

impl From<sqlx::Error> for OAuthHttpError {
    fn from(err: sqlx::Error) -> Self {
        Self::server_error("Database operation failed").with_source(err)
    }
}

impl From<anyhow::Error> for OAuthHttpError {
    fn from(err: anyhow::Error) -> Self {
        Self::server_error("Authorization operation failed").with_source(err)
    }
}

impl From<IssuanceError> for OAuthHttpError {
    fn from(error: IssuanceError) -> Self {
        match error {
            IssuanceError::InvalidRequest { field, message } => {
                Self::invalid_request(format!("{field}: {message}"))
            },
            IssuanceError::MalformedField {
                field,
                reason,
                source,
            } => internal::rejected(Self::invalid_request(format!("{field}: {reason}")), source),
            IssuanceError::UnsupportedGrantType { grant_type } => {
                Self::unsupported_grant_type(format!("Grant type '{grant_type}' is not supported"))
            },
            IssuanceError::InvalidClient => Self::invalid_client("Client authentication failed"),
            IssuanceError::InvalidGrant { reason } => Self::invalid_grant(reason),
            IssuanceError::RejectedGrant { reason, source } => {
                internal::rejected(Self::invalid_grant(reason), source)
            },
            IssuanceError::InvalidRefreshToken { reason } => {
                Self::invalid_grant(format!("Refresh token invalid: {reason}"))
            },
            IssuanceError::InvalidCredentials => Self::invalid_grant("Invalid credentials"),
            IssuanceError::InvalidClientSecret => Self::invalid_client("Invalid client secret"),
            IssuanceError::ExpiredCode => Self::invalid_grant("Authorization code expired"),
            IssuanceError::ServerError { context, source } => {
                internal::server_error(context, source)
            },
            IssuanceError::InvalidTarget { message } => Self::invalid_target(message),
            IssuanceError::InvalidScope { message } => Self::invalid_scope(message),
            IssuanceError::IdJagRejected(error) => Self::invalid_grant(error.to_string()),
            IssuanceError::BoundResource(error) => Self::invalid_target(error.to_string()),
            IssuanceError::Oauth(error) => Self::from(error),
        }
    }
}