Skip to main content

cityjson_lib_ffi_core/
error.rs

1use std::cell::RefCell;
2use std::ffi::c_char;
3use std::panic::{AssertUnwindSafe, UnwindSafe, catch_unwind};
4use std::ptr;
5
6use crate::abi::{cj_error_kind_t, cj_status_t};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct AbiError {
10    pub status: cj_status_t,
11    pub kind: cj_error_kind_t,
12    pub message: String,
13}
14
15impl AbiError {
16    pub fn new(status: cj_status_t, kind: cj_error_kind_t, message: impl Into<String>) -> Self {
17        Self {
18            status,
19            kind,
20            message: message.into(),
21        }
22    }
23
24    pub fn invalid_argument(message: impl Into<String>) -> Self {
25        Self::new(
26            cj_status_t::CJ_STATUS_INVALID_ARGUMENT,
27            cj_error_kind_t::CJ_ERROR_KIND_INVALID_ARGUMENT,
28            message,
29        )
30    }
31
32    pub fn internal(message: impl Into<String>) -> Self {
33        Self::new(
34            cj_status_t::CJ_STATUS_INTERNAL,
35            cj_error_kind_t::CJ_ERROR_KIND_INTERNAL,
36            message,
37        )
38    }
39}
40
41impl From<&cityjson_lib::Error> for AbiError {
42    fn from(error: &cityjson_lib::Error) -> Self {
43        match error {
44            cityjson_lib::Error::Io(inner) => Self::new(
45                cj_status_t::CJ_STATUS_IO,
46                cj_error_kind_t::CJ_ERROR_KIND_IO,
47                inner.to_string(),
48            ),
49            cityjson_lib::Error::Syntax(inner) => Self::new(
50                cj_status_t::CJ_STATUS_SYNTAX,
51                cj_error_kind_t::CJ_ERROR_KIND_SYNTAX,
52                inner.clone(),
53            ),
54            cityjson_lib::Error::CityJSON(inner) => Self::new(
55                cj_status_t::CJ_STATUS_MODEL,
56                cj_error_kind_t::CJ_ERROR_KIND_MODEL,
57                inner.to_string(),
58            ),
59            cityjson_lib::Error::MissingVersion => Self::new(
60                cj_status_t::CJ_STATUS_VERSION,
61                cj_error_kind_t::CJ_ERROR_KIND_VERSION,
62                error.to_string(),
63            ),
64            cityjson_lib::Error::ExpectedCityJSON(_)
65            | cityjson_lib::Error::ExpectedCityJSONFeature(_) => Self::new(
66                cj_status_t::CJ_STATUS_SHAPE,
67                cj_error_kind_t::CJ_ERROR_KIND_SHAPE,
68                error.to_string(),
69            ),
70            cityjson_lib::Error::UnsupportedType(_) => Self::new(
71                cj_status_t::CJ_STATUS_UNSUPPORTED,
72                cj_error_kind_t::CJ_ERROR_KIND_UNSUPPORTED,
73                error.to_string(),
74            ),
75            cityjson_lib::Error::UnsupportedVersion { .. } => Self::new(
76                cj_status_t::CJ_STATUS_VERSION,
77                cj_error_kind_t::CJ_ERROR_KIND_VERSION,
78                error.to_string(),
79            ),
80            cityjson_lib::Error::Streaming(_) => Self::new(
81                cj_status_t::CJ_STATUS_SHAPE,
82                cj_error_kind_t::CJ_ERROR_KIND_SHAPE,
83                error.to_string(),
84            ),
85            cityjson_lib::Error::Import(_) => Self::new(
86                cj_status_t::CJ_STATUS_MODEL,
87                cj_error_kind_t::CJ_ERROR_KIND_MODEL,
88                error.to_string(),
89            ),
90            cityjson_lib::Error::UnsupportedFeature(_) => Self::new(
91                cj_status_t::CJ_STATUS_UNSUPPORTED,
92                cj_error_kind_t::CJ_ERROR_KIND_UNSUPPORTED,
93                error.to_string(),
94            ),
95        }
96    }
97}
98
99impl From<cityjson_lib::Error> for AbiError {
100    fn from(error: cityjson_lib::Error) -> Self {
101        Self::from(&error)
102    }
103}
104
105impl From<cityjson_lib::cityjson_types::error::Error> for AbiError {
106    fn from(error: cityjson_lib::cityjson_types::error::Error) -> Self {
107        Self::from(cityjson_lib::Error::from(error))
108    }
109}
110
111impl From<cityjson_lib::ErrorKind> for cj_error_kind_t {
112    fn from(value: cityjson_lib::ErrorKind) -> Self {
113        match value {
114            cityjson_lib::ErrorKind::Io => Self::CJ_ERROR_KIND_IO,
115            cityjson_lib::ErrorKind::Syntax => Self::CJ_ERROR_KIND_SYNTAX,
116            cityjson_lib::ErrorKind::Version => Self::CJ_ERROR_KIND_VERSION,
117            cityjson_lib::ErrorKind::Shape => Self::CJ_ERROR_KIND_SHAPE,
118            cityjson_lib::ErrorKind::Unsupported => Self::CJ_ERROR_KIND_UNSUPPORTED,
119            cityjson_lib::ErrorKind::Model => Self::CJ_ERROR_KIND_MODEL,
120        }
121    }
122}
123
124impl From<cityjson_lib::ErrorKind> for cj_status_t {
125    fn from(value: cityjson_lib::ErrorKind) -> Self {
126        match value {
127            cityjson_lib::ErrorKind::Io => Self::CJ_STATUS_IO,
128            cityjson_lib::ErrorKind::Syntax => Self::CJ_STATUS_SYNTAX,
129            cityjson_lib::ErrorKind::Version => Self::CJ_STATUS_VERSION,
130            cityjson_lib::ErrorKind::Shape => Self::CJ_STATUS_SHAPE,
131            cityjson_lib::ErrorKind::Unsupported => Self::CJ_STATUS_UNSUPPORTED,
132            cityjson_lib::ErrorKind::Model => Self::CJ_STATUS_MODEL,
133        }
134    }
135}
136
137#[derive(Debug, Clone)]
138struct LastError {
139    status: cj_status_t,
140    kind: cj_error_kind_t,
141    message: String,
142}
143
144impl LastError {
145    fn empty() -> Self {
146        Self {
147            status: cj_status_t::CJ_STATUS_SUCCESS,
148            kind: cj_error_kind_t::CJ_ERROR_KIND_NONE,
149            message: String::new(),
150        }
151    }
152}
153
154thread_local! {
155    static LAST_ERROR: RefCell<LastError> = RefCell::new(LastError::empty());
156}
157
158pub fn clear_last_error() {
159    LAST_ERROR.with(|cell| {
160        *cell.borrow_mut() = LastError::empty();
161    });
162}
163
164pub fn set_last_error(error: AbiError) {
165    LAST_ERROR.with(|cell| {
166        *cell.borrow_mut() = LastError {
167            status: error.status,
168            kind: error.kind,
169            message: error.message,
170        };
171    });
172}
173
174pub fn set_last_error_from_cityjson_lib_error(error: cityjson_lib::Error) -> cj_status_t {
175    let abi_error = AbiError::from(error);
176    let status = abi_error.status;
177    set_last_error(abi_error);
178    status
179}
180
181pub fn last_error_kind() -> cj_error_kind_t {
182    LAST_ERROR.with(|cell| cell.borrow().kind)
183}
184
185pub fn last_error_status() -> cj_status_t {
186    LAST_ERROR.with(|cell| cell.borrow().status)
187}
188
189pub fn last_error_message_len() -> usize {
190    LAST_ERROR.with(|cell| cell.borrow().message.len())
191}
192
193pub unsafe fn copy_last_error_message(
194    buffer: *mut c_char,
195    capacity: usize,
196    out_len: *mut usize,
197) -> cj_status_t {
198    if out_len.is_null() {
199        return cj_status_t::CJ_STATUS_INVALID_ARGUMENT;
200    }
201
202    let (message_len, message) = LAST_ERROR.with(|cell| {
203        let borrowed = cell.borrow();
204        (borrowed.message.len(), borrowed.message.clone())
205    });
206
207    unsafe {
208        ptr::write(out_len, message_len);
209    }
210
211    if capacity == 0 {
212        if buffer.is_null() {
213            return cj_status_t::CJ_STATUS_SUCCESS;
214        }
215
216        return cj_status_t::CJ_STATUS_INVALID_ARGUMENT;
217    }
218
219    if buffer.is_null() {
220        return cj_status_t::CJ_STATUS_INVALID_ARGUMENT;
221    }
222
223    let available = capacity.saturating_sub(1);
224    let copy_len = message_len.min(available);
225    if copy_len > 0 {
226        unsafe {
227            ptr::copy_nonoverlapping(message.as_ptr().cast::<c_char>(), buffer, copy_len);
228        }
229    }
230    unsafe {
231        *buffer.add(copy_len) = 0;
232    }
233
234    if message_len >= capacity {
235        return cj_status_t::CJ_STATUS_INVALID_ARGUMENT;
236    }
237
238    cj_status_t::CJ_STATUS_SUCCESS
239}
240
241pub fn run_ffi<T, E, F>(f: F) -> Result<T, cj_status_t>
242where
243    E: Into<AbiError>,
244    F: FnOnce() -> Result<T, E> + UnwindSafe,
245{
246    match catch_unwind(AssertUnwindSafe(f)) {
247        Ok(Ok(value)) => {
248            clear_last_error();
249            Ok(value)
250        }
251        Ok(Err(error)) => {
252            let abi_error = error.into();
253            let status = abi_error.status;
254            set_last_error(abi_error);
255            Err(status)
256        }
257        Err(_) => {
258            let abi_error = AbiError::internal("panic across the C ABI boundary");
259            let status = abi_error.status;
260            set_last_error(abi_error);
261            Err(status)
262        }
263    }
264}