use std::sync::Arc;

use thiserror::Error;

/// Any error encountered while building, validating, or executing a query
/// struct.
#[derive(Clone, Debug, Error)]
pub enum QueryError {
    #[error("sqlx error: {0}")]
    Database(Arc<sqlx::Error>),

    #[error("query validation failed: {0}")]
    Validation(validator::ValidationErrors),

    #[error("uninitialized field: {0}")]
    Incomplete(derive_builder::UninitializedFieldError),
}

impl From<sqlx::Error> for QueryError {
    fn from(value: sqlx::Error) -> Self {
        Self::Database(Arc::new(value))
    }
}

impl From<validator::ValidationErrors> for QueryError {
    fn from(value: validator::ValidationErrors) -> Self {
        Self::Validation(value)
    }
}

impl From<derive_builder::UninitializedFieldError> for QueryError {
    fn from(value: derive_builder::UninitializedFieldError) -> Self {
        Self::Incomplete(value)
    }
}

pub type QueryResult<T> = Result<T, QueryError>;

/// Encompasses all possible (non-fatal) errors encountered while executing an
/// [`Accessor`]'s `fetch_one()` or `fetch_optional()` methods.
#[derive(Clone, Debug, thiserror::Error)]
pub enum AccessError {
    #[error("not found or access denied")]
    NotFound,

    #[error(transparent)]
    Query(QueryError),
}

impl<T: Into<QueryError>> From<T> for AccessError {
    fn from(value: T) -> Self {
        Self::Query(value.into())
    }
}
