use serde::{Deserialize, Serialize};
pub use claude_wrapper::types::QueryResult;
pub const VERSION: u32 = 1;
pub const EXIT_FAILURE: i32 = 1;
pub const EXIT_AUTH: i32 = 2;
pub const EXIT_BUDGET: i32 = 3;
pub const EXIT_TIMEOUT: i32 = 4;
pub const EXIT_MAX_TURNS: i32 = 5;
pub const EXIT_UNUSABLE_RESULT: i32 = 6;
pub const EXIT_MAX_BUDGET: i32 = 7;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SuccessEnvelope<T> {
pub version: u32,
pub result: T,
pub refusal: bool,
}
impl<T> SuccessEnvelope<T> {
pub fn new(result: T, refusal: bool) -> Self {
Self {
version: VERSION,
result,
refusal,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionedResult<T> {
pub version: u32,
pub result: T,
}
impl<T> VersionedResult<T> {
pub fn new(result: T) -> Self {
Self {
version: VERSION,
result,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorEnvelope {
pub version: u32,
pub error: ErrorBody,
}
impl ErrorEnvelope {
pub fn new(error: ErrorBody) -> Self {
Self {
version: VERSION,
error,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorBody {
pub kind: String,
pub message: String,
pub exit_code: i32,
pub chain: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub see_also: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn success_envelope_round_trips() {
let payload = serde_json::json!({ "result": "hi", "session_id": "s1" });
let json = serde_json::to_string(&SuccessEnvelope::new(&payload, false)).unwrap();
let back: SuccessEnvelope<serde_json::Value> = serde_json::from_str(&json).unwrap();
assert_eq!(back.version, 1);
assert!(!back.refusal);
assert_eq!(back.result["session_id"], "s1");
}
#[test]
fn versioned_result_round_trips() {
let json = serde_json::to_string(&VersionedResult::new(&vec![1, 2, 3])).unwrap();
assert_eq!(json, r#"{"version":1,"result":[1,2,3]}"#);
let back: VersionedResult<Vec<i32>> = serde_json::from_str(&json).unwrap();
assert_eq!(back.result, vec![1, 2, 3]);
}
#[test]
fn error_envelope_shape_and_see_also_omitted_when_empty() {
let env = ErrorEnvelope::new(ErrorBody {
kind: "auth".to_string(),
message: "not authenticated".to_string(),
exit_code: EXIT_AUTH,
chain: vec!["top".to_string(), "root".to_string()],
see_also: Vec::new(),
});
let json = serde_json::to_string(&env).unwrap();
assert!(json.contains(r#""version":1"#), "{json}");
assert!(json.contains(r#""kind":"auth""#), "{json}");
assert!(json.contains(r#""exit_code":2"#), "{json}");
assert!(
!json.contains("see_also"),
"empty see_also must be omitted: {json}"
);
let back: ErrorEnvelope = serde_json::from_str(&json).unwrap();
assert_eq!(back.error.exit_code, 2);
assert!(back.error.see_also.is_empty());
}
#[test]
fn exit_codes_are_the_stable_map() {
assert_eq!(
(
EXIT_FAILURE,
EXIT_AUTH,
EXIT_BUDGET,
EXIT_TIMEOUT,
EXIT_MAX_TURNS,
EXIT_UNUSABLE_RESULT,
EXIT_MAX_BUDGET
),
(1, 2, 3, 4, 5, 6, 7)
);
}
}