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
8pub struct HapiError {
11 pub kind: Kind,
13 pub contexts: Vec<Cow<'static, str>>,
15 pub server_message: Option<Cow<'static, str>>,
17}
18
19impl 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 Hapi(HapiResult),
73 NullByte(std::ffi::NulError),
75 Utf8Error(std::string::FromUtf8Error),
77 Io(std::io::Error),
79 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)?; }
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 let text = std::str::from_utf8_unchecked(&text);
184 write!(f, "{e} in string \"{}\"", text)
185 },
186 Kind::Utf8Error(e) => unsafe {
187 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}