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_core as maplibre_core;
10use maplibre_native_sys as sys;
11
12use crate::Result;
13
14pub use maplibre_core::resource::{
15 ByteRange, ResourceProviderDecision, ResourceRequest, ResourceResponse,
16 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: *mut 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<()> {
67 self.state.complete(&response)
68 }
69
70 pub fn is_cancelled(&self) -> Result<bool> {
72 self.state.is_cancelled()
73 }
74
75 pub fn close(self) {
77 self.state.close();
78 }
79}
80
81impl Drop for ResourceRequestHandle {
82 fn drop(&mut self) {
83 self.state.close();
84 }
85}
86
87type ResourceProviderCallback = dyn Fn(ResourceRequest, ResourceRequestHandle) -> ResourceProviderDecision
88 + Send
89 + Sync
90 + 'static;
91
92pub(crate) struct ResourceProviderState {
93 callback: Box<ResourceProviderCallback>,
94 handle_fns: ResourceRequestHandleFns,
95}
96
97impl fmt::Debug for ResourceProviderState {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 f.debug_struct("ResourceProviderState")
100 .finish_non_exhaustive()
101 }
102}
103
104impl ResourceProviderState {
105 pub(crate) fn new<F>(callback: F) -> Box<Self>
106 where
107 F: Fn(ResourceRequest, ResourceRequestHandle) -> ResourceProviderDecision
108 + Send
109 + Sync
110 + 'static,
111 {
112 Box::new(Self {
113 callback: Box::new(callback),
114 handle_fns: ResourceRequestHandleFns::NATIVE,
115 })
116 }
117
118 pub(crate) fn descriptor(&self) -> sys::mln_resource_provider {
119 maplibre_core::resource::resource_provider_descriptor(
120 Some(resource_provider_trampoline),
121 ptr::from_ref(self).cast_mut().cast::<c_void>(),
122 )
123 }
124
125 fn invoke(
126 &self,
127 request: *const sys::mln_resource_request,
128 handle: *mut sys::mln_resource_request_handle,
129 ) -> u32 {
130 let Some(raw_request) = ptr::NonNull::new(request.cast_mut()) else {
131 return UNKNOWN_PROVIDER_DECISION;
132 };
133 let handle = match ResourceRequestHandle::from_raw_with_fns(handle, self.handle_fns) {
134 Ok(handle) => handle,
135 Err(_) => return UNKNOWN_PROVIDER_DECISION,
136 };
137 let state = Arc::clone(&handle.state);
138
139 let request =
141 match unsafe { maplibre_core::resource::copy_resource_request(raw_request.as_ref()) } {
142 Ok(request) => request,
143 Err(_) => return state.finish_provider_exception(),
144 };
145
146 match catch_unwind(AssertUnwindSafe(|| (self.callback)(request, handle))) {
147 Ok(decision) => state.finish_provider_decision(decision),
148 Err(_) => state.finish_provider_exception(),
149 }
150 }
151}
152
153unsafe extern "C" fn resource_provider_trampoline(
154 user_data: *mut c_void,
155 request: *const sys::mln_resource_request,
156 handle: *mut sys::mln_resource_request_handle,
157) -> u32 {
158 let Some(state) = ptr::NonNull::new(user_data.cast::<ResourceProviderState>()) else {
159 return UNKNOWN_PROVIDER_DECISION;
160 };
161 unsafe { state.as_ref() }.invoke(request, handle)
165}
166
167type ResourceTransformCallback =
168 dyn Fn(ResourceTransformRequest) -> Option<String> + Send + Sync + 'static;
169
170pub(crate) struct ResourceTransformState {
171 callback: Box<ResourceTransformCallback>,
172}
173
174impl fmt::Debug for ResourceTransformState {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 f.debug_struct("ResourceTransformState")
177 .finish_non_exhaustive()
178 }
179}
180
181impl ResourceTransformState {
182 pub(crate) fn new<F>(callback: F) -> Box<Self>
183 where
184 F: Fn(ResourceTransformRequest) -> Option<String> + Send + Sync + 'static,
185 {
186 Box::new(Self {
187 callback: Box::new(callback),
188 })
189 }
190
191 pub(crate) fn descriptor(&self) -> sys::mln_resource_transform {
192 maplibre_core::resource::resource_transform_descriptor(
193 Some(resource_transform_trampoline),
194 ptr::from_ref(self).cast_mut().cast::<c_void>(),
195 )
196 }
197
198 fn invoke(
199 &self,
200 raw_kind: u32,
201 url: *const c_char,
202 out_response: *mut sys::mln_resource_transform_response,
203 ) -> sys::mln_status {
204 let status = unsafe {
207 maplibre_core::resource::initialize_resource_transform_response(out_response)
208 };
209 if status != sys::MLN_STATUS_OK {
210 return status;
211 }
212
213 let request = match unsafe {
215 maplibre_core::resource::copy_resource_transform_request(raw_kind, url)
216 } {
217 Ok(request) => request,
218 Err(error) => return maplibre_core::resource::status_for_error(&error),
219 };
220
221 let replacement = match catch_unwind(AssertUnwindSafe(|| (self.callback)(request))) {
222 Ok(replacement) => replacement,
223 Err(_) => return sys::MLN_STATUS_NATIVE_ERROR,
224 };
225
226 match replacement {
227 Some(replacement) if !replacement.is_empty() => {
228 unsafe {
234 sys::mln_resource_transform_response_set_url(
235 out_response,
236 replacement.as_ptr().cast(),
237 replacement.len(),
238 )
239 }
240 }
241 _ => sys::MLN_STATUS_OK,
242 }
243 }
244}
245
246unsafe extern "C" fn resource_transform_trampoline(
247 user_data: *mut c_void,
248 kind: u32,
249 url: *const c_char,
250 out_response: *mut sys::mln_resource_transform_response,
251) -> sys::mln_status {
252 let Some(state) = ptr::NonNull::new(user_data.cast::<ResourceTransformState>()) else {
253 return sys::MLN_STATUS_INVALID_ARGUMENT;
254 };
255 unsafe { state.as_ref() }.invoke(kind, url, out_response)
259}
260
261#[cfg(test)]
262mod tests {
263 use std::ffi::CString;
264 use std::sync::Mutex as StdMutex;
265 use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
266
267 use static_assertions::{assert_impl_all, assert_not_impl_any};
268
269 use super::*;
270 use crate::{
271 ErrorKind, ResourceKind, ResourceLoadingMethod, ResourcePriority, ResourceStoragePolicy,
272 ResourceUsage,
273 };
274
275 static FAKE_HANDLE_TEST_LOCK: StdMutex<()> = StdMutex::new(());
276 static COMPLETE_COUNT: AtomicUsize = AtomicUsize::new(0);
277 static CANCELLED_COUNT: AtomicUsize = AtomicUsize::new(0);
278 static RELEASE_COUNT: AtomicUsize = AtomicUsize::new(0);
279 static CANCELLED_VALUE: AtomicBool = AtomicBool::new(false);
280 static COMPLETE_STATUS: AtomicI32 = AtomicI32::new(sys::MLN_STATUS_OK);
281
282 unsafe extern "C" fn fake_complete(
283 _handle: *mut sys::mln_resource_request_handle,
284 _response: *const sys::mln_resource_response,
285 ) -> sys::mln_status {
286 COMPLETE_COUNT.fetch_add(1, Ordering::SeqCst);
287 COMPLETE_STATUS.load(Ordering::SeqCst)
288 }
289
290 unsafe extern "C" fn fake_cancelled(
291 _handle: *const sys::mln_resource_request_handle,
292 out_cancelled: *mut bool,
293 ) -> sys::mln_status {
294 CANCELLED_COUNT.fetch_add(1, Ordering::SeqCst);
295 if out_cancelled.is_null() {
296 return sys::MLN_STATUS_INVALID_ARGUMENT;
297 }
298 unsafe { *out_cancelled = CANCELLED_VALUE.load(Ordering::SeqCst) };
300 sys::MLN_STATUS_OK
301 }
302
303 unsafe extern "C" fn fake_release(_handle: *mut sys::mln_resource_request_handle) {
304 RELEASE_COUNT.fetch_add(1, Ordering::SeqCst);
305 }
306
307 fn fake_fns() -> ResourceRequestHandleFns {
308 unsafe { ResourceRequestHandleFns::new(fake_complete, fake_cancelled, fake_release) }
311 }
312
313 fn reset_fake_handle_state() {
314 COMPLETE_COUNT.store(0, Ordering::SeqCst);
315 CANCELLED_COUNT.store(0, Ordering::SeqCst);
316 RELEASE_COUNT.store(0, Ordering::SeqCst);
317 CANCELLED_VALUE.store(false, Ordering::SeqCst);
318 COMPLETE_STATUS.store(sys::MLN_STATUS_OK, Ordering::SeqCst);
319 }
320
321 fn fake_handle() -> ResourceRequestHandle {
322 reset_fake_handle_state();
323 ResourceRequestHandle::from_raw_with_fns(
324 0x1234usize as *mut sys::mln_resource_request_handle,
325 fake_fns(),
326 )
327 .unwrap()
328 }
329
330 fn request() -> sys::mln_resource_request {
331 sys::mln_resource_request {
332 size: std::mem::size_of::<sys::mln_resource_request>() as u32,
333 url: c"https://example.test/tile.pbf".as_ptr(),
334 kind: sys::MLN_RESOURCE_KIND_TILE,
335 loading_method: sys::MLN_RESOURCE_LOADING_METHOD_NETWORK_ONLY,
336 priority: sys::MLN_RESOURCE_PRIORITY_LOW,
337 usage: sys::MLN_RESOURCE_USAGE_OFFLINE,
338 storage_policy: sys::MLN_RESOURCE_STORAGE_POLICY_VOLATILE,
339 has_range: true,
340 range_start: 7,
341 range_end: 11,
342 has_prior_modified: true,
343 prior_modified_unix_ms: 123,
344 has_prior_expires: true,
345 prior_expires_unix_ms: 456,
346 prior_etag: c"etag".as_ptr(),
347 prior_data: [1u8, 2, 3].as_ptr(),
348 prior_data_size: 3,
349 }
350 }
351
352 fn response() -> sys::mln_resource_transform_response {
353 sys::mln_resource_transform_response {
354 size: std::mem::size_of::<sys::mln_resource_transform_response>() as u32,
355 url: ptr::null(),
356 context: ptr::null_mut(),
357 }
358 }
359
360 #[test]
361 fn resource_request_handle_is_send_but_not_sync() {
364 assert_impl_all!(ResourceRequestHandle: Send);
365 assert_not_impl_any!(ResourceRequestHandle: Sync);
366 }
367
368 #[test]
369 fn provider_callback_copies_request_and_pass_through_does_not_release() {
371 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
372 reset_fake_handle_state();
373 let state = ResourceProviderState {
374 callback: Box::new(|request, handle| {
375 assert_eq!(request.url, "https://example.test/tile.pbf");
376 assert_eq!(request.kind, ResourceKind::Tile);
377 assert_eq!(request.raw_kind, sys::MLN_RESOURCE_KIND_TILE);
378 assert_eq!(request.loading_method, ResourceLoadingMethod::NetworkOnly);
379 assert_eq!(request.priority, ResourcePriority::Low);
380 assert_eq!(request.usage, ResourceUsage::Offline);
381 assert_eq!(request.storage_policy, ResourceStoragePolicy::Volatile);
382 let range = request.range.unwrap();
383 assert_eq!((range.start, range.end), (7, 11));
384 assert_eq!(request.prior_modified_unix_ms, Some(123));
385 assert_eq!(request.prior_expires_unix_ms, Some(456));
386 assert_eq!(request.prior_etag.as_deref(), Some("etag"));
387 assert_eq!(request.prior_data, vec![1, 2, 3]);
388 drop(handle);
389 ResourceProviderDecision::PassThrough
390 }),
391 handle_fns: fake_fns(),
392 };
393 let raw_request = request();
394
395 let decision = state.invoke(
396 &raw_request,
397 0x1234usize as *mut sys::mln_resource_request_handle,
398 );
399
400 assert_eq!(decision, sys::MLN_RESOURCE_PROVIDER_DECISION_PASS_THROUGH);
401 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
402 }
403
404 #[test]
405 fn retained_pass_through_request_handle_is_stale_after_callback() {
407 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
408 reset_fake_handle_state();
409 let (sender, receiver) = std::sync::mpsc::channel();
410 let state = ResourceProviderState {
411 callback: Box::new(move |_, handle| {
412 sender.send(handle).unwrap();
413 ResourceProviderDecision::PassThrough
414 }),
415 handle_fns: fake_fns(),
416 };
417 let raw_request = request();
418
419 let decision = state.invoke(
420 &raw_request,
421 0x1234usize as *mut sys::mln_resource_request_handle,
422 );
423 let handle = receiver.recv().unwrap();
424
425 assert_eq!(decision, sys::MLN_RESOURCE_PROVIDER_DECISION_PASS_THROUGH);
426 let error = handle.is_cancelled().unwrap_err();
427 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
428 let error = handle.complete(ResourceResponse::no_content()).unwrap_err();
429 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
430 handle.close();
431 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 0);
432 assert_eq!(CANCELLED_COUNT.load(Ordering::SeqCst), 0);
433 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
434 }
435
436 #[test]
437 fn inline_completion_returns_handle_and_releases_once() {
439 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
440 reset_fake_handle_state();
441 let state = ResourceProviderState {
442 callback: Box::new(|_, handle| {
443 handle.complete(ResourceResponse::ok([1, 2, 3])).unwrap();
444 ResourceProviderDecision::PassThrough
445 }),
446 handle_fns: fake_fns(),
447 };
448 let raw_request = request();
449
450 let decision = state.invoke(
451 &raw_request,
452 0x1234usize as *mut sys::mln_resource_request_handle,
453 );
454
455 assert_eq!(decision, sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE);
456 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
457 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
458 }
459
460 #[test]
461 fn deferred_completion_from_another_thread_releases_once() {
463 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
464 let handle = fake_handle();
465 assert_eq!(
466 handle
467 .state
468 .finish_provider_decision(ResourceProviderDecision::Handle),
469 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
470 );
471
472 let thread = std::thread::spawn(move || {
473 handle
474 .complete(ResourceResponse::ok(vec![4, 5, 6]))
475 .unwrap();
476 });
477 thread.join().unwrap();
478
479 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
480 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
481 }
482
483 #[test]
484 fn failed_completion_is_terminal_after_reaching_c() {
486 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
487 let handle = fake_handle();
488 assert_eq!(
489 handle
490 .state
491 .finish_provider_decision(ResourceProviderDecision::Handle),
492 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
493 );
494 COMPLETE_STATUS.store(sys::MLN_STATUS_INVALID_STATE, Ordering::SeqCst);
495
496 let error = handle
497 .complete(ResourceResponse::ok(Vec::new()))
498 .unwrap_err();
499
500 assert_eq!(error.kind(), ErrorKind::InvalidState);
501 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
502 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
503 let error = handle.complete(ResourceResponse::no_content()).unwrap_err();
504 assert_eq!(error.kind(), ErrorKind::InvalidState);
505 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
506 }
507
508 #[test]
509 fn validation_failure_before_completion_keeps_handle_retryable() {
511 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
512 let handle = fake_handle();
513 assert_eq!(
514 handle
515 .state
516 .finish_provider_decision(ResourceProviderDecision::Handle),
517 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
518 );
519
520 let error = handle
521 .complete(ResourceResponse::error(
522 crate::ResourceErrorReason::Other,
523 "bad\0message",
524 ))
525 .unwrap_err();
526
527 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
528 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 0);
529 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
530 handle.complete(ResourceResponse::no_content()).unwrap();
531 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
532 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
533 }
534
535 #[test]
536 fn drop_releases_uncompleted_handled_request_once() {
538 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
539 let handle = fake_handle();
540 assert_eq!(
541 handle
542 .state
543 .finish_provider_decision(ResourceProviderDecision::Handle),
544 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
545 );
546 drop(handle);
547
548 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
549 }
550
551 #[test]
552 fn close_before_handle_decision_releases_after_decision() {
554 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
555 let handle = fake_handle();
556 let state = Arc::clone(&handle.state);
557 handle.close();
558 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
559 assert_eq!(
560 state.finish_provider_decision(ResourceProviderDecision::Handle),
561 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
562 );
563
564 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
565 }
566
567 #[test]
568 fn cancelled_queries_use_native_function_until_closed() {
570 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
571 let handle = fake_handle();
572 CANCELLED_VALUE.store(true, Ordering::SeqCst);
573
574 assert!(handle.is_cancelled().unwrap());
575 handle.close();
576 }
577
578 #[test]
579 fn late_completion_after_observed_cancellation_maps_native_status_and_closes() {
581 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
582 let handle = fake_handle();
583 assert_eq!(
584 handle
585 .state
586 .finish_provider_decision(ResourceProviderDecision::Handle),
587 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
588 );
589 CANCELLED_VALUE.store(true, Ordering::SeqCst);
590
591 assert!(handle.is_cancelled().unwrap());
592 COMPLETE_STATUS.store(sys::MLN_STATUS_INVALID_STATE, Ordering::SeqCst);
593 let error = handle.complete(ResourceResponse::no_content()).unwrap_err();
594
595 assert_eq!(error.kind(), ErrorKind::InvalidState);
596 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
597 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
598 let error = handle.complete(ResourceResponse::no_content()).unwrap_err();
599 assert_eq!(error.kind(), ErrorKind::InvalidState);
600 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
601 }
602
603 #[test]
604 fn provider_panics_produce_unknown_decision_without_unwinding() {
606 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
607 reset_fake_handle_state();
608 let state = ResourceProviderState {
609 callback: Box::new(|_, _| panic!("boom")),
610 handle_fns: fake_fns(),
611 };
612 let raw_request = request();
613
614 let decision = state.invoke(
615 &raw_request,
616 0x1234usize as *mut sys::mln_resource_request_handle,
617 );
618
619 assert_eq!(decision, UNKNOWN_PROVIDER_DECISION);
620 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
621 }
622
623 #[test]
624 fn transform_callback_copies_request_when_keeping_original_url() {
626 let state = ResourceTransformState::new(|request| {
627 assert_eq!(request.kind, ResourceKind::Style);
628 assert_eq!(request.raw_kind, sys::MLN_RESOURCE_KIND_STYLE);
629 assert_eq!(request.url, "https://example.test/style.json");
630 None
631 });
632 let descriptor = state.descriptor();
633 let callback = descriptor.callback.unwrap();
634 let url = CString::new("https://example.test/style.json").unwrap();
635 let mut response = response();
636
637 let status = unsafe {
638 callback(
639 descriptor.user_data,
640 sys::MLN_RESOURCE_KIND_STYLE,
641 url.as_ptr(),
642 &mut response,
643 )
644 };
645
646 assert_eq!(status, sys::MLN_STATUS_OK);
647 assert!(response.url.is_null());
648 }
649
650 #[test]
651 fn transform_callback_clears_stale_response_when_keeping_original_url() {
653 let state = ResourceTransformState::new(|_| None);
654 let descriptor = state.descriptor();
655 let callback = descriptor.callback.unwrap();
656 let url = CString::new("https://example.test/style.json").unwrap();
657 let stale = CString::new("https://stale.test/style.json").unwrap();
658 let mut response = response();
659 response.url = stale.as_ptr();
660
661 let status = unsafe {
662 callback(
663 descriptor.user_data,
664 sys::MLN_RESOURCE_KIND_STYLE,
665 url.as_ptr(),
666 &mut response,
667 )
668 };
669
670 assert_eq!(status, sys::MLN_STATUS_OK);
671 assert!(response.url.is_null());
672 }
673
674 #[test]
675 fn transform_callback_contains_panics() {
677 let state = ResourceTransformState::new(|_| panic!("boom"));
678 let descriptor = state.descriptor();
679 let callback = descriptor.callback.unwrap();
680 let url = CString::new("https://example.test/style.json").unwrap();
681 let mut response = response();
682
683 let status = unsafe {
684 callback(
685 descriptor.user_data,
686 sys::MLN_RESOURCE_KIND_STYLE,
687 url.as_ptr(),
688 &mut response,
689 )
690 };
691
692 assert_eq!(status, sys::MLN_STATUS_NATIVE_ERROR);
693 assert!(response.url.is_null());
694 }
695
696 #[test]
697 fn transform_callback_rejects_embedded_nul_replacements() {
699 let state = ResourceTransformState::new(|_| Some("https://example.test/\0bad".to_owned()));
700 let descriptor = state.descriptor();
701 let callback = descriptor.callback.unwrap();
702 let url = CString::new("https://example.test/style.json").unwrap();
703 let mut response = response();
704
705 let status = unsafe {
706 callback(
707 descriptor.user_data,
708 sys::MLN_RESOURCE_KIND_STYLE,
709 url.as_ptr(),
710 &mut response,
711 )
712 };
713
714 assert_eq!(status, sys::MLN_STATUS_INVALID_ARGUMENT);
715 assert!(response.url.is_null());
716 }
717
718 #[test]
719 fn transform_state_drops_callback_capture() {
722 let token = Arc::new(());
723 let callback_token = Arc::clone(&token);
724 let state = ResourceTransformState::new(move |_| {
725 let _ = &callback_token;
726 None
727 });
728 assert_eq!(Arc::strong_count(&token), 2);
729 drop(state);
730 assert_eq!(Arc::strong_count(&token), 1);
731 }
732
733 #[test]
734 fn callback_can_run_from_multiple_threads() {
736 let calls = Arc::new(AtomicUsize::new(0));
737 let callback_calls = Arc::clone(&calls);
738 let state = Arc::new(ResourceTransformState {
739 callback: Box::new(move |request| {
740 callback_calls.fetch_add(1, Ordering::SeqCst);
741 let _ = request;
742 None
743 }),
744 });
745
746 let handles = (0..2)
747 .map(|_| {
748 let state = Arc::clone(&state);
749 std::thread::spawn(move || {
750 let descriptor = state.descriptor();
751 let callback = descriptor.callback.unwrap();
752 let url = CString::new("https://example.test/tile").unwrap();
753 let mut response = response();
754 let status = unsafe {
755 callback(
756 descriptor.user_data,
757 sys::MLN_RESOURCE_KIND_TILE,
758 url.as_ptr(),
759 &mut response,
760 )
761 };
762 assert_eq!(status, sys::MLN_STATUS_OK);
763 assert!(response.url.is_null());
764 })
765 })
766 .collect::<Vec<_>>();
767
768 for handle in handles {
769 handle.join().unwrap();
770 }
771 assert_eq!(calls.load(Ordering::SeqCst), 2);
772 }
773}