hapi_rs/
errors.rs

1use crate::session::Session;
2
3pub use crate::ffi::raw::{HapiResult, StatusType, StatusVerbosity};
4use std::borrow::Cow;
5
6pub type Result<T> = std::result::Result<T, HapiError>;
7
8/// Error type returned by all APIs
9// TODO: This should really be an Enum.
10pub struct HapiError {
11    /// A specific error type.
12    pub kind: Kind,
13    /// Context error messages.
14    pub contexts: Vec<Cow<'static, str>>,
15    /// Error message from server or static if server couldn't respond.
16    pub server_message: Option<Cow<'static, str>>,
17}
18
19// This special case for TryFrom<T, Error = HapiError> where conversion can't fail.
20// for example when "impl TryInto<AttributeName>" receives AttributeName.
21impl From<std::convert::Infallible> for HapiError {
22    fn from(_: std::convert::Infallible) -> Self {
23        unreachable!()
24    }
25}
26
27pub(crate) trait ErrorContext<T> {
28    fn context<C>(self, context: C) -> Result<T>
29    where
30        C: Into<Cow<'static, str>>;
31
32    #[allow(unused)]
33    fn with_context<C, F>(self, func: F) -> Result<T>
34    where
35        C: Into<Cow<'static, str>>,
36        F: FnOnce() -> C;
37}
38
39impl<T> ErrorContext<T> for Result<T> {
40    fn context<C>(self, context: C) -> Result<T>
41    where
42        C: Into<Cow<'static, str>>,
43    {
44        match self {
45            Ok(ok) => Ok(ok),
46            Err(mut error) => {
47                error.contexts.push(context.into());
48                Err(error)
49            }
50        }
51    }
52
53    fn with_context<C, F>(self, func: F) -> Result<T>
54    where
55        C: Into<Cow<'static, str>>,
56        F: FnOnce() -> C,
57    {
58        match self {
59            Ok(ok) => Ok(ok),
60            Err(mut error) => {
61                error.contexts.push(func().into());
62                Err(error)
63            }
64        }
65    }
66}
67
68#[derive(Debug)]
69#[non_exhaustive]
70pub enum Kind {
71    /// Error returned by ffi calls
72    Hapi(HapiResult),
73    /// CString contains null byte
74    NullByte(std::ffi::NulError),
75    /// String is not a valid utf-8
76    Utf8Error(std::string::FromUtf8Error),
77    /// IO Error
78    Io(std::io::Error),
79    /// Misc error message from this crate.
80    Internal(Cow<'static, str>),
81}
82
83impl Kind {
84    fn description(&self) -> &str {
85        use HapiResult::*;
86
87        match self {
88            Kind::Hapi(Success) => "SUCCESS",
89            Kind::Hapi(Failure) => "FAILURE",
90            Kind::Hapi(AlreadyInitialized) => "ALREADY_INITIALIZED",
91            Kind::Hapi(NotInitialized) => "NOT_INITIALIZED",
92            Kind::Hapi(CantLoadfile) => "CANT_LOADFILE",
93            Kind::Hapi(ParmSetFailed) => "PARM_SET_FAILED",
94            Kind::Hapi(InvalidArgument) => "INVALID_ARGUMENT",
95            Kind::Hapi(CantLoadGeo) => "CANT_LOAD_GEO",
96            Kind::Hapi(CantGeneratePreset) => "CANT_GENERATE_PRESET",
97            Kind::Hapi(CantLoadPreset) => "CANT_LOAD_PRESET",
98            Kind::Hapi(AssetDefAlreadyLoaded) => "ASSET_DEF_ALREADY_LOADED",
99            Kind::Hapi(NoLicenseFound) => "NO_LICENSE_FOUND",
100            Kind::Hapi(DisallowedNcLicenseFound) => "DISALLOWED_NC_LICENSE_FOUND",
101            Kind::Hapi(DisallowedNcAssetWithCLicense) => "DISALLOWED_NC_ASSET_WITH_C_LICENSE",
102            Kind::Hapi(DisallowedNcAssetWithLcLicense) => "DISALLOWED_NC_ASSET_WITH_LC_LICENSE",
103            Kind::Hapi(DisallowedLcAssetWithCLicense) => "DISALLOWED_LC_ASSET_WITH_C_LICENSE",
104            Kind::Hapi(DisallowedHengineindieW3partyPlugin) => {
105                "DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN"
106            }
107            Kind::Hapi(AssetInvalid) => "ASSET_INVALID",
108            Kind::Hapi(NodeInvalid) => "NODE_INVALID",
109            Kind::Hapi(UserInterrupted) => "USER_INTERRUPTED",
110            Kind::Hapi(InvalidSession) => "INVALID_SESSION",
111            Kind::Hapi(SharedMemoryBufferOverflow) => "SHARED_MEMORY_BUFFER_OVERFLOW",
112            Kind::NullByte(_) => "String contains null byte!",
113            Kind::Utf8Error(_) => "String is not UTF-8!",
114            Kind::Internal(s) => s,
115            Kind::Io(_) => "IO Error",
116        }
117    }
118}
119
120impl From<HapiResult> for HapiError {
121    fn from(r: HapiResult) -> Self {
122        HapiError {
123            kind: Kind::Hapi(r),
124            contexts: Vec::new(),
125            server_message: None,
126        }
127    }
128}
129
130impl From<std::io::Error> for HapiError {
131    fn from(value: std::io::Error) -> Self {
132        HapiError {
133            kind: Kind::Io(value),
134            contexts: Vec::new(),
135            server_message: None,
136        }
137    }
138}
139
140impl HapiError {
141    pub(crate) fn new(
142        kind: Kind,
143        mut static_message: Option<Cow<'static, str>>,
144        server_message: Option<Cow<'static, str>>,
145    ) -> HapiError {
146        let mut contexts = vec![];
147        if let Some(m) = static_message.take() {
148            contexts.push(m);
149        }
150        HapiError {
151            kind,
152            contexts,
153            server_message,
154        }
155    }
156    pub(crate) fn internal<M: Into<Cow<'static, str>>>(message: M) -> Self {
157        HapiError {
158            kind: Kind::Internal(message.into()),
159            server_message: None,
160            contexts: vec![],
161        }
162    }
163}
164
165impl std::fmt::Display for HapiError {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        match &self.kind {
168            Kind::Hapi(_) => {
169                write!(f, "[{}]: ", self.kind.description())?;
170                if let Some(ref msg) = self.server_message {
171                    write!(f, "[Engine Message]: {}", msg)?;
172                }
173                if !self.contexts.is_empty() {
174                    writeln!(f)?; // blank line
175                }
176                for (n, msg) in self.contexts.iter().enumerate() {
177                    writeln!(f, "\t{}. {}", n, msg)?;
178                }
179                Ok(())
180            }
181            Kind::NullByte(e) => unsafe {
182                let text = e.clone().into_vec();
183                // SAFETY: We don't care about utf8 for error reporting I think
184                let text = std::str::from_utf8_unchecked(&text);
185                write!(f, "{e} in string \"{}\"", text)
186            },
187            Kind::Utf8Error(e) => unsafe {
188                // SAFETY: We don't care about utf8 for error reporting I think
189                let text = std::str::from_utf8_unchecked(e.as_bytes());
190                write!(f, "{e} in string \"{}\"", text)
191            },
192            Kind::Io(e) => {
193                write!(f, "{}", e)
194            }
195            Kind::Internal(e) => {
196                write!(f, "[Internal Error]: {e}")
197            }
198        }
199    }
200}
201
202impl std::fmt::Debug for HapiError {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        write!(f, "{}", self)
205    }
206}
207
208impl From<std::ffi::NulError> for HapiError {
209    fn from(e: std::ffi::NulError) -> Self {
210        HapiError::new(Kind::NullByte(e), None, None)
211    }
212}
213
214impl From<std::string::FromUtf8Error> for HapiError {
215    fn from(e: std::string::FromUtf8Error) -> Self {
216        HapiError::new(Kind::Utf8Error(e), None, None)
217    }
218}
219
220impl From<&str> for HapiError {
221    fn from(value: &str) -> Self {
222        HapiError::new(Kind::Internal(Cow::Owned(value.to_string())), None, None)
223    }
224}
225
226impl std::error::Error for HapiError {}
227
228impl HapiResult {
229    pub(crate) fn check_err<F, M>(self, session: &Session, context: F) -> Result<()>
230    where
231        M: Into<Cow<'static, str>>,
232        F: FnOnce() -> M,
233    {
234        match self {
235            HapiResult::Success => Ok(()),
236            _err => {
237                let server_message = session
238                    .get_status_string(StatusType::CallResult, StatusVerbosity::All)
239                    .map(Cow::Owned)
240                    .unwrap_or_else(|_| Cow::Borrowed("Could not retrieve error message"));
241                let mut err = HapiError::new(Kind::Hapi(self), None, Some(server_message));
242                err.contexts.push(context().into());
243                Err(err)
244            }
245        }
246    }
247
248    pub(crate) fn error_message<I: Into<Cow<'static, str>>>(self, message: I) -> Result<()> {
249        match self {
250            HapiResult::Success => Ok(()),
251            _err => {
252                let mut err = HapiError::new(
253                    Kind::Hapi(self),
254                    None,
255                    Some(Cow::Borrowed("Server error message unavailable")),
256                );
257                err.contexts.push(message.into());
258                Err(err)
259            }
260        }
261    }
262}