@@ -60,6 +60,22 @@ pub struct Builder {
6060 /// the raw pointer via `Arc::from_raw`) instead of borrowed. Used for
6161 /// close/undeclare-style functions that invalidate their handle.
6262 consume_args : HashMap < String , HashSet < String > > ,
63+ /// Return-type wrappers keyed by the last-segment name of `T` in
64+ /// `ZResult<T>`. Applies when `T` is a plain (non-`Vec`) type.
65+ return_wrappers : HashMap < String , ReturnWrapper > ,
66+ /// Return-type wrappers keyed by the element type name of `Vec<T>` in
67+ /// `ZResult<Vec<T>>`.
68+ return_wrappers_vec : HashMap < String , ReturnWrapper > ,
69+ }
70+
71+ /// Describes how to render a `ZResult<T>` return value into a JNI-compatible
72+ /// output value. Registered via [`Builder::return_wrapper`] /
73+ /// [`Builder::return_wrapper_vec`].
74+ #[ derive( Clone ) ]
75+ pub ( crate ) struct ReturnWrapper {
76+ jni_type : syn:: Type ,
77+ wrap_fn : syn:: Path ,
78+ default_expr : syn:: Expr ,
6379}
6480
6581impl Default for Builder {
@@ -78,6 +94,8 @@ impl Default for Builder {
7894 enum_decoders : HashMap :: new ( ) ,
7995 callback_decoders : HashMap :: new ( ) ,
8096 consume_args : HashMap :: new ( ) ,
97+ return_wrappers : HashMap :: new ( ) ,
98+ return_wrappers_vec : HashMap :: new ( ) ,
8199 }
82100 }
83101}
@@ -188,6 +206,37 @@ impl Builder {
188206 self
189207 }
190208
209+ /// Register a return-type wrapper for `ZResult<T>` where `T`'s
210+ /// last-segment name equals `type_name`. `jni_type` is the generated
211+ /// `extern "C"` return type. `wrap_fn` must have signature
212+ /// `fn(&mut JNIEnv, T) -> ZResult<jni_type>`. `default_expr` is the value
213+ /// returned on error (before the exception is thrown on the JVM side).
214+ pub fn return_wrapper (
215+ mut self ,
216+ type_name : impl Into < String > ,
217+ jni_type : impl AsRef < str > ,
218+ wrap_fn : impl AsRef < str > ,
219+ default_expr : impl AsRef < str > ,
220+ ) -> Self {
221+ self . return_wrappers
222+ . insert ( type_name. into ( ) , parse_return_wrapper ( jni_type, wrap_fn, default_expr) ) ;
223+ self
224+ }
225+
226+ /// Like [`Builder::return_wrapper`] but applies when `T` is `Vec<E>`
227+ /// with `E`'s last-segment name equal to `element_type_name`.
228+ pub fn return_wrapper_vec (
229+ mut self ,
230+ element_type_name : impl Into < String > ,
231+ jni_type : impl AsRef < str > ,
232+ wrap_fn : impl AsRef < str > ,
233+ default_expr : impl AsRef < str > ,
234+ ) -> Self {
235+ self . return_wrappers_vec
236+ . insert ( element_type_name. into ( ) , parse_return_wrapper ( jni_type, wrap_fn, default_expr) ) ;
237+ self
238+ }
239+
191240 /// Mark a specific argument of a source function as consuming: the
192241 /// generated wrapper will take ownership of the raw pointer via
193242 /// `Arc::from_raw` (dropping the Arc at end of scope), rather than
@@ -428,6 +477,37 @@ impl JniConverter {
428477 } ) ;
429478 call_args. push ( quote ! { #name } ) ;
430479 }
480+ ArgKind :: OptionEncoding => {
481+ let decoder = self
482+ . cfg
483+ . encoding_decoder
484+ . as_ref ( )
485+ . expect ( "encoding_decoder not configured" ) ;
486+ let id_ident = format_ident ! ( "{}_id" , name) ;
487+ let schema_ident = format_ident ! ( "{}_schema" , name) ;
488+ jni_params. push ( quote ! { #id_ident: jni:: sys:: jint } ) ;
489+ jni_params. push ( quote ! { #schema_ident: jni:: objects:: JString } ) ;
490+ prelude. push ( quote ! {
491+ let #name = Some ( #decoder( & mut env, #id_ident, & #schema_ident) ?) ;
492+ } ) ;
493+ call_args. push ( quote ! { #name } ) ;
494+ }
495+ ArgKind :: OptionString => {
496+ let decoder = self
497+ . cfg
498+ . string_decoder
499+ . as_ref ( )
500+ . expect ( "string_decoder not configured" ) ;
501+ jni_params. push ( quote ! { #name: jni:: objects:: JString } ) ;
502+ prelude. push ( quote ! {
503+ let #name = if !#name. is_null( ) {
504+ Some ( #decoder( & mut env, & #name) ?)
505+ } else {
506+ None
507+ } ;
508+ } ) ;
509+ call_args. push ( quote ! { #name } ) ;
510+ }
431511 ArgKind :: Unsupported => panic ! (
432512 "unsupported parameter type `{}` for `{}` at {loc}" ,
433513 ty. to_token_stream( ) ,
@@ -453,6 +533,18 @@ impl JniConverter {
453533 quote ! { ( ) } ,
454534 quote ! { #zresult<( ) > } ,
455535 )
536+ } else if let Some ( wrapper) = self . lookup_return_wrapper ( & inner) {
537+ let ReturnWrapper {
538+ jni_type,
539+ wrap_fn,
540+ default_expr,
541+ } = wrapper;
542+ (
543+ quote ! { #jni_type } ,
544+ quote ! { #wrap_fn( & mut env, __result) } ,
545+ quote ! { #default_expr } ,
546+ quote ! { #zresult<#jni_type> } ,
547+ )
456548 } else {
457549 (
458550 quote ! { * const #inner } ,
@@ -497,6 +589,23 @@ impl JniConverter {
497589 syn:: parse2 ( tokens) . expect ( "generated JNI wrapper must parse" )
498590 }
499591
592+ fn lookup_return_wrapper ( & self , ty : & syn:: Type ) -> Option < ReturnWrapper > {
593+ let syn:: Type :: Path ( tp) = ty else { return None } ;
594+ let seg = tp. path . segments . last ( ) ?;
595+ let name = seg. ident . to_string ( ) ;
596+ if name == "Vec" {
597+ let syn:: PathArguments :: AngleBracketed ( args) = & seg. arguments else {
598+ return None ;
599+ } ;
600+ let syn:: GenericArgument :: Type ( elem) = args. args . first ( ) ? else {
601+ return None ;
602+ } ;
603+ let elem_name = type_last_segment ( elem) ?;
604+ return self . cfg . return_wrappers_vec . get ( & elem_name) . cloned ( ) ;
605+ }
606+ self . cfg . return_wrappers . get ( & name) . cloned ( )
607+ }
608+
500609 fn classify_arg ( & self , ty : & syn:: Type ) -> ArgKind {
501610 match ty {
502611 syn:: Type :: Reference ( r) if r. mutability . is_none ( ) => {
@@ -537,6 +646,16 @@ impl JniConverter {
537646 if name == "Option" && is_option_of_vec_u8 ( last) {
538647 return ArgKind :: OptionVecU8 ;
539648 }
649+ if name == "Option" {
650+ if let Some ( inner) = option_inner_type_name ( last) {
651+ if inner == "String" {
652+ return ArgKind :: OptionString ;
653+ }
654+ if inner == "Encoding" {
655+ return ArgKind :: OptionEncoding ;
656+ }
657+ }
658+ }
540659 if name == "Vec" && is_vec_of_u8 ( last) {
541660 return ArgKind :: VecU8 ;
542661 }
@@ -564,12 +683,31 @@ enum ArgKind {
564683 VecU8 ,
565684 /// `Encoding` → `(jint id, JString schema)` pair via `encoding_decoder`.
566685 Encoding ,
686+ /// `Option<Encoding>` → `(jint id, JString schema)` pair via `encoding_decoder`,
687+ /// wrapped in `Some(_)`. Semantic gating on payload presence is the
688+ /// callee's responsibility.
689+ OptionEncoding ,
690+ /// `Option<String>` → nullable `JString`.
691+ OptionString ,
567692 /// `impl Fn(T) + Send + Sync + 'static` → `(JObject callback, JObject on_close)`
568693 /// pair decoded via a callback decoder registered for `T`.
569694 Callback ( syn:: Path ) ,
570695 Unsupported ,
571696}
572697
698+ fn parse_return_wrapper (
699+ jni_type : impl AsRef < str > ,
700+ wrap_fn : impl AsRef < str > ,
701+ default_expr : impl AsRef < str > ,
702+ ) -> ReturnWrapper {
703+ ReturnWrapper {
704+ jni_type : syn:: parse_str ( jni_type. as_ref ( ) ) . expect ( "invalid return wrapper jni_type" ) ,
705+ wrap_fn : syn:: parse_str ( wrap_fn. as_ref ( ) ) . expect ( "invalid return wrapper wrap_fn path" ) ,
706+ default_expr : syn:: parse_str ( default_expr. as_ref ( ) )
707+ . expect ( "invalid return wrapper default_expr" ) ,
708+ }
709+ }
710+
573711fn type_last_segment ( ty : & syn:: Type ) -> Option < String > {
574712 let syn:: Type :: Path ( tp) = ty else { return None } ;
575713 tp. path . segments . last ( ) . map ( |s| s. ident . to_string ( ) )
@@ -593,6 +731,18 @@ fn extract_fn_single_arg_type_name(
593731 None
594732}
595733
734+ /// Return the last-segment name of the single generic argument of an
735+ /// `Option<...>` path segment, if any (e.g. `Option<String>` → `Some("String")`).
736+ fn option_inner_type_name ( seg : & syn:: PathSegment ) -> Option < String > {
737+ let syn:: PathArguments :: AngleBracketed ( args) = & seg. arguments else {
738+ return None ;
739+ } ;
740+ let syn:: GenericArgument :: Type ( inner) = args. args . first ( ) ? else {
741+ return None ;
742+ } ;
743+ type_last_segment ( inner)
744+ }
745+
596746/// Check whether an `Option<...>` path segment wraps exactly `Vec<u8>`.
597747fn is_option_of_vec_u8 ( seg : & syn:: PathSegment ) -> bool {
598748 let syn:: PathArguments :: AngleBracketed ( args) = & seg. arguments else {
0 commit comments