1use std::cell::Cell;
2use std::fmt;
3use std::marker::PhantomData;
4use std::os::raw::{c_char, c_void};
5use std::panic::{AssertUnwindSafe, catch_unwind};
6use std::ptr;
7use std::sync::Arc;
8
9use maplibre_native_ffi_core as maplibre_core;
10use maplibre_native_ffi_sys as sys;
11
12use crate::Result;
13
14pub use maplibre_core::resource::{
15 ByteRange, HttpHeader, HttpHeaderTransformRequest, ResourceProviderDecision, ResourceRequest,
16 ResourceResponse, ResourceTransformRequest,
17};
18
19use maplibre_core::resource::{
20 ResourceRequestHandleFns, ResourceRequestHandleState, UNKNOWN_PROVIDER_DECISION,
21};
22
23pub struct ResourceRequestHandle {
29 state: Arc<ResourceRequestHandleState>,
30 _not_sync: PhantomData<Cell<()>>,
31}
32
33impl fmt::Debug for ResourceRequestHandle {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.debug_struct("ResourceRequestHandle")
36 .finish_non_exhaustive()
37 }
38}
39
40impl ResourceRequestHandle {
41 fn from_state(state: Arc<ResourceRequestHandleState>) -> Self {
42 Self {
43 state,
44 _not_sync: PhantomData,
45 }
46 }
47
48 fn from_raw_with_fns(
49 handle: sys::mln_resource_request_handle,
50 fns: ResourceRequestHandleFns,
51 ) -> Result<Self> {
52 unsafe { ResourceRequestHandleState::new(handle, fns) }.map(Self::from_state)
55 }
56
57 pub fn complete(&self, response: ResourceResponse) -> Result<()> {
65 self.state.complete(&response)
66 }
67
68 pub fn is_cancelled(&self) -> Result<bool> {
70 self.state.is_cancelled()
71 }
72
73 pub fn set_cancel_callback<F>(&self, callback: F) -> Result<()>
94 where
95 F: FnOnce() + Send + 'static,
96 {
97 self.state.set_cancel_callback(Box::new(callback))
98 }
99
100 pub fn close(self) {
104 self.state.close();
105 }
106}
107
108impl Drop for ResourceRequestHandle {
109 fn drop(&mut self) {
110 self.state.close();
111 }
112}
113
114type ResourceProviderCallback = dyn Fn(ResourceRequest, ResourceRequestHandle) -> ResourceProviderDecision
115 + Send
116 + Sync
117 + 'static;
118
119pub(crate) struct ResourceProviderState {
120 callback: Box<ResourceProviderCallback>,
121 handle_fns: ResourceRequestHandleFns,
122}
123
124impl fmt::Debug for ResourceProviderState {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 f.debug_struct("ResourceProviderState")
127 .finish_non_exhaustive()
128 }
129}
130
131impl ResourceProviderState {
132 pub(crate) fn new<F>(callback: F) -> Box<Self>
133 where
134 F: Fn(ResourceRequest, ResourceRequestHandle) -> ResourceProviderDecision
135 + Send
136 + Sync
137 + 'static,
138 {
139 Box::new(Self {
140 callback: Box::new(callback),
141 handle_fns: ResourceRequestHandleFns::NATIVE,
142 })
143 }
144
145 pub(crate) fn descriptor(&self) -> sys::mln_resource_provider {
146 maplibre_core::resource::resource_provider_descriptor(
147 Some(resource_provider_trampoline),
148 ptr::from_ref(self).cast_mut().cast::<c_void>(),
149 )
150 }
151
152 fn invoke(
153 &self,
154 request: *const sys::mln_resource_request,
155 handle: sys::mln_resource_request_handle,
156 ) -> u32 {
157 let Some(raw_request) = ptr::NonNull::new(request.cast_mut()) else {
158 return UNKNOWN_PROVIDER_DECISION;
159 };
160 let handle = match ResourceRequestHandle::from_raw_with_fns(handle, self.handle_fns) {
161 Ok(handle) => handle,
162 Err(_) => return UNKNOWN_PROVIDER_DECISION,
163 };
164 let state = Arc::clone(&handle.state);
165
166 let request =
168 match unsafe { maplibre_core::resource::copy_resource_request(raw_request.as_ref()) } {
169 Ok(request) => request,
170 Err(_) => return state.finish_provider_exception(),
171 };
172
173 match catch_unwind(AssertUnwindSafe(|| (self.callback)(request, handle))) {
174 Ok(decision) => state.finish_provider_decision(decision),
175 Err(_) => state.finish_provider_exception(),
176 }
177 }
178}
179
180unsafe extern "C" fn resource_provider_trampoline(
181 user_data: *mut c_void,
182 request: *const sys::mln_resource_request,
183 handle: sys::mln_resource_request_handle,
184) -> u32 {
185 let Some(state) = ptr::NonNull::new(user_data.cast::<ResourceProviderState>()) else {
186 return UNKNOWN_PROVIDER_DECISION;
187 };
188 unsafe { state.as_ref() }.invoke(request, handle)
192}
193
194type ResourceTransformCallback =
195 dyn Fn(ResourceTransformRequest) -> Option<String> + Send + Sync + 'static;
196
197pub(crate) struct ResourceTransformState {
198 callback: Box<ResourceTransformCallback>,
199}
200
201impl fmt::Debug for ResourceTransformState {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 f.debug_struct("ResourceTransformState")
204 .finish_non_exhaustive()
205 }
206}
207
208impl ResourceTransformState {
209 pub(crate) fn new<F>(callback: F) -> Box<Self>
210 where
211 F: Fn(ResourceTransformRequest) -> Option<String> + Send + Sync + 'static,
212 {
213 Box::new(Self {
214 callback: Box::new(callback),
215 })
216 }
217
218 pub(crate) fn descriptor(&self) -> sys::mln_resource_transform {
219 maplibre_core::resource::resource_transform_descriptor(
220 Some(resource_transform_trampoline),
221 ptr::from_ref(self).cast_mut().cast::<c_void>(),
222 )
223 }
224
225 fn invoke(
226 &self,
227 raw_kind: u32,
228 url: *const c_char,
229 out_response: *mut sys::mln_resource_transform_response,
230 ) -> sys::mln_status {
231 let status = unsafe {
234 maplibre_core::resource::initialize_resource_transform_response(out_response)
235 };
236 if status != sys::MLN_STATUS_OK {
237 return status;
238 }
239
240 let request = match unsafe {
242 maplibre_core::resource::copy_resource_transform_request(raw_kind, url)
243 } {
244 Ok(request) => request,
245 Err(error) => return maplibre_core::resource::status_for_error(&error),
246 };
247
248 let replacement = match catch_unwind(AssertUnwindSafe(|| (self.callback)(request))) {
249 Ok(replacement) => replacement,
250 Err(_) => return sys::MLN_STATUS_NATIVE_ERROR,
251 };
252
253 match replacement {
254 Some(replacement) if !replacement.is_empty() => {
255 unsafe {
260 sys::mln_resource_transform_response_set_url(
261 out_response,
262 replacement.as_ptr().cast(),
263 replacement.len(),
264 )
265 }
266 }
267 _ => sys::MLN_STATUS_OK,
268 }
269 }
270}
271
272unsafe extern "C" fn resource_transform_trampoline(
273 user_data: *mut c_void,
274 kind: u32,
275 url: *const c_char,
276 out_response: *mut sys::mln_resource_transform_response,
277) -> sys::mln_status {
278 let Some(state) = ptr::NonNull::new(user_data.cast::<ResourceTransformState>()) else {
279 return sys::MLN_STATUS_INVALID_ARGUMENT;
280 };
281 unsafe { state.as_ref() }.invoke(kind, url, out_response)
285}
286
287type HttpHeaderTransformCallback =
288 dyn Fn(HttpHeaderTransformRequest) -> Vec<HttpHeader> + Send + Sync + 'static;
289
290pub(crate) struct HttpHeaderTransformState {
291 callback: Box<HttpHeaderTransformCallback>,
292}
293
294impl fmt::Debug for HttpHeaderTransformState {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 f.debug_struct("HttpHeaderTransformState")
297 .finish_non_exhaustive()
298 }
299}
300
301impl HttpHeaderTransformState {
302 pub(crate) fn new<F>(callback: F) -> Box<Self>
303 where
304 F: Fn(HttpHeaderTransformRequest) -> Vec<HttpHeader> + Send + Sync + 'static,
305 {
306 Box::new(Self {
307 callback: Box::new(callback),
308 })
309 }
310
311 pub(crate) fn descriptor(&self) -> sys::mln_http_header_transform {
312 maplibre_core::resource::http_header_transform_descriptor(
313 Some(http_header_transform_trampoline),
314 ptr::from_ref(self).cast_mut().cast::<c_void>(),
315 )
316 }
317
318 fn invoke(
319 &self,
320 raw_kind: u32,
321 url: *const c_char,
322 out_response: *mut sys::mln_http_header_transform_response,
323 ) -> sys::mln_status {
324 let status = unsafe {
326 maplibre_core::resource::initialize_http_header_transform_response(out_response)
327 };
328 if status != sys::MLN_STATUS_OK {
329 return status;
330 }
331 let request = match unsafe {
333 maplibre_core::resource::copy_http_header_transform_request(raw_kind, url)
334 } {
335 Ok(request) => request,
336 Err(error) => return maplibre_core::resource::status_for_error(&error),
337 };
338 let headers = match catch_unwind(AssertUnwindSafe(|| (self.callback)(request))) {
339 Ok(headers) => headers,
340 Err(_) => return sys::MLN_STATUS_NATIVE_ERROR,
341 };
342
343 let mut names = Vec::<String>::with_capacity(headers.len());
344 for header in headers {
345 if names
346 .iter()
347 .any(|name| name.eq_ignore_ascii_case(&header.name))
348 {
349 return sys::MLN_STATUS_INVALID_ARGUMENT;
350 }
351 names.push(header.name.clone());
352 let status = unsafe {
354 sys::mln_http_header_transform_response_set(
355 out_response,
356 header.name.as_ptr().cast(),
357 header.name.len(),
358 header.value.as_ptr().cast(),
359 header.value.len(),
360 )
361 };
362 if status != sys::MLN_STATUS_OK {
363 return status;
364 }
365 }
366 sys::MLN_STATUS_OK
367 }
368}
369
370unsafe extern "C" fn http_header_transform_trampoline(
371 user_data: *mut c_void,
372 kind: u32,
373 url: *const c_char,
374 out_response: *mut sys::mln_http_header_transform_response,
375) -> sys::mln_status {
376 let Some(state) = ptr::NonNull::new(user_data.cast::<HttpHeaderTransformState>()) else {
377 return sys::MLN_STATUS_INVALID_ARGUMENT;
378 };
379 unsafe { state.as_ref() }.invoke(kind, url, out_response)
381}
382
383#[cfg(test)]
384mod tests {
385 use std::ffi::CString;
386 use std::sync::Mutex as StdMutex;
387 use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
388
389 use static_assertions::{assert_impl_all, assert_not_impl_any};
390
391 use super::*;
392 use crate::{
393 ErrorKind, ResourceKind, ResourceLoadingMethod, ResourcePriority, ResourceStoragePolicy,
394 ResourceUsage,
395 };
396
397 static FAKE_HANDLE_TEST_LOCK: StdMutex<()> = StdMutex::new(());
398 static COMPLETE_COUNT: AtomicUsize = AtomicUsize::new(0);
399 static CANCELLED_COUNT: AtomicUsize = AtomicUsize::new(0);
400 static RELEASE_COUNT: AtomicUsize = AtomicUsize::new(0);
401 static SET_CANCEL_COUNT: AtomicUsize = AtomicUsize::new(0);
402 static CANCELLED_VALUE: AtomicBool = AtomicBool::new(false);
403 static COMPLETE_STATUS: AtomicI32 = AtomicI32::new(sys::MLN_STATUS_OK);
404
405 unsafe extern "C" fn fake_complete(
406 _handle: sys::mln_resource_request_handle,
407 _response: *const sys::mln_resource_response,
408 ) -> sys::mln_status {
409 COMPLETE_COUNT.fetch_add(1, Ordering::SeqCst);
410 COMPLETE_STATUS.load(Ordering::SeqCst)
411 }
412
413 unsafe extern "C" fn fake_cancelled(
414 _handle: sys::mln_resource_request_handle,
415 out_cancelled: *mut bool,
416 ) -> sys::mln_status {
417 CANCELLED_COUNT.fetch_add(1, Ordering::SeqCst);
418 if out_cancelled.is_null() {
419 return sys::MLN_STATUS_INVALID_ARGUMENT;
420 }
421 unsafe { *out_cancelled = CANCELLED_VALUE.load(Ordering::SeqCst) };
423 sys::MLN_STATUS_OK
424 }
425
426 unsafe extern "C" fn fake_release(_handle: sys::mln_resource_request_handle) {
427 RELEASE_COUNT.fetch_add(1, Ordering::SeqCst);
428 }
429
430 unsafe extern "C" fn fake_set_cancel_callback(
433 _handle: sys::mln_resource_request_handle,
434 callback: sys::mln_resource_request_cancel_callback,
435 _user_data: *mut c_void,
436 out_cancelled: *mut bool,
437 ) -> sys::mln_status {
438 SET_CANCEL_COUNT.fetch_add(1, Ordering::SeqCst);
439 if callback.is_none() || out_cancelled.is_null() {
440 return sys::MLN_STATUS_INVALID_ARGUMENT;
441 }
442 unsafe { *out_cancelled = CANCELLED_VALUE.load(Ordering::SeqCst) };
444 sys::MLN_STATUS_OK
445 }
446
447 fn fake_fns() -> ResourceRequestHandleFns {
448 unsafe {
451 ResourceRequestHandleFns::new(
452 fake_complete,
453 fake_cancelled,
454 fake_set_cancel_callback,
455 fake_release,
456 )
457 }
458 }
459
460 fn reset_fake_handle_state() {
461 COMPLETE_COUNT.store(0, Ordering::SeqCst);
462 CANCELLED_COUNT.store(0, Ordering::SeqCst);
463 RELEASE_COUNT.store(0, Ordering::SeqCst);
464 SET_CANCEL_COUNT.store(0, Ordering::SeqCst);
465 CANCELLED_VALUE.store(false, Ordering::SeqCst);
466 COMPLETE_STATUS.store(sys::MLN_STATUS_OK, Ordering::SeqCst);
467 }
468
469 fn fake_handle() -> ResourceRequestHandle {
470 reset_fake_handle_state();
471 ResourceRequestHandle::from_raw_with_fns(
474 sys::mln_resource_request_handle(0x0c00_0000_0000_0034),
475 fake_fns(),
476 )
477 .unwrap()
478 }
479
480 fn request() -> sys::mln_resource_request {
481 sys::mln_resource_request {
482 size: std::mem::size_of::<sys::mln_resource_request>() as u32,
483 requested_url: c"maplibre://tiles/2/1/1.pbf".as_ptr(),
484 resolved_url: c"https://example.test/tile.pbf".as_ptr(),
485 kind: sys::MLN_RESOURCE_KIND_TILE,
486 loading_method: sys::MLN_RESOURCE_LOADING_METHOD_NETWORK_ONLY,
487 priority: sys::MLN_RESOURCE_PRIORITY_LOW,
488 usage: sys::MLN_RESOURCE_USAGE_OFFLINE,
489 storage_policy: sys::MLN_RESOURCE_STORAGE_POLICY_VOLATILE,
490 has_range: true,
491 range_start: 7,
492 range_end: 11,
493 has_prior_modified: true,
494 prior_modified_unix_ms: 123,
495 has_prior_expires: true,
496 prior_expires_unix_ms: 456,
497 prior_etag: c"etag".as_ptr(),
498 prior_data: [1u8, 2, 3].as_ptr(),
499 prior_data_size: 3,
500 }
501 }
502
503 fn response() -> sys::mln_resource_transform_response {
504 sys::mln_resource_transform_response {
505 size: std::mem::size_of::<sys::mln_resource_transform_response>() as u32,
506 url: ptr::null(),
507 context: ptr::null_mut(),
508 }
509 }
510
511 #[test]
512 fn resource_request_handle_is_send_but_not_sync() {
515 assert_impl_all!(ResourceRequestHandle: Send);
516 assert_not_impl_any!(ResourceRequestHandle: Sync);
517 }
518
519 #[test]
520 fn provider_callback_copies_request_and_pass_through_does_not_release() {
522 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
523 reset_fake_handle_state();
524 let state = ResourceProviderState {
525 callback: Box::new(|request, handle| {
526 assert_eq!(request.requested_url, "maplibre://tiles/2/1/1.pbf");
527 assert_eq!(request.resolved_url, "https://example.test/tile.pbf");
528 assert_eq!(request.kind, ResourceKind::Tile);
529 assert_eq!(request.raw_kind, sys::MLN_RESOURCE_KIND_TILE);
530 assert_eq!(request.loading_method, ResourceLoadingMethod::NetworkOnly);
531 assert_eq!(request.priority, ResourcePriority::Low);
532 assert_eq!(request.usage, ResourceUsage::Offline);
533 assert_eq!(request.storage_policy, ResourceStoragePolicy::Volatile);
534 let range = request.range.unwrap();
535 assert_eq!((range.start, range.end), (7, 11));
536 assert_eq!(request.prior_modified_unix_ms, Some(123));
537 assert_eq!(request.prior_expires_unix_ms, Some(456));
538 assert_eq!(request.prior_etag.as_deref(), Some("etag"));
539 assert_eq!(request.prior_data, vec![1, 2, 3]);
540 drop(handle);
541 ResourceProviderDecision::PassThrough
542 }),
543 handle_fns: fake_fns(),
544 };
545 let raw_request = request();
546
547 let decision = state.invoke(
548 &raw_request,
549 sys::mln_resource_request_handle(0x0c00_0000_0000_0034),
550 );
551
552 assert_eq!(decision, sys::MLN_RESOURCE_PROVIDER_DECISION_PASS_THROUGH);
553 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
554 }
555
556 #[test]
557 fn retained_pass_through_request_handle_is_stale_after_callback() {
559 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
560 reset_fake_handle_state();
561 let (sender, receiver) = std::sync::mpsc::channel();
562 let state = ResourceProviderState {
563 callback: Box::new(move |_, handle| {
564 sender.send(handle).unwrap();
565 ResourceProviderDecision::PassThrough
566 }),
567 handle_fns: fake_fns(),
568 };
569 let raw_request = request();
570
571 let decision = state.invoke(
572 &raw_request,
573 sys::mln_resource_request_handle(0x0c00_0000_0000_0034),
574 );
575 let handle = receiver.recv().unwrap();
576
577 assert_eq!(decision, sys::MLN_RESOURCE_PROVIDER_DECISION_PASS_THROUGH);
578 let error = handle.is_cancelled().unwrap_err();
579 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
580 let error = handle.complete(ResourceResponse::no_content()).unwrap_err();
581 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
582 handle.close();
583 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 0);
584 assert_eq!(CANCELLED_COUNT.load(Ordering::SeqCst), 0);
585 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
586 }
587
588 #[test]
589 fn inline_completion_returns_handle_and_releases_once() {
591 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
592 reset_fake_handle_state();
593 let state = ResourceProviderState {
594 callback: Box::new(|_, handle| {
595 handle.complete(ResourceResponse::ok([1, 2, 3])).unwrap();
596 ResourceProviderDecision::PassThrough
597 }),
598 handle_fns: fake_fns(),
599 };
600 let raw_request = request();
601
602 let decision = state.invoke(
603 &raw_request,
604 sys::mln_resource_request_handle(0x0c00_0000_0000_0034),
605 );
606
607 assert_eq!(decision, sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE);
608 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
609 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
610 }
611
612 #[test]
613 fn deferred_completion_from_another_thread_releases_once() {
615 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
616 let handle = fake_handle();
617 assert_eq!(
618 handle
619 .state
620 .finish_provider_decision(ResourceProviderDecision::Handle),
621 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
622 );
623
624 let thread = std::thread::spawn(move || {
625 handle
626 .complete(ResourceResponse::ok(vec![4, 5, 6]))
627 .unwrap();
628 });
629 thread.join().unwrap();
630
631 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
632 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
633 }
634
635 #[test]
636 fn failed_completion_is_terminal_after_reaching_c() {
638 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
639 let handle = fake_handle();
640 assert_eq!(
641 handle
642 .state
643 .finish_provider_decision(ResourceProviderDecision::Handle),
644 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
645 );
646 COMPLETE_STATUS.store(sys::MLN_STATUS_INVALID_STATE, Ordering::SeqCst);
647
648 let error = handle
649 .complete(ResourceResponse::ok(Vec::new()))
650 .unwrap_err();
651
652 assert_eq!(error.kind(), ErrorKind::InvalidState);
653 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
654 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
655 let error = handle.complete(ResourceResponse::no_content()).unwrap_err();
656 assert_eq!(error.kind(), ErrorKind::InvalidState);
657 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
658 }
659
660 #[test]
661 fn validation_failure_before_completion_keeps_handle_retryable() {
663 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
664 let handle = fake_handle();
665 assert_eq!(
666 handle
667 .state
668 .finish_provider_decision(ResourceProviderDecision::Handle),
669 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
670 );
671
672 let error = handle
673 .complete(ResourceResponse::error(
674 crate::ResourceErrorReason::Other,
675 "bad\0message",
676 ))
677 .unwrap_err();
678
679 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
680 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 0);
681 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
682 handle.complete(ResourceResponse::no_content()).unwrap();
683 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
684 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
685 }
686
687 #[test]
688 fn drop_releases_uncompleted_handled_request_once() {
690 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
691 let handle = fake_handle();
692 assert_eq!(
693 handle
694 .state
695 .finish_provider_decision(ResourceProviderDecision::Handle),
696 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
697 );
698 drop(handle);
699
700 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
701 }
702
703 #[test]
704 fn close_before_handle_decision_releases_after_decision() {
706 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
707 let handle = fake_handle();
708 let state = Arc::clone(&handle.state);
709 handle.close();
710 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
711 assert_eq!(
712 state.finish_provider_decision(ResourceProviderDecision::Handle),
713 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
714 );
715
716 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
717 }
718
719 #[test]
720 fn cancelled_queries_use_native_function_until_closed() {
722 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
723 let handle = fake_handle();
724 CANCELLED_VALUE.store(true, Ordering::SeqCst);
725
726 assert!(handle.is_cancelled().unwrap());
727 handle.close();
728 }
729
730 #[test]
731 fn late_completion_after_observed_cancellation_maps_native_status_and_closes() {
733 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
734 let handle = fake_handle();
735 assert_eq!(
736 handle
737 .state
738 .finish_provider_decision(ResourceProviderDecision::Handle),
739 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
740 );
741 CANCELLED_VALUE.store(true, Ordering::SeqCst);
742
743 assert!(handle.is_cancelled().unwrap());
744 COMPLETE_STATUS.store(sys::MLN_STATUS_INVALID_STATE, Ordering::SeqCst);
745 let error = handle.complete(ResourceResponse::no_content()).unwrap_err();
746
747 assert_eq!(error.kind(), ErrorKind::InvalidState);
748 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
749 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
750 let error = handle.complete(ResourceResponse::no_content()).unwrap_err();
751 assert_eq!(error.kind(), ErrorKind::InvalidState);
752 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
753 }
754
755 #[test]
756 fn provider_panics_produce_unknown_decision_without_unwinding() {
758 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
759 reset_fake_handle_state();
760 let state = ResourceProviderState {
761 callback: Box::new(|_, _| panic!("boom")),
762 handle_fns: fake_fns(),
763 };
764 let raw_request = request();
765
766 let decision = state.invoke(
767 &raw_request,
768 sys::mln_resource_request_handle(0x0c00_0000_0000_0034),
769 );
770
771 assert_eq!(decision, UNKNOWN_PROVIDER_DECISION);
772 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
773 }
774
775 #[test]
776 fn stale_request_rejects_cancel_registration_as_closed() {
778 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
779 reset_fake_handle_state();
780 let (sender, receiver) = std::sync::mpsc::channel();
781 let state = ResourceProviderState {
782 callback: Box::new(move |_, handle| {
783 sender.send(handle).unwrap();
784 ResourceProviderDecision::PassThrough
785 }),
786 handle_fns: fake_fns(),
787 };
788 let raw_request = request();
789 let decision = state.invoke(
790 &raw_request,
791 sys::mln_resource_request_handle(0x0c00_0000_0000_0034),
792 );
793 let handle = receiver.recv().unwrap();
794 assert_eq!(decision, sys::MLN_RESOURCE_PROVIDER_DECISION_PASS_THROUGH);
795
796 let error = handle.set_cancel_callback(|| {}).unwrap_err();
797
798 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
799 assert_eq!(SET_CANCEL_COUNT.load(Ordering::SeqCst), 0);
800 }
801
802 #[test]
803 fn cancel_callback_panic_does_not_unwind_into_c() {
806 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
807 let handle = fake_handle();
808 assert_eq!(
809 handle
810 .state
811 .finish_provider_decision(ResourceProviderDecision::Handle),
812 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
813 );
814 CANCELLED_VALUE.store(true, Ordering::SeqCst);
817 let calls = Arc::new(AtomicUsize::new(0));
818 let callback_calls = Arc::clone(&calls);
819
820 handle
821 .set_cancel_callback(move || {
822 callback_calls.fetch_add(1, Ordering::SeqCst);
823 panic!("boom");
824 })
825 .unwrap();
826
827 assert_eq!(calls.load(Ordering::SeqCst), 1);
828 handle.close();
829 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
830 }
831
832 #[test]
833 fn transform_callback_copies_request_when_keeping_original_url() {
835 let state = ResourceTransformState::new(|request| {
836 assert_eq!(request.kind, ResourceKind::Style);
837 assert_eq!(request.raw_kind, sys::MLN_RESOURCE_KIND_STYLE);
838 assert_eq!(request.url, "https://example.test/style.json");
839 None
840 });
841 let descriptor = state.descriptor();
842 let callback = descriptor.callback.unwrap();
843 let url = CString::new("https://example.test/style.json").unwrap();
844 let mut response = response();
845
846 let status = unsafe {
847 callback(
848 descriptor.user_data,
849 sys::MLN_RESOURCE_KIND_STYLE,
850 url.as_ptr(),
851 &mut response,
852 )
853 };
854
855 assert_eq!(status, sys::MLN_STATUS_OK);
856 assert!(response.url.is_null());
857 }
858
859 #[test]
860 fn transform_callback_clears_stale_response_when_keeping_original_url() {
862 let state = ResourceTransformState::new(|_| None);
863 let descriptor = state.descriptor();
864 let callback = descriptor.callback.unwrap();
865 let url = CString::new("https://example.test/style.json").unwrap();
866 let stale = CString::new("https://stale.test/style.json").unwrap();
867 let mut response = response();
868 response.url = stale.as_ptr();
869
870 let status = unsafe {
871 callback(
872 descriptor.user_data,
873 sys::MLN_RESOURCE_KIND_STYLE,
874 url.as_ptr(),
875 &mut response,
876 )
877 };
878
879 assert_eq!(status, sys::MLN_STATUS_OK);
880 assert!(response.url.is_null());
881 }
882
883 #[test]
884 fn transform_callback_contains_panics() {
886 let state = ResourceTransformState::new(|_| panic!("boom"));
887 let descriptor = state.descriptor();
888 let callback = descriptor.callback.unwrap();
889 let url = CString::new("https://example.test/style.json").unwrap();
890 let mut response = response();
891
892 let status = unsafe {
893 callback(
894 descriptor.user_data,
895 sys::MLN_RESOURCE_KIND_STYLE,
896 url.as_ptr(),
897 &mut response,
898 )
899 };
900
901 assert_eq!(status, sys::MLN_STATUS_NATIVE_ERROR);
902 assert!(response.url.is_null());
903 }
904
905 #[test]
906 fn transform_callback_rejects_embedded_nul_replacements() {
908 let state = ResourceTransformState::new(|_| Some("https://example.test/\0bad".to_owned()));
909 let descriptor = state.descriptor();
910 let callback = descriptor.callback.unwrap();
911 let url = CString::new("https://example.test/style.json").unwrap();
912 let mut response = response();
913
914 let status = unsafe {
915 callback(
916 descriptor.user_data,
917 sys::MLN_RESOURCE_KIND_STYLE,
918 url.as_ptr(),
919 &mut response,
920 )
921 };
922
923 assert_eq!(status, sys::MLN_STATUS_INVALID_ARGUMENT);
924 assert!(response.url.is_null());
925 }
926
927 #[test]
928 fn transform_state_drops_callback_capture() {
931 let token = Arc::new(());
932 let callback_token = Arc::clone(&token);
933 let state = ResourceTransformState::new(move |_| {
934 let _ = &callback_token;
935 None
936 });
937 assert_eq!(Arc::strong_count(&token), 2);
938 drop(state);
939 assert_eq!(Arc::strong_count(&token), 1);
940 }
941
942 #[test]
943 fn callback_can_run_from_multiple_threads() {
945 let calls = Arc::new(AtomicUsize::new(0));
946 let callback_calls = Arc::clone(&calls);
947 let state = Arc::new(ResourceTransformState {
948 callback: Box::new(move |request| {
949 callback_calls.fetch_add(1, Ordering::SeqCst);
950 let _ = request;
951 None
952 }),
953 });
954
955 let handles = (0..2)
956 .map(|_| {
957 let state = Arc::clone(&state);
958 std::thread::spawn(move || {
959 let descriptor = state.descriptor();
960 let callback = descriptor.callback.unwrap();
961 let url = CString::new("https://example.test/tile").unwrap();
962 let mut response = response();
963 let status = unsafe {
964 callback(
965 descriptor.user_data,
966 sys::MLN_RESOURCE_KIND_TILE,
967 url.as_ptr(),
968 &mut response,
969 )
970 };
971 assert_eq!(status, sys::MLN_STATUS_OK);
972 assert!(response.url.is_null());
973 })
974 })
975 .collect::<Vec<_>>();
976
977 for handle in handles {
978 handle.join().unwrap();
979 }
980 assert_eq!(calls.load(Ordering::SeqCst), 2);
981 }
982}