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::NullByte(_) => "String contains null byte!",
112            Kind::Utf8Error(_) => "String is not UTF-8!",
113            Kind::Internal(s) => s,
114            Kind::Io(_) => "IO Error",
115        }
116    }
117}
118
119impl From<HapiResult> for HapiError {
120    fn from(r: HapiResult) -> Self {
121        HapiError {
122            kind: Kind::Hapi(r),
123            contexts: Vec::new(),
124            server_message: None,
125        }
126    }
127}
128
129impl From<std::io::Error> for HapiError {
130    fn from(value: std::io::Error) -> Self {
131        HapiError {
132            kind: Kind::Io(value),
133            contexts: Vec::new(),
134            server_message: None,
135        }
136    }
137}
138
139impl HapiError {
140    pub(crate) fn new(
141        kind: Kind,
142        mut static_message: Option<Cow<'static, str>>,
143        server_message: Option<Cow<'static, str>>,
144    ) -> HapiError {
145        let mut contexts = vec![];
146        if let Some(m) = static_message.take() {
147            contexts.push(m);
148        }
149        HapiError {
150            kind,
151            contexts,
152            server_message,
153        }
154    }
155    pub(crate) fn internal<M: Into<Cow<'static, str>>>(message: M) -> Self {
156        HapiError {
157            kind: Kind::Internal(message.into()),
158            server_message: None,
159            contexts: vec![],
160        }
161    }
162}
163
164impl std::fmt::Display for HapiError {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        match &self.kind {
167            Kind::Hapi(_) => {
168                write!(f, "[{}]: ", self.kind.description())?;
169                if let Some(ref msg) = self.server_message {
170                    write!(f, "[Engine Message]: {}", msg)?;
171                }
172                if !self.contexts.is_empty() {
173                    writeln!(f)?; // blank line
174                }
175                for (n, msg) in self.contexts.iter().enumerate() {
176                    writeln!(f, "\t{}. {}", n, msg)?;
177                }
178                Ok(())
179            }
180            Kind::NullByte(e) => unsafe {
181                let text = e.clone().into_vec();
182                // SAFETY: We don't care about utf8 for error reporting I think
183                let text = std::str::from_utf8_unchecked(&text);
184                write!(f, "{e} in string \"{}\"", text)
185            },
186            Kind::Utf8Error(e) => unsafe {
187                // SAFETY: We don't care about utf8 for error reporting I think
188                let text = std::str::from_utf8_unchecked(e.as_bytes());
189                write!(f, "{e} in string \"{}\"", text)
190            },
191            Kind::Io(e) => {
192                write!(f, "{}", e)
193            }
194            Kind::Internal(e) => {
195                write!(f, "[Internal Error]: {e}")
196            }
197        }
198    }
199}
200
201impl std::fmt::Debug for HapiError {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        write!(f, "{}", self)
204    }
205}
206
207impl From<std::ffi::NulError> for HapiError {
208    fn from(e: std::ffi::NulError) -> Self {
209        HapiError::new(Kind::NullByte(e), None, None)
210    }
211}
212
213impl From<std::string::FromUtf8Error> for HapiError {
214    fn from(e: std::string::FromUtf8Error) -> Self {
215        HapiError::new(Kind::Utf8Error(e), None, None)
216    }
217}
218
219impl From<&str> for HapiError {
220    fn from(value: &str) -> Self {
221        HapiError::new(Kind::Internal(Cow::Owned(value.to_string())), None, None)
222    }
223}
224
225impl std::error::Error for HapiError {}
226
227impl HapiResult {
228    pub(crate) fn check_err<F, M>(self, session: &Session, context: F) -> Result<()>
229    where
230        M: Into<Cow<'static, str>>,
231        F: FnOnce() -> M,
232    {
233        match self {
234            HapiResult::Success => Ok(()),
235            _err => {
236                let server_message = session
237                    .get_status_string(StatusType::CallResult, StatusVerbosity::All)
238                    .map(Cow::Owned)
239                    .unwrap_or_else(|_| Cow::Borrowed("Could not retrieve error message"));
240                let mut err = HapiError::new(Kind::Hapi(self), None, Some(server_message));
241                err.contexts.push(context().into());
242                Err(err)
243            }
244        }
245    }
246
247    pub(crate) fn error_message<I: Into<Cow<'static, str>>>(self, message: I) -> Result<()> {
248        match self {
249            HapiResult::Success => Ok(()),
250            _err => {
251                let mut err = HapiError::new(
252                    Kind::Hapi(self),
253                    None,
254                    Some(Cow::Borrowed("Server error message unavailable")),
255                );
256                err.contexts.push(message.into());
257                Err(err)
258            }
259        }
260    }
261}