#[derive(Debug)]
pub(crate) struct ApiErrorResponse {
pub(crate) message: String,
}
impl<'de> serde::Deserialize<'de> for ApiErrorResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Self {
message: error_message(deserializer)?,
})
}
}
pub(crate) fn error_message<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <serde_json::Value as serde::Deserialize>::deserialize(deserializer)?;
let serde_json::Value::Object(body) = value else {
return Err(serde::de::Error::custom(
"error envelope must be a JSON object",
));
};
Ok(body
.get("error")
.filter(|value| !value.is_null())
.or_else(|| body.get("message"))
.map(|value| match value {
serde_json::Value::String(message) => message.clone(),
other => other.to_string(),
})
.unwrap_or_default())
}
pub(crate) trait ProviderEnvelope {
type Payload;
fn into_payload(self) -> Result<Self::Payload, String>;
}
#[derive(serde::Deserialize)]
#[serde(transparent)]
pub(crate) struct DirectPayload<T>(T);
impl<T> ProviderEnvelope for DirectPayload<T> {
type Payload = T;
fn into_payload(self) -> Result<T, String> {
Ok(self.0)
}
}
impl<T> ProviderEnvelope for crate::providers::openai::client::ApiResponse<T> {
type Payload = T;
fn into_payload(self) -> Result<T, String> {
match self {
Self::Ok(value) => Ok(value),
Self::Err(error) => Err(error.message),
}
}
}
#[cfg(test)]
mod tests {
use crate::providers::openai::client::ApiResponse;
#[derive(Debug, serde::Deserialize)]
struct Success {
#[allow(dead_code)]
text: String,
}
fn classify(body: &str) -> String {
match serde_json::from_str::<ApiResponse<Success>>(body).expect("body must decode") {
ApiResponse::Err(error) => error.message,
ApiResponse::Ok(_) => panic!("error body must classify as the error envelope"),
}
}
#[test]
fn dual_message_and_error_keys_classify_as_the_error_envelope() {
assert_eq!(
classify(r#"{"message":"quota exceeded","error":{"code":"429"}}"#),
r#"{"code":"429"}"#
);
}
#[test]
fn null_error_key_falls_back_to_message() {
assert_eq!(
classify(r#"{"error":null,"message":"over capacity"}"#),
"over capacity"
);
}
}