Skip to main content

opencode/
errors.rs

1use thiserror::Error;
2
3/// General SDK error for validation and logic failures.
4#[derive(Debug, Error, Clone)]
5#[error("{message}")]
6pub struct OpencodeSDKError {
7    /// Human-readable error message.
8    pub message: String,
9}
10
11impl OpencodeSDKError {
12    pub fn new(message: impl Into<String>) -> Self {
13        Self {
14            message: message.into(),
15        }
16    }
17}
18
19/// Error when the OpenCode CLI executable cannot be found.
20#[derive(Debug, Error, Clone)]
21#[error("{message}")]
22pub struct CLINotFoundError {
23    /// Human-readable not-found message.
24    pub message: String,
25    /// The path that was searched, if a specific path was configured.
26    pub cli_path: Option<String>,
27}
28
29impl CLINotFoundError {
30    pub fn new(message: impl Into<String>, cli_path: Option<String>) -> Self {
31        let base = message.into();
32        let message = match &cli_path {
33            Some(path) => format!("{base}: {path}"),
34            None => base,
35        };
36        Self { message, cli_path }
37    }
38}
39
40/// Error from OpenCode CLI subprocess execution.
41#[derive(Debug, Error, Clone)]
42#[error("{message}")]
43pub struct ProcessError {
44    /// Human-readable process error message.
45    pub message: String,
46    /// Process exit code when available.
47    pub exit_code: Option<i32>,
48    /// Captured output snippet from stdout/stderr.
49    pub output: Option<String>,
50}
51
52impl ProcessError {
53    pub fn new(message: impl Into<String>, exit_code: Option<i32>, output: Option<String>) -> Self {
54        let base = message.into();
55        let mut message = base;
56        if let Some(code) = exit_code {
57            message = format!("{message} (exit code: {code})");
58        }
59        if let Some(content) = &output {
60            if !content.trim().is_empty() {
61                message = format!("{message}\nOutput: {content}");
62            }
63        }
64
65        Self {
66            message,
67            exit_code,
68            output,
69        }
70    }
71}
72
73/// Error returned by OpenCode HTTP API.
74#[derive(Debug, Error, Clone)]
75#[error("OpenCode API error: status {status}, body: {body}")]
76pub struct ApiError {
77    /// HTTP status code.
78    pub status: u16,
79    /// Raw body text (JSON or plain text).
80    pub body: String,
81}
82
83/// Unified error type for OpenCode SDK operations.
84#[derive(Debug, Error)]
85pub enum Error {
86    /// General SDK-level validation and logic errors.
87    #[error(transparent)]
88    OpencodeSDK(#[from] OpencodeSDKError),
89    /// OpenCode CLI executable not found.
90    #[error(transparent)]
91    CLINotFound(#[from] CLINotFoundError),
92    /// OpenCode CLI process failure.
93    #[error(transparent)]
94    Process(#[from] ProcessError),
95    /// HTTP API response error.
96    #[error(transparent)]
97    Api(#[from] ApiError),
98    /// Standard I/O error.
99    #[error(transparent)]
100    Io(#[from] std::io::Error),
101    /// JSON serialization/deserialization error.
102    #[error(transparent)]
103    Json(#[from] serde_json::Error),
104    /// HTTP client error.
105    #[error(transparent)]
106    Http(#[from] reqwest::Error),
107    /// Header parsing/encoding error.
108    #[error(transparent)]
109    InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
110    /// Header name parsing error.
111    #[error(transparent)]
112    InvalidHeaderName(#[from] reqwest::header::InvalidHeaderName),
113    /// Timeout while waiting for server startup.
114    #[error("Timeout waiting for server to start after {timeout_ms}ms")]
115    ServerStartupTimeout { timeout_ms: u64 },
116    /// Missing required path parameter.
117    #[error("Missing required path parameter: {0}")]
118    MissingPathParameter(String),
119}
120
121/// Specialized `Result` for OpenCode SDK.
122pub type Result<T> = std::result::Result<T, Error>;