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