rustmex/
message.rs

1/*!
2 * Create and send Matlab warning and error messages
3 *
4 * Matlab handles errors in a variety of ways, but all of them ultimately boil down to
5 * error/warning id plus a message. This module describes that way of error handling in a
6 * more Rusty way.
7 */
8
9use std::ffi::CString;
10
11use std::fmt::{self, Display};
12
13use ndarray::{ShapeError, ErrorKind};
14
15use rustmex_core::{
16	convert::{
17		FromMatlabError,
18		FromMatlabErrorReason,
19	},
20};
21
22use rustmex_core::mxArray;
23use rustmex_core::pointers::MxArray;
24
25/**
26 * Trait describing a "mex message", i.e. the input to mex's warning and error functions.
27 *
28 * The methods return &str, because this trait is meant to be used within Rust library
29 * code. Having this trait return C string pointers is too much of a burden on the users;
30 * conversions to c strings for matlab are done by rustmex
31 */
32pub trait MexMessage: Display {
33	fn id(&self) -> &str;
34}
35
36// TODO: Redesign how errors are handled; since MexMessages can sometimes hold references
37// in recent versions it's a complete mess.
38/**
39 * Error type, to be returned from the entrypoint function
40 */
41pub struct Error {
42	error_id: String,
43	error_msg: String,
44}
45
46impl Error {
47	pub fn id(&self) -> &str {
48		&self.error_id
49	}
50
51	pub fn to_string(self) -> String {
52		self.error_msg
53	}
54}
55
56impl<T> From<T> for Error where T: MexMessage {
57	fn from(other: T) -> Self {
58		Error { error_id: other.id().to_string(), error_msg: other.to_string()}
59	}
60}
61
62#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
63pub struct MissingVariable {
64	id: &'static str,
65	msg: &'static str,
66}
67
68/**
69 * One of the first things a MEX file has to do is parse its arguments — the LHS array.
70 * Part of that is checking whether it got all the arguments it expected. In MEX files,
71 * this is best done via slice's `.get()` method. This method then returns an Option;
72 * Some if there is an argument, None if that slot is out of bounds. The value can then
73 * be pattern matched out via a `let val = if let Some(_)`, but that requires an `else`
74 * arm and is overall just combersome.
75 *
76 * This trait is the solution for that problem. It elegantly maps an `Option` to a
77 * `Result`; an `Ok` if there was some value, an `Err` with a [`MissingVariable`]
78 * [`MexMessage`] if not.
79 *
80 * Note that there is a slight difference in functionality between the two implementers.
81 */
82pub trait Missing<T> {
83	fn error_if_missing(self, id: &'static str , msg: &'static str)
84		-> Result<T, MissingVariable> where Self: Sized;
85}
86
87/**
88 * See [`Missing`] for the main explanation. This implementation also dereferences the
89 * indirection of the slice, yielding a direct reference to the
90 * [`mxArray`](crate::mxArray).
91 *
92 * Best used with `get` on a slice.
93 */
94impl<'a> Missing<&'a mxArray> for Option<&'_ &'a mxArray> {
95	fn error_if_missing(self, id: &'static str, msg: &'static str)
96		-> Result<&'a mxArray, MissingVariable>
97	{
98		match self {
99			Some(v) => Ok(*v),
100			None => Err(MissingVariable {
101				id, msg
102			})
103		}
104	}
105}
106
107/**
108 * See [`Missing`] for the main explanation. Contrast with the implementation for
109 * Option<&&mxArray>. This implementation does not dereference the slice's reference,
110 * since [`MxArray`] does not implement copy —­The slice remains the owner of the
111 * [`MxArray`].
112 *
113 * Best used with `get_mut` on a slice, and `replace` on the reference yielded to place
114 * the new value.
115 */
116impl<'s> Missing<&'s mut Option<MxArray>> for Option<&'s mut Option<MxArray>> {
117	fn error_if_missing(self, id: &'static str, msg: &'static str)
118		-> Result<&'s mut Option<MxArray>, MissingVariable>
119	{
120		match self {
121			Some(v) => Ok(v),
122			None => Err(MissingVariable {
123				id, msg
124			})
125		}
126	}
127}
128
129impl MexMessage for MissingVariable {
130	fn id(&self) -> &str {
131		self.id
132	}
133}
134
135impl Display for MissingVariable {
136	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137		write!(f, "{}", self.msg)
138	}
139}
140
141impl<S> MexMessage for FromMatlabError<S> {
142	fn id(&self) -> &str {
143		match self.reason() {
144			FromMatlabErrorReason::BadClass => "rustmex:convert:bad_class",
145			FromMatlabErrorReason::BadComplexity => "rustmex:convert:bad_complexity",
146			FromMatlabErrorReason::BadSparsity => "rustmex:convert:bad_sparsity",
147			FromMatlabErrorReason::Size => "rustmex:convert:size_mismatch",
148			// TODO: Fix this
149			_ => "rustmex:enum_defined_elsewhere"
150		}
151	}
152}
153
154pub struct AdHoc<Id, Msg>(pub Id, pub Msg);
155
156/**
157 * Convenience implementation for MexMessage for two str's. Sometimes an ad-hoc
158 * error/warning is generated, from two strings instead of a specific error type.
159 */
160impl<Id, Msg> MexMessage for AdHoc<Id, Msg> where Id: AsRef<str>, Msg: Display {
161	fn id(&self) -> &str { self.0.as_ref() }
162}
163
164impl<Id, Msg> Display for AdHoc<Id, Msg> where Msg: Display {
165	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.1.fmt(f) }
166}
167
168/**
169 * Generate a Matlab warning
170 */
171pub fn warning(w: &dyn MexMessage) -> () {
172	let id = CString::new(w.id()).expect("No inner nul bytes");
173	let msg = CString::new(w.to_string()).expect("No inner nul bytes");
174	unsafe { rustmex_core::shim::rustmex_warn_id_and_txt(id.as_ptr(), msg.as_ptr()); }
175}
176
177/**
178 * Trigger a Matlab error
179 *
180 * Note that this macro diverges, as it returns control to the Matlab prompt. This
181 * divergent behaviour prevents destructors from running, and therefore calling this
182 * method may leak memory. Consider returning from your entrypoint with a
183 * [`rustmex::Result::Err`](crate::Result) instead.
184 */
185// This macro was previously named just `error!`, but is now `trigger_error`. Both
186// because the name reflects what it does (actually trigger the error), and because now
187// the name can be used for something more ergonomic in the root module (to
188// unconditionally return a `rustmex::Result::Err`; something which is more likely to
189// occur in "normal" code than triggering a diverging error in the middle of it).
190#[macro_export]
191macro_rules! trigger_error {
192	($e:expr) => {{
193		use ::rustmex::{
194			message::MexMessage,
195			alloc::NonPersistent as NP,
196		};
197
198		let e = $e;
199		let id = unsafe { NP::from_stringish(e.id(), true) };
200		let msg = unsafe { NP::from_stringish(e.to_string(), true) };
201
202		// Run e's destructor now, as it can't run after mexErrMsgIdAndTxt
203		// drop(e);
204
205		unsafe { ::rustmex::message::trigger_error_fn(NP::ptr(&id) as *const i8, NP::ptr(&msg) as *const i8); }
206	}}
207}
208
209/**
210 * Function to trigger a Matlab error.
211 *
212 * Used in [`trigger_error`].
213 */
214pub unsafe fn trigger_error_fn(id: *const i8, msg: *const i8) -> ! {
215	::rustmex_core::shim::rustmex_err_id_and_txt(id, msg);
216	unreachable!()
217}
218
219impl MexMessage for ShapeError {
220	fn id(&self) -> &str {
221		use ErrorKind::*;
222		match self.kind() {
223			IncompatibleShape  => "ndarray:incompatible_shape",
224			IncompatibleLayout => "ndarray:incompatible_layout",
225			RangeLimited	   => "ndarray:range_limited",
226			OutOfBounds        => "ndarray:out_of_bounds",
227			Unsupported        => "ndarray:unsupported",
228			Overflow           => "ndarray:overflow",
229			_                  => "rustmex:ndarray:unrecognised_errorkind",
230		}
231	}
232}
233
234use crate::structs::StructError;
235
236impl MexMessage for StructError {
237	fn id(&self) -> &str {
238		match self {
239			StructError::OutOfBounds => "rustmex:structs:out_of_bounds",
240			StructError::NotAField => "rustmex:structs:not_a_field",
241		}
242	}
243}
244
245impl Display for StructError {
246	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247		let s = match self {
248			StructError::OutOfBounds => "Index is out of bounds",
249			StructError::NotAField => "Field does not exist",
250		};
251		write!(f, "{}", s)
252	}
253}
254
255// NOTE: Objects are only really supported on platforms other than GNU/Octave
256use crate::objects::ObjectError;
257
258impl MexMessage for ObjectError {
259	fn id(&self) -> &str {
260		match self {
261			ObjectError::OutOfBounds => "rustmex:objects:out_of_bounds",
262			ObjectError::NotAccessible => "rustmex:objects:not_accessible",
263		}
264	}
265}
266
267impl Display for ObjectError {
268	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269		let s = match self {
270			ObjectError::OutOfBounds => "Index is out of bounds",
271			ObjectError::NotAccessible => "Property is not accessible",
272		};
273		write!(f, "{}", s)
274	}
275}