use std::path::PathBuf;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ProviderErrorKind {
Auth,
RateLimit,
ModelNotFound,
Quota,
Overloaded,
Timeout,
Spawn,
Protocol,
Other,
}
impl ProviderErrorKind {
#[must_use]
pub fn classify(raw: &str) -> Self {
match raw {
"auth" => Self::Auth,
"rate_limit" => Self::RateLimit,
"model_not_found" => Self::ModelNotFound,
"quota" => Self::Quota,
"overloaded" => Self::Overloaded,
"timeout" => Self::Timeout,
"spawn" => Self::Spawn,
"protocol" => Self::Protocol,
_ => Self::Other,
}
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Auth => "auth",
Self::RateLimit => "rate_limit",
Self::ModelNotFound => "model_not_found",
Self::Quota => "quota",
Self::Overloaded => "overloaded",
Self::Timeout => "timeout",
Self::Spawn => "spawn",
Self::Protocol => "protocol",
Self::Other => "other",
}
}
}
impl std::fmt::Display for ProviderErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("could not read {path}: {source}")]
Io {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("invalid YAML in {path}: {source}")]
Yaml {
path: PathBuf,
#[source]
source: serde_yaml::Error,
},
#[error("invalid test definition: {0}")]
Invalid(String),
#[error("provider error ({context}): {message}")]
Provider {
context: String,
message: String,
kind: Option<ProviderErrorKind>,
},
#[error("skill validation failed with {} finding(s)", .0.len())]
Validation(Vec<String>),
}
impl Error {
pub fn provider(context: impl Into<String>, message: impl std::fmt::Display) -> Self {
Error::Provider {
context: context.into(),
message: message.to_string(),
kind: None,
}
}
pub fn provider_classified(
context: impl Into<String>,
message: impl std::fmt::Display,
kind: ProviderErrorKind,
) -> Self {
Error::Provider {
context: context.into(),
message: message.to_string(),
kind: Some(kind),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provider_error_kind_string_forms_agree() {
let all = [
ProviderErrorKind::Auth,
ProviderErrorKind::RateLimit,
ProviderErrorKind::ModelNotFound,
ProviderErrorKind::Quota,
ProviderErrorKind::Overloaded,
ProviderErrorKind::Timeout,
ProviderErrorKind::Spawn,
ProviderErrorKind::Protocol,
ProviderErrorKind::Other,
];
for kind in all {
let wire = kind.as_str();
assert_eq!(serde_json::to_value(kind).unwrap(), serde_json::json!(wire));
assert_eq!(kind.to_string(), wire);
assert_eq!(ProviderErrorKind::classify(wire), kind);
}
}
#[test]
fn classify_maps_unknown_labels_to_other() {
assert_eq!(
ProviderErrorKind::classify("brand_new_upstream_kind"),
ProviderErrorKind::Other
);
}
}