1#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
19
20#[cfg(not(panic = "unwind"))]
21compile_error!(
22 "ccf-app requires panic = \"unwind\" because its C ABI catches panics at the boundary"
23);
24
25use std::cell::Cell;
26use std::ffi::c_void;
27use std::marker::PhantomData;
28use std::panic::{AssertUnwindSafe, catch_unwind};
29use std::ptr::NonNull;
30use std::slice;
31use std::sync::Once;
32
33#[doc(hidden)]
34pub const ABI_VERSION: u32 = 1;
35
36#[doc(hidden)]
37#[repr(C)]
38pub struct RawRegistry {
39 _private: [u8; 0],
40}
41
42#[doc(hidden)]
43#[repr(C)]
44pub struct RawEndpointContext {
45 _private: [u8; 0],
46}
47
48#[repr(C)]
49#[derive(Clone, Copy)]
50struct RawSlice {
51 data: *const u8,
52 len: usize,
53}
54
55#[repr(i32)]
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57enum RawResult {
58 Ok = 0,
59 NotFound = 1,
60 InvalidArgument = 2,
61 ReadOnly = 3,
62 InternalError = 4,
63}
64
65#[repr(i32)]
66#[derive(Clone, Copy)]
67enum RawAuth {
68 None = 0,
69 UserCert = 1,
70}
71
72type RawHandler = unsafe extern "C" fn(*mut c_void, *mut RawEndpointContext) -> i32;
73type RawDrop = unsafe extern "C" fn(*mut c_void);
74
75#[cfg(not(test))]
76mod ffi {
77 use super::*;
78
79 unsafe extern "C" {
80 pub fn ccf_rust_get_abi_version() -> u32;
81 pub fn ccf_rust_register_endpoint(
82 registry: *mut RawRegistry,
83 path: RawSlice,
84 method: RawSlice,
85 auth: RawAuth,
86 read_only: i32,
87 callback: RawHandler,
88 drop: RawDrop,
89 user_data: *mut c_void,
90 ) -> i32;
91 pub fn ccf_rust_request_body(ctx: *mut RawEndpointContext, body: *mut RawSlice) -> i32;
92 pub fn ccf_rust_request_query(ctx: *mut RawEndpointContext, query: *mut RawSlice) -> i32;
93 pub fn ccf_rust_request_path_param(
94 ctx: *mut RawEndpointContext,
95 name: RawSlice,
96 value: *mut RawSlice,
97 ) -> i32;
98 pub fn ccf_rust_request_header(
99 ctx: *mut RawEndpointContext,
100 name: RawSlice,
101 value: *mut RawSlice,
102 ) -> i32;
103 pub fn ccf_rust_response_status(ctx: *mut RawEndpointContext, status: u16) -> i32;
104 pub fn ccf_rust_response_header(
105 ctx: *mut RawEndpointContext,
106 name: RawSlice,
107 value: RawSlice,
108 ) -> i32;
109 pub fn ccf_rust_response_body(ctx: *mut RawEndpointContext, body: RawSlice) -> i32;
110 pub fn ccf_rust_response_error(
111 ctx: *mut RawEndpointContext,
112 status: u16,
113 code: RawSlice,
114 message: RawSlice,
115 ) -> i32;
116 pub fn ccf_rust_kv_get(
117 ctx: *mut RawEndpointContext,
118 map_name: RawSlice,
119 key: RawSlice,
120 value: *mut RawSlice,
121 ) -> i32;
122 pub fn ccf_rust_kv_has(
123 ctx: *mut RawEndpointContext,
124 map_name: RawSlice,
125 key: RawSlice,
126 present: *mut i32,
127 ) -> i32;
128 pub fn ccf_rust_kv_put(
129 ctx: *mut RawEndpointContext,
130 map_name: RawSlice,
131 key: RawSlice,
132 value: RawSlice,
133 ) -> i32;
134 pub fn ccf_rust_kv_remove(
135 ctx: *mut RawEndpointContext,
136 map_name: RawSlice,
137 key: RawSlice,
138 ) -> i32;
139 }
140}
141
142#[cfg(test)]
143mod ffi {
144 use super::*;
145
146 pub unsafe extern "C" fn ccf_rust_get_abi_version() -> u32 {
147 ABI_VERSION
148 }
149
150 pub unsafe extern "C" fn ccf_rust_register_endpoint(
151 _registry: *mut RawRegistry,
152 _path: RawSlice,
153 _method: RawSlice,
154 _auth: RawAuth,
155 _read_only: i32,
156 _callback: RawHandler,
157 _drop: RawDrop,
158 _user_data: *mut c_void,
159 ) -> i32 {
160 RawResult::InternalError as i32
161 }
162
163 macro_rules! failing_ffi {
164 ($name:ident($($arg:ident: $ty:ty),*)) => {
165 pub unsafe extern "C" fn $name($($arg: $ty),*) -> i32 {
166 $(let _ = $arg;)*
167 RawResult::InternalError as i32
168 }
169 };
170 }
171
172 failing_ffi!(ccf_rust_request_body(ctx: *mut RawEndpointContext, body: *mut RawSlice));
173 failing_ffi!(ccf_rust_request_query(ctx: *mut RawEndpointContext, query: *mut RawSlice));
174 failing_ffi!(ccf_rust_request_path_param(ctx: *mut RawEndpointContext, name: RawSlice, value: *mut RawSlice));
175 failing_ffi!(ccf_rust_request_header(ctx: *mut RawEndpointContext, name: RawSlice, value: *mut RawSlice));
176 failing_ffi!(ccf_rust_response_status(ctx: *mut RawEndpointContext, status: u16));
177 failing_ffi!(ccf_rust_response_header(ctx: *mut RawEndpointContext, name: RawSlice, value: RawSlice));
178 failing_ffi!(ccf_rust_response_body(ctx: *mut RawEndpointContext, body: RawSlice));
179 failing_ffi!(ccf_rust_response_error(ctx: *mut RawEndpointContext, status: u16, code: RawSlice, message: RawSlice));
180 failing_ffi!(ccf_rust_kv_get(ctx: *mut RawEndpointContext, map_name: RawSlice, key: RawSlice, value: *mut RawSlice));
181 failing_ffi!(ccf_rust_kv_has(ctx: *mut RawEndpointContext, map_name: RawSlice, key: RawSlice, present: *mut i32));
182 failing_ffi!(ccf_rust_kv_put(ctx: *mut RawEndpointContext, map_name: RawSlice, key: RawSlice, value: RawSlice));
183 failing_ffi!(ccf_rust_kv_remove(ctx: *mut RawEndpointContext, map_name: RawSlice, key: RawSlice));
184}
185
186fn raw_slice(value: &[u8]) -> RawSlice {
187 RawSlice {
188 data: value.as_ptr(),
189 len: value.len(),
190 }
191}
192
193fn raw_str(value: &str) -> RawSlice {
194 raw_slice(value.as_bytes())
195}
196
197fn decode_result(result: i32) -> Result<(), BridgeError> {
198 match result {
199 value if value == RawResult::Ok as i32 => Ok(()),
200 value if value == RawResult::NotFound as i32 => Err(BridgeError::NotFound),
201 value if value == RawResult::InvalidArgument as i32 => Err(BridgeError::InvalidArgument),
202 value if value == RawResult::ReadOnly as i32 => Err(BridgeError::ReadOnly),
203 _ => Err(BridgeError::Internal),
204 }
205}
206
207unsafe fn borrowed_slice<'a>(value: RawSlice) -> &'a [u8] {
208 if value.len == 0 {
209 &[]
210 } else {
211 unsafe { slice::from_raw_parts(value.data, value.len) }
214 }
215}
216
217#[derive(Clone, Copy, Debug, Eq, PartialEq)]
222pub enum BridgeError {
223 NotFound,
225 InvalidArgument,
228 ReadOnly,
230 Internal,
233 AbiMismatch,
235}
236
237pub type BridgeResult<T> = Result<T, BridgeError>;
239
240#[derive(Clone, Copy, Debug)]
244pub enum Auth {
245 None,
247 UserCert,
250}
251
252impl Auth {
253 fn raw(self) -> RawAuth {
254 match self {
255 Self::None => RawAuth::None,
256 Self::UserCert => RawAuth::UserCert,
257 }
258 }
259}
260
261#[derive(Clone, Debug)]
274pub struct EndpointError {
275 pub status: u16,
277 pub code: String,
279 pub message: String,
281}
282
283impl EndpointError {
284 pub fn new(status: u16, code: impl Into<String>, message: impl Into<String>) -> Self {
286 Self {
287 status,
288 code: code.into(),
289 message: message.into(),
290 }
291 }
292
293 pub fn internal(message: impl Into<String>) -> Self {
295 Self::new(500, "InternalError", message)
296 }
297}
298
299impl From<BridgeError> for EndpointError {
309 fn from(error: BridgeError) -> Self {
310 Self::internal(format!("CCF bridge error: {error:?}"))
311 }
312}
313
314pub type EndpointResult = Result<(), EndpointError>;
317
318pub trait Codec<T> {
345 type Error;
347
348 fn encode(value: &T) -> Result<Vec<u8>, Self::Error>;
350 fn decode(value: &[u8]) -> Result<T, Self::Error>;
352}
353
354struct Context<'a> {
355 raw: NonNull<RawEndpointContext>,
356 _lifetime: PhantomData<&'a mut RawEndpointContext>,
357}
358
359impl Context<'_> {
360 fn body(&self) -> BridgeResult<&[u8]> {
361 let mut value = RawSlice {
362 data: std::ptr::null(),
363 len: 0,
364 };
365 decode_result(unsafe { ffi::ccf_rust_request_body(self.raw.as_ptr(), &mut value) })?;
367 Ok(unsafe { borrowed_slice(value) })
369 }
370
371 fn query(&self) -> BridgeResult<&str> {
372 let mut value = RawSlice {
373 data: std::ptr::null(),
374 len: 0,
375 };
376 decode_result(unsafe { ffi::ccf_rust_request_query(self.raw.as_ptr(), &mut value) })?;
378 let bytes = unsafe { borrowed_slice(value) };
380 std::str::from_utf8(bytes).map_err(|_| BridgeError::Internal)
381 }
382
383 fn copied_optional(
384 &mut self,
385 name: &str,
386 get: unsafe extern "C" fn(*mut RawEndpointContext, RawSlice, *mut RawSlice) -> i32,
387 ) -> BridgeResult<Option<Vec<u8>>> {
388 let mut value = RawSlice {
389 data: std::ptr::null(),
390 len: 0,
391 };
392 match decode_result(unsafe { get(self.raw.as_ptr(), raw_str(name), &mut value) }) {
395 Ok(()) => {
396 Ok(Some(unsafe { borrowed_slice(value) }.to_vec()))
398 }
399 Err(BridgeError::NotFound) => Ok(None),
400 Err(error) => Err(error),
401 }
402 }
403
404 fn path_param(&mut self, name: &str) -> BridgeResult<Option<String>> {
405 self.copied_optional(name, ffi::ccf_rust_request_path_param)?
406 .map(|value| String::from_utf8(value).map_err(|_| BridgeError::Internal))
407 .transpose()
408 }
409
410 fn header(&mut self, name: &str) -> BridgeResult<Option<Vec<u8>>> {
411 self.copied_optional(name, ffi::ccf_rust_request_header)
412 }
413
414 fn set_status(&mut self, status: u16) -> BridgeResult<()> {
415 decode_result(unsafe { ffi::ccf_rust_response_status(self.raw.as_ptr(), status) })
417 }
418
419 fn set_header(&mut self, name: &str, value: &str) -> BridgeResult<()> {
420 decode_result(unsafe {
422 ffi::ccf_rust_response_header(self.raw.as_ptr(), raw_str(name), raw_str(value))
423 })
424 }
425
426 fn set_body(&mut self, body: &[u8]) -> BridgeResult<()> {
427 decode_result(unsafe { ffi::ccf_rust_response_body(self.raw.as_ptr(), raw_slice(body)) })
429 }
430
431 fn set_error(&mut self, error: &EndpointError) -> BridgeResult<()> {
432 decode_result(unsafe {
434 ffi::ccf_rust_response_error(
435 self.raw.as_ptr(),
436 error.status,
437 raw_str(&error.code),
438 raw_str(&error.message),
439 )
440 })
441 }
442
443 fn get(&mut self, map_name: &str, key: &[u8]) -> BridgeResult<Option<Vec<u8>>> {
444 let mut value = RawSlice {
445 data: std::ptr::null(),
446 len: 0,
447 };
448 match decode_result(unsafe {
450 ffi::ccf_rust_kv_get(
451 self.raw.as_ptr(),
452 raw_str(map_name),
453 raw_slice(key),
454 &mut value,
455 )
456 }) {
457 Ok(()) => {
458 Ok(Some(unsafe { borrowed_slice(value) }.to_vec()))
460 }
461 Err(BridgeError::NotFound) => Ok(None),
462 Err(error) => Err(error),
463 }
464 }
465
466 fn has(&mut self, map_name: &str, key: &[u8]) -> BridgeResult<bool> {
467 let mut present = 0;
468 decode_result(unsafe {
470 ffi::ccf_rust_kv_has(
471 self.raw.as_ptr(),
472 raw_str(map_name),
473 raw_slice(key),
474 &mut present,
475 )
476 })?;
477 Ok(present != 0)
478 }
479
480 fn put(&mut self, map_name: &str, key: &[u8], value: &[u8]) -> BridgeResult<()> {
481 decode_result(unsafe {
483 ffi::ccf_rust_kv_put(
484 self.raw.as_ptr(),
485 raw_str(map_name),
486 raw_slice(key),
487 raw_slice(value),
488 )
489 })
490 }
491
492 fn remove(&mut self, map_name: &str, key: &[u8]) -> BridgeResult<()> {
493 decode_result(unsafe {
495 ffi::ccf_rust_kv_remove(self.raw.as_ptr(), raw_str(map_name), raw_slice(key))
496 })
497 }
498}
499
500pub struct ReadOnlyContext<'a>(Context<'a>);
514
515impl<'ctx> ReadOnlyContext<'ctx> {
516 pub fn body(&self) -> BridgeResult<&[u8]> {
519 self.0.body()
520 }
521
522 pub fn query(&self) -> BridgeResult<&str> {
525 self.0.query()
526 }
527
528 pub fn path_param(&mut self, name: &str) -> BridgeResult<Option<String>> {
531 self.0.path_param(name)
532 }
533
534 pub fn header(&mut self, name: &str) -> BridgeResult<Option<Vec<u8>>> {
537 self.0.header(name)
538 }
539
540 pub fn set_status(&mut self, status: u16) -> BridgeResult<()> {
543 self.0.set_status(status)
544 }
545
546 pub fn set_header(&mut self, name: &str, value: &str) -> BridgeResult<()> {
549 self.0.set_header(name, value)
550 }
551
552 pub fn set_body(&mut self, body: &[u8]) -> BridgeResult<()> {
554 self.0.set_body(body)
555 }
556
557 pub fn map<'a>(&'a mut self, name: &'a str) -> ReadOnlyMap<'a, 'ctx> {
559 ReadOnlyMap {
560 context: &mut self.0,
561 name,
562 }
563 }
564}
565
566pub struct WriteContext<'a>(Context<'a>);
590
591impl<'ctx> WriteContext<'ctx> {
592 pub fn body(&self) -> BridgeResult<&[u8]> {
595 self.0.body()
596 }
597
598 pub fn query(&self) -> BridgeResult<&str> {
601 self.0.query()
602 }
603
604 pub fn path_param(&mut self, name: &str) -> BridgeResult<Option<String>> {
607 self.0.path_param(name)
608 }
609
610 pub fn header(&mut self, name: &str) -> BridgeResult<Option<Vec<u8>>> {
613 self.0.header(name)
614 }
615
616 pub fn set_status(&mut self, status: u16) -> BridgeResult<()> {
619 self.0.set_status(status)
620 }
621
622 pub fn set_header(&mut self, name: &str, value: &str) -> BridgeResult<()> {
625 self.0.set_header(name, value)
626 }
627
628 pub fn set_body(&mut self, body: &[u8]) -> BridgeResult<()> {
630 self.0.set_body(body)
631 }
632
633 pub fn map<'a>(&'a mut self, name: &'a str) -> Map<'a, 'ctx> {
635 Map {
636 context: &mut self.0,
637 name,
638 }
639 }
640}
641
642pub struct ReadOnlyMap<'a, 'ctx> {
644 context: &'a mut Context<'ctx>,
645 name: &'a str,
646}
647
648impl ReadOnlyMap<'_, '_> {
649 pub fn get(&mut self, key: &[u8]) -> BridgeResult<Option<Vec<u8>>> {
651 self.context.get(self.name, key)
652 }
653
654 pub fn has(&mut self, key: &[u8]) -> BridgeResult<bool> {
656 self.context.has(self.name, key)
657 }
658}
659
660pub struct Map<'a, 'ctx> {
665 context: &'a mut Context<'ctx>,
666 name: &'a str,
667}
668
669impl Map<'_, '_> {
670 pub fn get(&mut self, key: &[u8]) -> BridgeResult<Option<Vec<u8>>> {
672 self.context.get(self.name, key)
673 }
674
675 pub fn has(&mut self, key: &[u8]) -> BridgeResult<bool> {
677 self.context.has(self.name, key)
678 }
679
680 pub fn put(&mut self, key: &[u8], value: &[u8]) -> BridgeResult<()> {
682 self.context.put(self.name, key, value)
683 }
684
685 pub fn remove(&mut self, key: &[u8]) -> BridgeResult<()> {
687 self.context.remove(self.name, key)
688 }
689}
690
691pub trait RetrySafeHandler: Send + Sync {}
699
700impl<T> RetrySafeHandler for T where T: Send + Sync {}
701
702trait ReadHandler:
703 for<'a> Fn(&mut ReadOnlyContext<'a>) -> EndpointResult + RetrySafeHandler + 'static
704{
705}
706
707impl<T> ReadHandler for T where
708 T: for<'a> Fn(&mut ReadOnlyContext<'a>) -> EndpointResult + RetrySafeHandler + 'static
709{
710}
711
712trait WriteHandler:
713 for<'a> Fn(&mut WriteContext<'a>) -> EndpointResult + RetrySafeHandler + 'static
714{
715}
716
717impl<T> WriteHandler for T where
718 T: for<'a> Fn(&mut WriteContext<'a>) -> EndpointResult + RetrySafeHandler + 'static
719{
720}
721
722enum Handler {
723 Read(Box<dyn ReadHandler>),
724 Write(Box<dyn WriteHandler>),
725}
726
727thread_local! {
728 static IN_APP_CALLBACK: Cell<bool> = const { Cell::new(false) };
729}
730
731fn in_app_callback() -> bool {
732 IN_APP_CALLBACK.try_with(Cell::get).unwrap_or(false)
734}
735
736fn catch_app_panic<R>(f: impl FnOnce() -> R) -> std::thread::Result<R> {
738 struct Restore(bool);
739
740 impl Drop for Restore {
741 fn drop(&mut self) {
742 IN_APP_CALLBACK.set(self.0);
743 }
744 }
745
746 let _restore = Restore(IN_APP_CALLBACK.replace(true));
747 catch_unwind(AssertUnwindSafe(f))
748}
749
750fn install_panic_hook() {
759 static INSTALL: Once = Once::new();
760 INSTALL.call_once(|| {
761 let previous = std::panic::take_hook();
762 std::panic::set_hook(Box::new(move |info| {
763 if !in_app_callback() {
764 previous(info);
765 }
766 }));
767 });
768}
769
770unsafe extern "C" fn invoke_handler(
771 user_data: *mut c_void,
772 raw_context: *mut RawEndpointContext,
773) -> i32 {
774 if user_data.is_null() || raw_context.is_null() {
775 return RawResult::InvalidArgument as i32;
776 }
777
778 let handler = unsafe { &*(user_data.cast::<Handler>()) };
780 let raw = unsafe { NonNull::new_unchecked(raw_context) };
782
783 let result = catch_app_panic(|| match handler {
784 Handler::Read(handler) => handler(&mut ReadOnlyContext(Context {
785 raw,
786 _lifetime: PhantomData,
787 })),
788 Handler::Write(handler) => handler(&mut WriteContext(Context {
789 raw,
790 _lifetime: PhantomData,
791 })),
792 });
793
794 let endpoint_error = match result {
795 Ok(Ok(())) => return RawResult::Ok as i32,
796 Ok(Err(error)) => error,
797 Err(_) => EndpointError::internal("Rust endpoint panicked"),
798 };
799
800 let mut context = Context {
801 raw,
802 _lifetime: PhantomData,
803 };
804 match context.set_error(&endpoint_error) {
805 Ok(()) => RawResult::Ok as i32,
806 Err(_) => RawResult::InternalError as i32,
807 }
808}
809
810unsafe extern "C" fn drop_handler(user_data: *mut c_void) {
811 if !user_data.is_null() {
812 let _ = catch_app_panic(|| {
813 drop(unsafe { Box::from_raw(user_data.cast::<Handler>()) });
816 });
817 }
818}
819
820pub struct Registry {
822 raw: NonNull<RawRegistry>,
823}
824
825impl Registry {
826 #[doc(hidden)]
827 pub unsafe fn from_raw(raw: *mut RawRegistry) -> BridgeResult<Self> {
832 if unsafe { ffi::ccf_rust_get_abi_version() } != ABI_VERSION {
833 return Err(BridgeError::AbiMismatch);
834 }
835 NonNull::new(raw)
836 .map(|raw| Self { raw })
837 .ok_or(BridgeError::InvalidArgument)
838 }
839
840 pub fn read_only<F>(
847 &mut self,
848 path: &str,
849 method: &str,
850 auth: Auth,
851 handler: F,
852 ) -> BridgeResult<()>
853 where
854 F: for<'a> Fn(&mut ReadOnlyContext<'a>) -> EndpointResult + RetrySafeHandler + 'static,
855 {
856 self.register(path, method, auth, Handler::Read(Box::new(handler)))
857 }
858
859 pub fn read_write<F>(
863 &mut self,
864 path: &str,
865 method: &str,
866 auth: Auth,
867 handler: F,
868 ) -> BridgeResult<()>
869 where
870 F: for<'a> Fn(&mut WriteContext<'a>) -> EndpointResult + RetrySafeHandler + 'static,
871 {
872 self.register(path, method, auth, Handler::Write(Box::new(handler)))
873 }
874
875 fn register(
876 &mut self,
877 path: &str,
878 method: &str,
879 auth: Auth,
880 handler: Handler,
881 ) -> BridgeResult<()> {
882 let read_only = matches!(handler, Handler::Read(_)) as i32;
883 let user_data = Box::into_raw(Box::new(handler)).cast::<c_void>();
884 let result = unsafe {
887 ffi::ccf_rust_register_endpoint(
888 self.raw.as_ptr(),
889 raw_str(path),
890 raw_str(method),
891 auth.raw(),
892 read_only,
893 invoke_handler,
894 drop_handler,
895 user_data,
896 )
897 };
898 if let Err(error) = decode_result(result) {
899 unsafe { drop_handler(user_data) };
901 return Err(error);
902 }
903 Ok(())
904 }
905}
906
907#[doc(hidden)]
914pub unsafe fn register_app<F>(raw_registry: *mut RawRegistry, register: F) -> i32
915where
916 F: FnOnce(&mut Registry) -> BridgeResult<()>,
917{
918 install_panic_hook();
919 let result = catch_app_panic(|| {
920 let mut registry = unsafe { Registry::from_raw(raw_registry) }?;
922 register(&mut registry)
923 });
924 match result {
925 Ok(Ok(())) => RawResult::Ok as i32,
926 _ => RawResult::InternalError as i32,
927 }
928}
929
930#[macro_export]
938macro_rules! export_app {
939 ($register:path) => {
940 #[doc(hidden)]
941 #[unsafe(no_mangle)]
942 pub extern "C" fn ccf_rust_app_abi_version() -> u32 {
943 $crate::ABI_VERSION
944 }
945
946 #[doc(hidden)]
947 #[unsafe(no_mangle)]
948 pub unsafe extern "C" fn ccf_rust_app_register(
949 raw_registry: *mut $crate::RawRegistry,
950 ) -> i32 {
951 unsafe { $crate::register_app(raw_registry, $register) }
953 }
954 };
955}
956
957#[cfg(test)]
958mod tests {
959 use super::*;
960
961 #[test]
962 fn maps_raw_result_codes() {
963 assert_eq!(decode_result(0), Ok(()));
964 assert_eq!(decode_result(1), Err(BridgeError::NotFound));
965 assert_eq!(decode_result(2), Err(BridgeError::InvalidArgument));
966 assert_eq!(decode_result(3), Err(BridgeError::ReadOnly));
967 assert_eq!(decode_result(99), Err(BridgeError::Internal));
968 }
969
970 #[test]
971 fn rejects_null_registry() {
972 assert!(matches!(
974 unsafe { Registry::from_raw(std::ptr::null_mut()) },
975 Err(BridgeError::InvalidArgument)
976 ));
977 }
978
979 #[test]
980 fn preserves_error_status_for_bridge_validation() {
981 assert_eq!(EndpointError::new(432, "Error", "message").status, 432);
982 }
983
984 #[test]
985 fn panicking_handler_returns_internal_error() {
986 let handler = Box::new(Handler::Write(Box::new(|_| panic!("test panic"))));
987 let user_data = Box::into_raw(handler).cast::<c_void>();
988 let raw_context = NonNull::<RawEndpointContext>::dangling().as_ptr();
989 let result = unsafe { invoke_handler(user_data, raw_context) };
993 assert_eq!(result, RawResult::InternalError as i32);
994 unsafe { drop_handler(user_data) };
996 }
997
998 #[test]
999 fn restores_app_callback_marker() {
1000 assert!(!in_app_callback());
1001 let nested = catch_app_panic(|| {
1002 assert!(catch_app_panic(|| ()).is_ok());
1003 in_app_callback()
1004 });
1005 assert_eq!(nested.ok(), Some(true));
1006 assert!(catch_app_panic::<()>(|| panic!("test panic")).is_err());
1007 assert!(!in_app_callback());
1008 }
1009
1010 #[test]
1011 fn panic_hook_forwards_only_panics_outside_app_callbacks() {
1012 thread_local! {
1013 static REPORTS: Cell<usize> = const { Cell::new(0) };
1014 }
1015 let default_hook = std::panic::take_hook();
1016 std::panic::set_hook(Box::new(move |info| {
1017 REPORTS.set(REPORTS.get() + 1);
1018 default_hook(info);
1019 }));
1020 install_panic_hook();
1021
1022 assert!(catch_app_panic::<()>(|| panic!("test panic")).is_err());
1023 assert_eq!(REPORTS.get(), 0);
1024 assert!(catch_unwind(|| panic!("test panic")).is_err());
1025 assert_eq!(REPORTS.get(), 1);
1026 }
1027}