1use reqwest::StatusCode;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use thiserror::Error;
7
8pub type Result<T> = std::result::Result<T, Error>;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct ApiErrorBody {
12 pub message: String,
13 #[serde(flatten)]
14 pub extra: Value,
15}
16
17#[derive(Debug, Error)]
18pub enum Error {
24 #[error("{message}")]
25 ApiStatus {
26 message: String,
27 status: StatusCode,
28 request_id: Option<String>,
29 body: Option<Value>,
30 },
31 #[error("request timed out")]
32 Timeout,
33 #[error("connection error: {0}")]
34 Connection(String),
35 #[error("configuration error: {0}")]
36 Config(String),
37 #[error("invalid URL: {0}")]
38 Url(#[from] url::ParseError),
39 #[error("invalid header value: {0}")]
40 HeaderValue(#[from] http::header::InvalidHeaderValue),
41 #[error("invalid JSON: {0}")]
42 Json(#[from] serde_json::Error),
43 #[error("I/O error: {0}")]
44 Io(#[from] std::io::Error),
45 #[error("SSE stream error: {0}")]
46 Stream(String),
47}
48
49impl Error {
50 pub fn api_status(status: StatusCode, request_id: Option<String>, body: Option<Value>) -> Self {
51 let message = body
52 .as_ref()
53 .and_then(extract_error_message)
54 .unwrap_or_else(|| format!("OpenAI API returned status {status}"));
55
56 Self::ApiStatus {
57 message,
58 status,
59 request_id,
60 body,
61 }
62 }
63}
64
65fn extract_error_message(body: &Value) -> Option<String> {
66 body.get("error")
67 .and_then(|error| error.get("message"))
68 .and_then(Value::as_str)
69 .or_else(|| body.get("message").and_then(Value::as_str))
70 .map(ToOwned::to_owned)
71}