use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::fmt::Debug;
pub trait FFIBoundary: Serialize + DeserializeOwned + Send + Sync + 'static {
#[allow(clippy::result_large_err)]
fn validate(&self) -> Result<(), FFIError> {
Ok(())
}
fn schema_version() -> u32 {
1
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ErrorCode {
NotFound,
NotAuthenticated,
GitError,
IoError,
NetworkError,
InvalidInput,
#[default]
InternalError,
Timeout,
AlreadyExists,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct ErrorContext {
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exit_code: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stderr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stdout: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub working_dir: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FFIError {
pub message: String,
pub code: ErrorCode,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<ErrorContext>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suggestion: Option<String>,
}
impl std::fmt::Display for FFIError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{:?}] {}", self.code, self.message)
}
}
impl std::error::Error for FFIError {}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "kind", content = "payload")]
pub enum FFIResult<T> {
Success(T),
Error(FFIError),
}
impl<T> FFIResult<T> {
pub fn success(value: T) -> Self {
Self::Success(value)
}
pub fn error(
message: impl Into<String>,
code: ErrorCode,
context: Option<ErrorContext>,
suggestion: Option<String>,
) -> Self {
Self::Error(FFIError {
message: message.into(),
code,
context,
suggestion,
})
}
pub fn simple_error(message: impl Into<String>, code: ErrorCode) -> Self {
Self::Error(FFIError {
message: message.into(),
code,
context: None,
suggestion: None,
})
}
}
impl<T: FFIBoundary> FFIBoundary for FFIResult<T> {}
impl FFIBoundary for FFIError {}
impl FFIBoundary for String {}
impl FFIBoundary for bool {}
impl<T: FFIBoundary> FFIBoundary for Vec<T> {}
impl<T: FFIBoundary> FFIBoundary for Option<T> {}
impl FFIBoundary for () {}
impl FFIBoundary for i32 {}
impl FFIBoundary for i64 {}
impl FFIBoundary for isize {}
impl FFIBoundary for i8 {}
impl FFIBoundary for i16 {}
impl FFIBoundary for u64 {}
impl FFIBoundary for u32 {}
impl FFIBoundary for usize {}
impl FFIBoundary for u8 {}
impl FFIBoundary for u16 {}