use std::{error::Error as StdError, time::Duration};
use switchyard_protocol::LlmClientError;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, LibsyError>;
#[derive(Debug, Error)]
pub enum LibsyError {
#[error("target {target:?} was not found")]
TargetNotFound {
target: String,
},
#[error("no routing targets are configured")]
NoTargets,
#[error("{message}")]
AlgorithmError {
message: String,
},
#[error("target {target:?} has no client to serve the call")]
MissingClient {
target: String,
},
#[error(transparent)]
Driver(#[from] DriverError),
#[error("algorithm task failed: {source}")]
AlgorithmTask {
#[from]
source: tokio::task::JoinError,
},
#[error("algorithm run ended without a final response")]
MissingFinalResponse,
#[error("client call to target {target:?} failed: {source}")]
ClientCall {
target: String,
#[source]
source: LlmClientError,
},
#[error("every target exceeded its context window")]
AllTargetsExcluded,
#[error("{operation} failed: {source}")]
External {
operation: &'static str,
#[source]
source: Box<dyn StdError + Send + Sync>,
},
}
impl LibsyError {
pub fn client_call(target: impl Into<String>, source: LlmClientError) -> Self {
Self::ClientCall {
target: target.into(),
source,
}
}
pub fn external(
operation: &'static str,
source: impl StdError + Send + Sync + 'static,
) -> Self {
Self::External {
operation,
source: Box::new(source),
}
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum DriverError {
#[error("driver stream must be taken before calling producer methods")]
NotStarted,
#[error("driver stream is closed")]
StreamClosed,
#[error("driver stream was already taken")]
StreamAlreadyTaken,
#[error("driver response promise was dropped")]
ResponseDropped,
#[error("driver response timed out after {timeout:?}")]
ResponseTimedOut {
timeout: Duration,
},
#[error("driver payload type mismatch: expected {expected}")]
TypeMismatch {
expected: &'static str,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn client_call_preserves_target_and_source() {
let error = LibsyError::client_call(
"strong",
LlmClientError::General("upstream down".to_string()),
);
match &error {
LibsyError::ClientCall { target, source } => {
assert_eq!(target, "strong");
assert_eq!(source.to_string(), "upstream down");
}
other => panic!("expected ClientCall, got {other:?}"),
}
assert_eq!(
StdError::source(&error).map(ToString::to_string),
Some("upstream down".to_string())
);
}
#[test]
fn external_preserves_operation_and_source() {
let error = LibsyError::external(
"loading extension",
std::io::Error::other("bad configuration"),
);
assert!(matches!(
&error,
LibsyError::External { operation, .. } if *operation == "loading extension"
));
assert_eq!(
StdError::source(&error).map(ToString::to_string),
Some("bad configuration".to_string())
);
}
}