1use serde::{de::DeserializeOwned, Deserialize, Serialize};
2use std::fmt::Debug;
3
4pub trait FFIBoundary: Serialize + DeserializeOwned + Send + Sync + 'static {
9 #[allow(clippy::result_large_err)]
14 fn validate(&self) -> Result<(), FFIError> {
15 Ok(())
16 }
17
18 fn schema_version() -> u32 {
20 1
21 }
22}
23
24#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
26#[serde(rename_all = "snake_case")]
27pub enum ErrorCode {
28 NotFound,
30 NotAuthenticated,
32 GitError,
34 IoError,
36 NetworkError,
38 InvalidInput,
40 #[default]
42 InternalError,
43 Timeout,
45 AlreadyExists,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
51pub struct ErrorContext {
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub command: Option<String>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub exit_code: Option<i32>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub stderr: Option<String>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub stdout: Option<String>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub file_path: Option<String>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub working_dir: Option<String>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
79pub struct FFIError {
80 pub message: String,
82
83 pub code: ErrorCode,
85
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub context: Option<ErrorContext>,
89
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub suggestion: Option<String>,
93}
94
95impl std::fmt::Display for FFIError {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 write!(f, "[{:?}] {}", self.code, self.message)
98 }
99}
100
101impl std::error::Error for FFIError {}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
105#[serde(tag = "kind", content = "payload")]
106pub enum FFIResult<T> {
107 Success(T),
108 Error(FFIError),
109}
110
111impl<T> FFIResult<T> {
112 pub fn success(value: T) -> Self {
114 Self::Success(value)
115 }
116
117 pub fn error(
119 message: impl Into<String>,
120 code: ErrorCode,
121 context: Option<ErrorContext>,
122 suggestion: Option<String>,
123 ) -> Self {
124 Self::Error(FFIError {
125 message: message.into(),
126 code,
127 context,
128 suggestion,
129 })
130 }
131
132 pub fn simple_error(message: impl Into<String>, code: ErrorCode) -> Self {
134 Self::Error(FFIError {
135 message: message.into(),
136 code,
137 context: None,
138 suggestion: None,
139 })
140 }
141}
142
143impl<T: FFIBoundary> FFIBoundary for FFIResult<T> {}
144impl FFIBoundary for FFIError {}
145impl FFIBoundary for String {}
146impl FFIBoundary for bool {}
147impl<T: FFIBoundary> FFIBoundary for Vec<T> {}
148impl<T: FFIBoundary> FFIBoundary for Option<T> {}
149
150impl FFIBoundary for () {}
153
154impl FFIBoundary for i32 {}
157impl FFIBoundary for i64 {}
158impl FFIBoundary for isize {}
159impl FFIBoundary for i8 {}
160impl FFIBoundary for i16 {}
161
162impl FFIBoundary for u64 {}
164impl FFIBoundary for u32 {}
165impl FFIBoundary for usize {}
166impl FFIBoundary for u8 {}
167impl FFIBoundary for u16 {}