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}