Skip to main content

codex_app_server_sdk/
error.rs

1use crate::protocol::shared::RequestId;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
7pub enum ClientError {
8    #[error("not initialized: call initialize() before invoking {method}")]
9    NotInitialized { method: String },
10    #[error("connection not ready: call initialized() before invoking {method}")]
11    NotReady { method: String },
12    #[error("connection is already initialized")]
13    AlreadyInitialized,
14    #[error("request timed out after {timeout_ms}ms for method {method}")]
15    Timeout { method: String, timeout_ms: u64 },
16    #[error("transport send failed: {0}")]
17    TransportSend(String),
18    #[error("transport closed")]
19    TransportClosed,
20    #[error("invalid message from server: {0}")]
21    InvalidMessage(String),
22    #[error("serialization error: {0}")]
23    Serialization(#[from] serde_json::Error),
24    #[error("io error: {0}")]
25    Io(#[from] std::io::Error),
26    #[error("rpc error {error:?}")]
27    Rpc { error: RpcError },
28    #[error("unexpected result shape for method {method}: {source}")]
29    UnexpectedResult {
30        method: String,
31        source: serde_json::Error,
32    },
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct RpcError {
37    pub code: i64,
38    pub message: String,
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub data: Option<Value>,
41}
42
43#[derive(Debug)]
44pub enum IncomingClassified {
45    Response {
46        id: RequestId,
47        result: Result<Value, RpcError>,
48    },
49    Notification {
50        method: String,
51        params: Value,
52        raw: Value,
53    },
54    ServerRequest {
55        id: RequestId,
56        method: String,
57        params: Value,
58        raw: Value,
59    },
60}
61
62pub fn classify_incoming(value: Value) -> Result<IncomingClassified, ClientError> {
63    let obj = value
64        .as_object()
65        .ok_or_else(|| ClientError::InvalidMessage("expected JSON object".to_string()))?;
66
67    let id = obj
68        .get("id")
69        .map(|v| serde_json::from_value::<RequestId>(v.clone()))
70        .transpose()
71        .map_err(ClientError::Serialization)?;
72
73    let method = obj
74        .get("method")
75        .and_then(|v| v.as_str())
76        .map(|s| s.to_string());
77
78    match (id, method) {
79        (Some(id), Some(method)) => {
80            let params = obj.get("params").cloned().unwrap_or(Value::Null);
81            Ok(IncomingClassified::ServerRequest {
82                id,
83                method,
84                params,
85                raw: value,
86            })
87        }
88        (None, Some(method)) => {
89            let params = obj.get("params").cloned().unwrap_or(Value::Null);
90            Ok(IncomingClassified::Notification {
91                method,
92                params,
93                raw: value,
94            })
95        }
96        (Some(id), None) => {
97            if let Some(result) = obj.get("result") {
98                return Ok(IncomingClassified::Response {
99                    id,
100                    result: Ok(result.clone()),
101                });
102            }
103            if let Some(error) = obj.get("error") {
104                let parsed = serde_json::from_value::<RpcError>(error.clone())?;
105                return Ok(IncomingClassified::Response {
106                    id,
107                    result: Err(parsed),
108                });
109            }
110            Err(ClientError::InvalidMessage(
111                "response missing both result and error".to_string(),
112            ))
113        }
114        (None, None) => Err(ClientError::InvalidMessage(
115            "message missing method and id".to_string(),
116        )),
117    }
118}