maplibre_native_ffi/
error.rs1use std::fmt;
2use std::mem::MaybeUninit;
3
4use maplibre_native_ffi_sys as sys;
5
6pub type Result<T> = std::result::Result<T, Error>;
7
8use crate::Status;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Error {
13 Status {
15 status: Status,
16 diagnostic: String,
17 generation: u64,
20 },
21 AbiVersionMismatch {
22 expected: u32,
23 actual: u32,
24 },
25}
26
27impl Error {
28 pub fn new(status: Status, diagnostic: impl Into<String>) -> Self {
29 Self::Status {
30 status,
31 diagnostic: diagnostic.into(),
32 generation: 0,
33 }
34 }
35
36 pub fn from_status_and_diagnostic(status: i32, diagnostic: impl Into<String>) -> Self {
37 Self::new(Status::from_raw(status), diagnostic)
38 }
39
40 pub fn invalid_argument(diagnostic: impl Into<String>) -> Self {
41 Self::new(Status::InvalidArgument, diagnostic)
42 }
43
44 pub fn abi_version_mismatch(expected: u32, actual: u32) -> Self {
45 Self::AbiVersionMismatch { expected, actual }
46 }
47
48 pub fn status(&self) -> Status {
49 match self {
50 Self::Status { status, .. } => *status,
51 Self::AbiVersionMismatch { .. } => Status::Unsupported,
52 }
53 }
54
55 pub fn diagnostic(&self) -> &str {
56 match self {
57 Self::Status { diagnostic, .. } => diagnostic,
58 Self::AbiVersionMismatch { .. } => "C ABI version mismatch",
59 }
60 }
61
62 pub fn generation(&self) -> u64 {
65 match self {
66 Self::Status { generation, .. } => *generation,
67 Self::AbiVersionMismatch { .. } => 0,
68 }
69 }
70}
71
72impl fmt::Display for Error {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 match self {
75 Self::Status {
76 status, diagnostic, ..
77 } => write!(f, "{status}: {diagnostic}"),
78 Self::AbiVersionMismatch { expected, actual } => write!(
79 f,
80 "unsupported MapLibre Native C ABI version {actual}; expected {expected}"
81 ),
82 }
83 }
84}
85
86impl std::error::Error for Error {}
87
88pub struct ReleaseError<H> {
101 pub handle: H,
103 pub error: Error,
105}
106
107impl<H: fmt::Debug> fmt::Debug for ReleaseError<H> {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 f.debug_struct("ReleaseError")
110 .field("handle", &self.handle)
111 .field("error", &self.error)
112 .finish()
113 }
114}
115
116impl<H: fmt::Debug> fmt::Display for ReleaseError<H> {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "native refused to end {:?}", self.handle)
119 }
120}
121
122impl<H: fmt::Debug> std::error::Error for ReleaseError<H> {
123 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
124 Some(&self.error)
125 }
126}
127
128impl<H> From<ReleaseError<H>> for Error {
129 fn from(error: ReleaseError<H>) -> Self {
130 error.error
131 }
132}
133
134pub fn check(call: impl FnOnce(*mut sys::mln_diagnostic) -> sys::mln_status) -> Result<()> {
138 let mut diagnostic = MaybeUninit::<sys::mln_diagnostic>::uninit();
139 let raw = diagnostic.as_mut_ptr();
140 unsafe {
144 (&raw mut (*raw).size).write(std::mem::size_of::<sys::mln_diagnostic>() as u32);
145 (&raw mut (*raw).message).cast::<u8>().write(0);
146 }
147 let status = call(raw);
148 if status == sys::MLN_STATUS_OK {
149 return Ok(());
150 }
151 Err(Error::from_status_and_diagnostic(status, unsafe {
153 diagnostic_message(raw)
154 }))
155}
156
157unsafe fn diagnostic_message(diagnostic: *const sys::mln_diagnostic) -> String {
159 let message = unsafe { &raw const (*diagnostic).message }.cast::<u8>();
161 let capacity = crate::DIAGNOSTIC_MESSAGE_CAPACITY as usize;
162 let mut length = 0;
163 while length < capacity && unsafe { message.add(length).read() } != 0 {
166 length += 1;
167 }
168 String::from_utf8_lossy(unsafe { std::slice::from_raw_parts(message, length) }).into_owned()
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 fn failing_call(status: sys::mln_status, message: &[u8]) -> Error {
179 check(|diagnostic| {
180 unsafe {
183 let out = (&raw mut (*diagnostic).message).cast::<u8>();
184 std::ptr::copy_nonoverlapping(message.as_ptr(), out, message.len());
185 }
186 status
187 })
188 .unwrap_err()
189 }
190
191 #[test]
192 fn a_failed_call_carries_its_status_and_keeps_an_unknown_code() {
193 let error = failing_call(sys::MLN_STATUS_INVALID_STATE, b"failed\0");
194 assert_eq!(error.status(), Status::InvalidState);
195 assert_eq!(error.diagnostic(), "failed");
196 assert_eq!(error.to_string(), "INVALID_STATE: failed");
197
198 let error = failing_call(-123_456, b"future status\0");
199 assert_eq!(error.status(), Status::Unknown(-123_456));
200 assert_eq!(error.status().as_raw(), -123_456);
201 assert_eq!(error.diagnostic(), "future status");
202 assert_eq!(error.to_string(), "UNKNOWN(-123456): future status");
203 }
204
205 #[test]
206 fn a_diagnostic_is_bounded_by_its_capacity_and_empty_when_none_was_written() {
207 let capacity = crate::DIAGNOSTIC_MESSAGE_CAPACITY as usize;
208 let error = failing_call(sys::MLN_STATUS_INVALID_STATE, &vec![b'x'; capacity]);
209 assert_eq!(error.diagnostic(), "x".repeat(capacity));
210
211 let error = failing_call(sys::MLN_STATUS_INVALID_STATE, b"bad \xff byte\0");
212 assert_eq!(error.diagnostic(), "bad \u{fffd} byte");
213
214 let error = check(|_| sys::MLN_STATUS_INVALID_ARGUMENT).unwrap_err();
217 assert_eq!(error.status(), Status::InvalidArgument);
218 assert_eq!(error.diagnostic(), "");
219 }
220}