use crate::catalog::ToolId;
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct ModelLoadError(#[from] ModelLoadRepr);
#[derive(Debug, thiserror::Error)]
enum ModelLoadRepr {
#[error("embedded model configuration")]
Config(#[source] BoxError),
#[error("embedded model dimension mismatch: hidden size {got}, expected {expected}")]
Dimensions {
got: usize,
expected: usize,
},
#[error("embedded model provenance: {0}")]
Provenance(String),
#[error("embedded tokenizer")]
Tokenizer(#[source] BoxError),
#[error("tokenizer truncation setup")]
Truncation(#[source] BoxError),
#[error("embedded model weights")]
Weights(#[source] BoxError),
#[error("embedded model architecture")]
Model(#[source] BoxError),
}
impl ModelLoadError {
pub(crate) fn config(source: impl Into<BoxError>) -> Self {
Self(ModelLoadRepr::Config(source.into()))
}
pub(crate) fn dimensions(got: usize, expected: usize) -> Self {
Self(ModelLoadRepr::Dimensions { got, expected })
}
pub(crate) fn provenance(detail: impl Into<String>) -> Self {
Self(ModelLoadRepr::Provenance(detail.into()))
}
pub(crate) fn tokenizer(source: impl Into<BoxError>) -> Self {
Self(ModelLoadRepr::Tokenizer(source.into()))
}
pub(crate) fn truncation(source: impl Into<BoxError>) -> Self {
Self(ModelLoadRepr::Truncation(source.into()))
}
pub(crate) fn weights(source: impl Into<BoxError>) -> Self {
Self(ModelLoadRepr::Weights(source.into()))
}
pub(crate) fn model(source: impl Into<BoxError>) -> Self {
Self(ModelLoadRepr::Model(source.into()))
}
}
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct QueryError(#[from] QueryRepr);
#[derive(Debug, thiserror::Error)]
enum QueryRepr {
#[error("need tokenization")]
Tokenization(#[source] BoxError),
#[error("need inference")]
Inference(#[source] BoxError),
#[error("invalid need embedding: {0}")]
InvalidEmbedding(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum QueryErrorKind {
Tokenization,
Inference,
InvalidEmbedding,
}
impl QueryError {
#[must_use]
pub fn kind(&self) -> QueryErrorKind {
match &self.0 {
QueryRepr::Tokenization(_) => QueryErrorKind::Tokenization,
QueryRepr::Inference(_) => QueryErrorKind::Inference,
QueryRepr::InvalidEmbedding(_) => QueryErrorKind::InvalidEmbedding,
}
}
pub(crate) fn tokenization(source: impl Into<BoxError>) -> Self {
Self(QueryRepr::Tokenization(source.into()))
}
pub(crate) fn inference(source: impl Into<BoxError>) -> Self {
Self(QueryRepr::Inference(source.into()))
}
pub(crate) fn invalid_embedding(detail: impl Into<String>) -> Self {
Self(QueryRepr::InvalidEmbedding(detail.into()))
}
}
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct IndexError(#[from] IndexRepr);
#[derive(Debug, thiserror::Error)]
enum IndexRepr {
#[error("catalog embedding")]
Embed(#[source] QueryError),
#[error("catalog vector layout: {0}")]
Layout(String),
}
impl IndexError {
pub(crate) fn embed(source: QueryError) -> Self {
Self(IndexRepr::Embed(source))
}
pub(crate) fn layout(detail: impl Into<String>) -> Self {
Self(IndexRepr::Layout(detail.into()))
}
}
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct BuildError(#[from] BuildRepr);
#[derive(Debug, thiserror::Error)]
enum BuildRepr {
#[error(transparent)]
Load(ModelLoadError),
#[error(transparent)]
Index(IndexError),
}
impl From<ModelLoadError> for BuildError {
fn from(error: ModelLoadError) -> Self {
Self(BuildRepr::Load(error))
}
}
impl From<IndexError> for BuildError {
fn from(error: IndexError) -> Self {
Self(BuildRepr::Index(error))
}
}
#[derive(Debug, thiserror::Error)]
#[error("selected tool identity absent from the picker catalog: {missing:?}")]
pub struct SelectionError {
missing: ToolId,
}
impl SelectionError {
pub(crate) fn new(missing: ToolId) -> Self {
Self { missing }
}
#[must_use]
pub fn missing_id(&self) -> &ToolId {
&self.missing
}
}
#[cfg(test)]
mod tests {
use super::{
BuildError, IndexError, ModelLoadError, QueryError, QueryErrorKind, SelectionError,
};
use crate::catalog::ToolId;
const fn assert_send_sync_static<T: Send + Sync + 'static>() {}
#[test]
fn every_public_error_is_send_sync_static() {
assert_send_sync_static::<ModelLoadError>();
assert_send_sync_static::<IndexError>();
assert_send_sync_static::<BuildError>();
assert_send_sync_static::<QueryError>();
assert_send_sync_static::<SelectionError>();
assert_send_sync_static::<QueryErrorKind>();
}
#[test]
fn a_query_error_classifies_and_displays_as_a_lowercase_noun_phrase() {
let error = QueryError::invalid_embedding("length zero");
assert_eq!(error.kind(), QueryErrorKind::InvalidEmbedding);
assert_eq!(error.to_string(), "invalid need embedding: length zero");
}
#[test]
fn a_build_error_wraps_an_index_error_transparently() {
let build = BuildError::from(IndexError::layout("count mismatch"));
assert_eq!(build.to_string(), "catalog vector layout: count mismatch");
}
#[test]
fn an_index_embed_error_retains_its_query_source() {
let index = IndexError::embed(QueryError::invalid_embedding("length zero"));
assert!(
std::error::Error::source(&index).is_some(),
"an embedding failure retains its query-error source"
);
let build = BuildError::from(index);
assert!(std::error::Error::source(&build).is_some());
}
#[test]
fn a_selection_error_reports_the_missing_identity() {
let missing = ToolId::new("files", "read_file");
let error = SelectionError::new(missing.clone());
assert_eq!(error.missing_id(), &missing);
}
}