more efforts to automate rhai bindings
This commit is contained in:
parent
16ad4f5743
commit
ec4769a6b0
515
_archive/lib.rs
Normal file
515
_archive/lib.rs
Normal file
@ -0,0 +1,515 @@
|
||||
// rhai_macros_derive/src/lib.rs
|
||||
|
||||
// We will add our derive macro implementations here.
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, format_ident, quote_spanned};
|
||||
use syn::{parse_macro_input, Ident, Type, ItemFn, spanned::Spanned, PathArguments, GenericArgument, DeriveInput, Data, LitStr};
|
||||
|
||||
// Old ToRhaiMap and FromRhaiMap definitions will be removed from here.
|
||||
// The export_fn macro definition starts after this.
|
||||
|
||||
// Trait definitions removed from here as proc-macro crates cannot export them.
|
||||
// They should be defined in a regular library crate (e.g., rhai_wrapper or a new rhai_traits crate).
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn export_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let func = parse_macro_input!(item as ItemFn);
|
||||
|
||||
let fn_name = &func.sig.ident;
|
||||
let wrapper_fn_name = Ident::new(&format!("rhai_wrapper_{}", fn_name), fn_name.span());
|
||||
let original_fn_generics = &func.sig.generics;
|
||||
let fn_inputs = &func.sig.inputs;
|
||||
// num_expected_args has been removed as Rhai handles arity with typed arguments
|
||||
|
||||
let mut arg_conversions: Vec<TokenStream2> = Vec::new(); // For let converted_arg_0 = ...
|
||||
let mut original_fn_call_args: Vec<TokenStream2> = Vec::new(); // For fn_name(converted_arg_0, ...)
|
||||
|
||||
let mut wrapper_args_dynamic_defs: Vec<TokenStream2> = Vec::new(); // Stores definitions like `wrapper_arg_0: ::rhai::Dynamic`
|
||||
|
||||
for (i, fn_arg) in fn_inputs.iter().enumerate() {
|
||||
if let syn::FnArg::Typed(pat_type) = fn_arg {
|
||||
let arg_ident_original_pat = match *pat_type.pat.clone() {
|
||||
syn::Pat::Ident(pat_ident) => pat_ident.ident,
|
||||
_ => panic!("#[export_fn] only supports simple identifiers as argument patterns, not complex patterns."),
|
||||
};
|
||||
let arg_ident_original_pat_str = arg_ident_original_pat.to_string();
|
||||
let wrapper_arg_name_ident = Ident::new(&format!("wrapper_arg_{}", i), pat_type.pat.span());
|
||||
let wrapper_arg_name_str = wrapper_arg_name_ident.to_string();
|
||||
|
||||
// Populate definitions for the wrapper function signature
|
||||
wrapper_args_dynamic_defs.push(quote! { #wrapper_arg_name_ident: ::rhai::Dynamic });
|
||||
|
||||
// Generate a unique ident for the converted argument variable
|
||||
let converted_rhai_arg_ident = Ident::new(&format!("converted_rhai_arg_{}", i), pat_type.pat.span());
|
||||
|
||||
let arg_rust_type = &*pat_type.ty;
|
||||
let arg_type_str = quote!(#arg_rust_type).to_string().replace(' ', "");
|
||||
|
||||
if let Type::Reference(ref type_ref) = *pat_type.ty {
|
||||
let is_mutable = type_ref.mutability.is_some();
|
||||
let referent_type = &*type_ref.elem;
|
||||
let lock_guard_ident = Ident::new(&format!("lock_guard_{}", i), pat_type.pat.span());
|
||||
|
||||
let conversion_logic = if is_mutable {
|
||||
quote! {
|
||||
let mut #lock_guard_ident = #wrapper_arg_name_ident.write_lock::<#referent_type>()
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be a mutable reference to {}. Ensure it's not already locked.",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i, stringify!(#referent_type)
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
let #converted_rhai_arg_ident = &mut *#lock_guard_ident;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let #lock_guard_ident = #wrapper_arg_name_ident.read_lock::<#referent_type>()
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be an immutable reference to {}.",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i, stringify!(#referent_type)
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
let #converted_rhai_arg_ident = &*#lock_guard_ident;
|
||||
}
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "&str" {
|
||||
let str_lock_guard_ident = Ident::new(&format!("str_lock_guard_{}", i), pat_type.pat.span());
|
||||
let conversion_logic = quote! {
|
||||
let #str_lock_guard_ident = #wrapper_arg_name_ident.read_lock::<rhai::ImmutableString>()
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type &str (requires read_lock on ImmutableString)",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
let #converted_rhai_arg_ident = #str_lock_guard_ident.as_str();
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "INT" || arg_type_str == "i64" {
|
||||
let conversion_logic = quote! {
|
||||
let int_option: Option<::rhai::INT> = #wrapper_arg_name_ident.as_int();
|
||||
let #converted_rhai_arg_ident = int_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type i64 (INT)",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "FLOAT" || arg_type_str == "f64" {
|
||||
let conversion_logic = quote! {
|
||||
let float_option: Option<::rhai::FLOAT> = #wrapper_arg_name_ident.as_float();
|
||||
let #converted_rhai_arg_ident = float_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type f64 (FLOAT)",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "bool" {
|
||||
let conversion_logic = quote! {
|
||||
let bool_option: Option<bool> = #wrapper_arg_name_ident.as_bool();
|
||||
let #converted_rhai_arg_ident = bool_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type bool",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "String" {
|
||||
let conversion_logic = quote! {
|
||||
let #converted_rhai_arg_ident = #wrapper_arg_name_ident.clone().into_string()
|
||||
.map_err(|_| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type String",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else {
|
||||
let owned_type_path = &*pat_type.ty; // No deref needed here, quote! can handle &Type
|
||||
let arg_type_for_conversion_code = quote!{#owned_type_path};
|
||||
let conversion_logic = quote! {
|
||||
let dyn_val_for_cast = #wrapper_arg_name_ident.clone(); // Clone for try_cast
|
||||
let actual_type_name = #wrapper_arg_name_ident.type_name();
|
||||
let #converted_rhai_arg_ident:#arg_type_for_conversion_code = match <#arg_type_for_conversion_code as std::convert::TryFrom<::rhai::Dynamic>>::try_from(dyn_val_for_cast) {
|
||||
Ok(val) => val,
|
||||
Err(_e) => {
|
||||
let cast_option: Option<#arg_type_for_conversion_code> = #wrapper_arg_name_ident.clone().try_cast::<#arg_type_for_conversion_code>();
|
||||
cast_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be convertible to type {}. TryFrom and try_cast failed.",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i, stringify!(#owned_type_path)
|
||||
),
|
||||
format!("but found type {}", actual_type_name),
|
||||
::rhai::Position::NONE
|
||||
)))?
|
||||
}
|
||||
};
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
}
|
||||
} else {
|
||||
// Handle receivers like self, &self, &mut self if necessary
|
||||
panic!("#[export_fn] does not support methods with 'self' receivers. Apply to static or free functions.");
|
||||
}
|
||||
}
|
||||
|
||||
let return_type = match &func.sig.output {
|
||||
syn::ReturnType::Default => quote!( () ),
|
||||
syn::ReturnType::Type(_, ty) => quote!( #ty ),
|
||||
};
|
||||
|
||||
// Determine the return type for the wrapper function's Result
|
||||
// If original is (), wrapper returns Dynamic (representing unit). Otherwise, original return type.
|
||||
let return_type_for_wrapper = if quote!(#return_type).to_string().replace(" ", "") == "()" {
|
||||
quote!(::rhai::Dynamic)
|
||||
} else {
|
||||
quote!(#return_type)
|
||||
};
|
||||
|
||||
// How to convert the result of the original function call into the wrapper's Ok Result.
|
||||
let return_conversion = if let syn::ReturnType::Type(_, _ty) = &func.sig.output {
|
||||
// If original function returns non-(), result is already #return_type_for_wrapper (which is #return_type)
|
||||
// So, it can be directly used.
|
||||
quote! { Ok(result) }
|
||||
} else {
|
||||
// If original function returns (), result is (), convert to Dynamic::UNIT for Rhai
|
||||
quote! { Ok(().into()) }
|
||||
};
|
||||
|
||||
let (impl_generics, _ty_generics, where_clause) = original_fn_generics.split_for_impl();
|
||||
|
||||
let expanded = quote! {
|
||||
#func // Re-emit the original function
|
||||
|
||||
// _cx is NativeCallContext. The arguments are now individual ::rhai::Dynamic types.
|
||||
pub fn #wrapper_fn_name #impl_generics (_cx: ::rhai::NativeCallContext, #(#wrapper_args_dynamic_defs),*) -> Result<#return_type_for_wrapper, Box<::rhai::EvalAltResult>> #where_clause {
|
||||
// Arity check is implicitly handled by Rhai's function registration for typed arguments.
|
||||
// If the script calls with wrong number of args, Rhai won't find a matching function signature.
|
||||
|
||||
#(#arg_conversions)*
|
||||
|
||||
// The following allow might be needed if the original function's result is directly usable
|
||||
// without explicit conversion to Dynamic, but the wrapper expects Dynamic for unit returns.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
let result = #fn_name(#(#original_fn_call_args),*);
|
||||
#return_conversion
|
||||
}
|
||||
};
|
||||
|
||||
// eprintln!("Generated code for {}:\n{}", stringify!(#fn_name), expanded.to_string());
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
|
||||
#[proc_macro_derive(FromRhaiMap, attributes(rhai_map_field))]
|
||||
pub fn derive_from_rhai_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = &input.ident;
|
||||
|
||||
// Helper function to check if a type is Option<T> and extract T
|
||||
// Returns (is_option, inner_type_quote_option)
|
||||
fn get_option_inner_type(ty: &Type) -> (bool, Option<&Type>) {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if type_path.path.segments.len() == 1 && type_path.path.segments.first().unwrap().ident == "Option" {
|
||||
if let PathArguments::AngleBracketed(params) = &type_path.path.segments.first().unwrap().arguments {
|
||||
if params.args.len() == 1 {
|
||||
if let GenericArgument::Type(inner_ty) = params.args.first().unwrap() {
|
||||
return (true, Some(inner_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(false, None)
|
||||
}
|
||||
|
||||
// Helper function to check if a type is Vec<T> and extract T
|
||||
// Returns (is_vec, inner_type_quote_option)
|
||||
fn get_vec_inner_type(ty: &Type) -> (bool, Option<&Type>) {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if type_path.path.segments.len() == 1 && type_path.path.segments.first().unwrap().ident == "Vec" {
|
||||
if let PathArguments::AngleBracketed(params) = &type_path.path.segments.first().unwrap().arguments {
|
||||
if params.args.len() == 1 {
|
||||
if let GenericArgument::Type(inner_ty) = params.args.first().unwrap() {
|
||||
return (true, Some(inner_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(false, None)
|
||||
}
|
||||
|
||||
// Helper to get the string representation of a type, simplified (e.g., "MyStruct", "String", "i64")
|
||||
fn get_simple_type_str(ty: &Type) -> String {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if let Some(segment) = type_path.path.segments.last() {
|
||||
return segment.ident.to_string();
|
||||
}
|
||||
}
|
||||
quote!(#ty).to_string().replace(' ', "").replace("::", "_") // fallback, might need refinement
|
||||
}
|
||||
|
||||
// Helper to check if a type is a primitive type based on its string representation
|
||||
fn is_primitive_type_str(simple_type_str: &str) -> bool {
|
||||
["String", "INT", "i64", "FLOAT", "f64", "bool"].contains(&simple_type_str)
|
||||
}
|
||||
|
||||
let fields_data = match &input.data {
|
||||
Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => &fields.named,
|
||||
_ => panic!("FromRhaiMapDerive only supports structs with named fields"),
|
||||
};
|
||||
|
||||
let mut field_value_declarations = Vec::new();
|
||||
let mut struct_field_assignments = Vec::new();
|
||||
|
||||
for field in fields_data.iter() {
|
||||
let field_name_ident = field.ident.as_ref().unwrap(); // Changed variable name for clarity
|
||||
let field_name_str = field_name_ident.to_string();
|
||||
let field_name_str_lit = LitStr::new(&field_name_str, field_name_ident.span()); // Corrected: Use LitStr for string literals
|
||||
let field_ty = &field.ty;
|
||||
let field_value_ident = format_ident!("__field_val_{}", field_name_str);
|
||||
|
||||
let (is_option, option_inner_ty_opt) = get_option_inner_type(field_ty);
|
||||
let type_for_vec_check = if is_option { option_inner_ty_opt.unwrap() } else { field_ty };
|
||||
let (is_vec, vec_inner_ty_opt) = get_vec_inner_type(type_for_vec_check);
|
||||
|
||||
let assignment_code = if is_option {
|
||||
let option_inner_ty = option_inner_ty_opt.expect("Option inner type not found");
|
||||
if is_vec { // Option<Vec<T>>
|
||||
let vec_element_ty = vec_inner_ty_opt.expect("Option<Vec<T>> inner T not found");
|
||||
let vec_element_ty_str = get_simple_type_str(vec_element_ty);
|
||||
|
||||
let element_conversion_logic = if is_primitive_type_str(&vec_element_ty_str) {
|
||||
quote! {
|
||||
let el_for_cast = el.clone();
|
||||
let actual_type_name = el.type_name();
|
||||
el_for_cast.try_cast::<#vec_element_ty>().ok_or_else(|| format!(
|
||||
"Element in Option<Vec> for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#vec_element_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = el.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for element in Option<Vec> for key '{}', got {}", #field_name_str_lit, el.type_name()))?;
|
||||
#vec_element_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
map.get(#field_name_str_lit).cloned().map(|dyn_val_array| {
|
||||
dyn_val_array.into_array().map_err(|e| e.to_string())?.into_iter().map(|el| {
|
||||
#element_conversion_logic
|
||||
}).collect::<Result<Vec<#vec_element_ty>, String>>()
|
||||
}).transpose()?
|
||||
}
|
||||
} else { // Option<T>
|
||||
let option_inner_ty_str = get_simple_type_str(option_inner_ty);
|
||||
let conversion_logic = if is_primitive_type_str(&option_inner_ty_str) {
|
||||
quote! {
|
||||
let val_for_cast = val.clone();
|
||||
let actual_type_name = val.type_name();
|
||||
val_for_cast.try_cast::<#option_inner_ty>().ok_or_else(|| format!(
|
||||
"Value in Option for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#option_inner_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = val.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for Option key '{}', got {}", #field_name_str_lit, val.type_name()))?;
|
||||
#option_inner_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
map.get(#field_name_str_lit).cloned().map(|val| {
|
||||
#conversion_logic
|
||||
}).transpose()?
|
||||
}
|
||||
}
|
||||
} else if is_vec { // Vec<T>
|
||||
let vec_inner_ty = vec_inner_ty_opt.expect("Vec inner type not found");
|
||||
let vec_inner_ty_str = get_simple_type_str(vec_inner_ty);
|
||||
|
||||
let element_conversion_logic = if is_primitive_type_str(&vec_inner_ty_str) {
|
||||
quote! {
|
||||
let el_for_cast = el.clone();
|
||||
let actual_type_name = el.type_name();
|
||||
el_for_cast.try_cast::<#vec_inner_ty>().ok_or_else(|| format!(
|
||||
"Element in Vec for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#vec_inner_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = el.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for element in Vec for key '{}', got {}", #field_name_str_lit, el.type_name()))?;
|
||||
#vec_inner_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
|
||||
quote! {{
|
||||
let dyn_val_array = map.get(#field_name_str_lit).cloned()
|
||||
.ok_or_else(|| format!("Key '{}' not found for Vec", #field_name_str_lit))?
|
||||
.into_array().map_err(|e| e.to_string())?;
|
||||
dyn_val_array.into_iter().map(|el| {
|
||||
#element_conversion_logic
|
||||
}).collect::<Result<Vec<#vec_inner_ty>, String>>()?
|
||||
}}
|
||||
} else { // Direct field T
|
||||
let field_ty_str = get_simple_type_str(field_ty);
|
||||
let conversion_logic = if is_primitive_type_str(&field_ty_str) {
|
||||
quote! {
|
||||
let dyn_val_for_cast = dyn_val_cloned.clone();
|
||||
let actual_type_name = dyn_val_cloned.type_name();
|
||||
dyn_val_for_cast.try_cast::<#field_ty>().ok_or_else(|| format!(
|
||||
"Value in map for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#field_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = dyn_val_cloned.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for key '{}', got {}", #field_name_str_lit, dyn_val_cloned.type_name()))?;
|
||||
#field_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
quote! {{
|
||||
let dyn_val_cloned = map.get(#field_name_str_lit).cloned()
|
||||
.ok_or_else(|| format!("Key '{}' not found", #field_name_str_lit))?;
|
||||
#conversion_logic?
|
||||
}}
|
||||
};
|
||||
|
||||
field_value_declarations.push(quote! { let #field_value_ident = #assignment_code; });
|
||||
struct_field_assignments.push(quote_spanned!(field_name_ident.span()=> #field_name_ident: #field_value_ident));
|
||||
}
|
||||
|
||||
let gen = quote! {
|
||||
impl #name {
|
||||
pub fn from_rhai_map(map: ::rhai::Map) -> Result<Self, String> {
|
||||
#(#field_value_declarations)*
|
||||
Ok(Self {
|
||||
#(#struct_field_assignments),*
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
proc_macro::TokenStream::from(gen)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ToRhaiMap)]
|
||||
pub fn derive_to_rhai_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
let name = &ast.ident;
|
||||
|
||||
let mut field_insertions = Vec::new();
|
||||
|
||||
if let syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) = ast.data {
|
||||
for field in fields.named {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let field_name_str = field_name.to_string();
|
||||
let ty = &field.ty;
|
||||
|
||||
let mut _is_vec_of_custom_struct = false;
|
||||
let mut is_direct_custom_struct = false;
|
||||
|
||||
let _field_type_name_for_logic = quote!(#ty).to_string().replace(" ", "");
|
||||
|
||||
if let syn::Type::Path(type_path) = ty {
|
||||
if type_path.path.segments.len() == 1 && type_path.path.segments.first().unwrap().ident == "Vec" {
|
||||
if let syn::PathArguments::AngleBracketed(angle_bracketed_args) = &type_path.path.segments.first().unwrap().arguments {
|
||||
if let Some(syn::GenericArgument::Type(_inner_ty_syn @ syn::Type::Path(inner_type_path))) = angle_bracketed_args.args.first() {
|
||||
if let Some(inner_segment) = inner_type_path.path.segments.last() {
|
||||
let inner_type_str = inner_segment.ident.to_string();
|
||||
if !["String", "INT", "i64", "FLOAT", "f64", "bool"].contains(&inner_type_str.as_str()) {
|
||||
_is_vec_of_custom_struct = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !["String", "INT", "i64", "FLOAT", "f64", "bool"].contains(&type_path.path.segments.first().unwrap().ident.to_string().as_str()) {
|
||||
// It's not a Vec, and not a primitive, so assume it's a direct custom struct
|
||||
is_direct_custom_struct = true;
|
||||
}
|
||||
}
|
||||
|
||||
if _is_vec_of_custom_struct {
|
||||
field_insertions.push(quote! {
|
||||
{
|
||||
let rhai_array: rhai::Array = self.#field_name.iter().map(|item| {
|
||||
// item is &CustomStruct, to_rhai_map() takes &self
|
||||
rhai::Dynamic::from(item.to_rhai_map())
|
||||
}).collect();
|
||||
map.insert(#field_name_str.into(), rhai::Dynamic::from(rhai_array));
|
||||
}
|
||||
});
|
||||
// FIX: Logic for direct custom struct field
|
||||
} else if is_direct_custom_struct {
|
||||
field_insertions.push(quote! {
|
||||
map.insert(#field_name_str.into(), rhai::Dynamic::from(self.#field_name.to_rhai_map()));
|
||||
});
|
||||
} else {
|
||||
// This handles Vec<Primitive> and direct Primitives
|
||||
let type_str = quote!(#ty).to_string().replace(" ", "");
|
||||
if type_str.starts_with("Vec<") {
|
||||
field_insertions.push(quote! {
|
||||
{
|
||||
let rhai_array: rhai::Array = self.#field_name.iter().map(|item| {
|
||||
// item is &Primitive, clone it for .into()
|
||||
item.clone().into()
|
||||
}).collect();
|
||||
map.insert(#field_name_str.into(), rhai::Dynamic::from(rhai_array));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Direct primitive field
|
||||
field_insertions.push(quote! {
|
||||
map.insert(#field_name_str.into(), self.#field_name.clone().into());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
|
||||
|
||||
let expanded = quote! {
|
||||
impl #impl_generics ToRhaiMap for #name #ty_generics #where_clause {
|
||||
fn to_rhai_map(&self) -> rhai::Map {
|
||||
let mut map = rhai::Map::new();
|
||||
#(#field_insertions)*
|
||||
map
|
||||
}
|
||||
}
|
||||
};
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
// Rest of the code remains the same
|
54
rhai_autobind_macros/Cargo.lock
generated
Normal file
54
rhai_autobind_macros/Cargo.lock
generated
Normal file
@ -0,0 +1,54 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_autobind_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
13
rhai_autobind_macros/Cargo.toml
Normal file
13
rhai_autobind_macros/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "rhai_autobind_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
heck = "0.4"
|
@ -0,0 +1,33 @@
|
||||
// calculator.rhai
|
||||
// This script demonstrates using the Calculator struct from Rust in Rhai
|
||||
|
||||
// Create a new calculator
|
||||
let calc = new_calculator();
|
||||
println("Created a new calculator with value: " + calc.value);
|
||||
|
||||
// Perform some calculations
|
||||
calc.add(5);
|
||||
println("After adding 5: " + calc.value);
|
||||
|
||||
calc.multiply(2);
|
||||
println("After multiplying by 2: " + calc.value);
|
||||
|
||||
calc.subtract(3);
|
||||
println("After subtracting 3: " + calc.value);
|
||||
|
||||
calc.divide(2);
|
||||
println("After dividing by 2: " + calc.value);
|
||||
|
||||
// Set value directly
|
||||
calc.value = 100;
|
||||
println("After setting value to 100: " + calc.value);
|
||||
|
||||
// Clear the calculator
|
||||
calc.clear();
|
||||
println("After clearing: " + calc.value);
|
||||
|
||||
// Chain operations
|
||||
let result = calc.add(10).multiply(2).subtract(5).divide(3);
|
||||
println("Result of chained operations: " + result);
|
||||
|
||||
println("Final calculator value: " + calc.value);
|
90
rhai_autobind_macros/examples/rhai_autobind_example/main.rs
Normal file
90
rhai_autobind_macros/examples/rhai_autobind_example/main.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai_autobind_macros::rhai_autobind;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
// Define a simple Calculator struct with the rhai_autobind attribute
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, rhai::CustomType)]
|
||||
#[rhai_autobind]
|
||||
pub struct Calculator {
|
||||
pub value: f64,
|
||||
}
|
||||
|
||||
impl Calculator {
|
||||
// Constructor
|
||||
pub fn new() -> Self {
|
||||
Calculator { value: 0.0 }
|
||||
}
|
||||
|
||||
// Add method
|
||||
pub fn add(&mut self, x: f64) -> f64 {
|
||||
self.value += x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Subtract method
|
||||
pub fn subtract(&mut self, x: f64) -> f64 {
|
||||
self.value -= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Multiply method
|
||||
pub fn multiply(&mut self, x: f64) -> f64 {
|
||||
self.value *= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Divide method
|
||||
pub fn divide(&mut self, x: f64) -> f64 {
|
||||
if x == 0.0 {
|
||||
println!("Error: Division by zero!");
|
||||
return self.value;
|
||||
}
|
||||
self.value /= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Clear method
|
||||
pub fn clear(&mut self) -> f64 {
|
||||
self.value = 0.0;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Get value method
|
||||
pub fn get_value(&self) -> f64 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
println!("Rhai Calculator Example");
|
||||
println!("======================");
|
||||
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register the Calculator type with the engine using the generated function
|
||||
Calculator::register_rhai_bindings_for_calculator(&mut engine);
|
||||
|
||||
// Register print function for output
|
||||
engine.register_fn("println", |s: &str| println!("{}", s));
|
||||
|
||||
// Create a calculator instance to demonstrate it works
|
||||
let calc = Calculator::new();
|
||||
println!("Initial value: {}", calc.value);
|
||||
|
||||
// Load and run the Rhai script
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("examples")
|
||||
.join("rhai_autobind_example")
|
||||
.join("calculator.rhai");
|
||||
|
||||
println!("Loading Rhai script from: {}", script_path.display());
|
||||
|
||||
match engine.eval_file::<()>(script_path) {
|
||||
Ok(_) => println!("Script executed successfully"),
|
||||
Err(e) => eprintln!("Error executing script: {}\nAt: {:?}", e, e.position()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
187
rhai_autobind_macros/src/lib.rs
Normal file
187
rhai_autobind_macros/src/lib.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, format_ident};
|
||||
use syn::{parse_macro_input, DeriveInput, Ident, Type, Fields, Visibility, ItemStruct, LitStr};
|
||||
|
||||
/// A procedural macro to automatically generate Rhai bindings for a struct.
|
||||
///
|
||||
/// This macro will generate an `impl` block for the annotated struct containing
|
||||
/// a static method, typically named `register_rhai_bindings_for_<struct_name_lowercase>`,
|
||||
/// which registers the struct, its fields (as getters), its methods, and common
|
||||
/// database operations with a Rhai `Engine`.
|
||||
///
|
||||
/// # Example Usage
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // Assuming MyDb is your database connection type
|
||||
/// // and it's wrapped in an Arc for sharing with Rhai.
|
||||
/// use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
/// use rhai_autobind_macros::rhai_model_export;
|
||||
///
|
||||
/// // Define your database type (placeholder)
|
||||
/// struct MyDb;
|
||||
/// impl MyDb {
|
||||
/// fn new() -> Self { MyDb }
|
||||
/// }
|
||||
///
|
||||
/// #[rhai_model_export(db_type = "Arc<MyDb>")]
|
||||
/// pub struct User {
|
||||
/// pub id: i64,
|
||||
/// pub name: String,
|
||||
/// }
|
||||
///
|
||||
/// impl User {
|
||||
/// // A constructor or builder Rhai can use
|
||||
/// pub fn new(id: i64, name: String) -> Self {
|
||||
/// Self { id, name }
|
||||
/// }
|
||||
///
|
||||
/// // An example method
|
||||
/// pub fn greet(&self) -> String {
|
||||
/// format!("Hello, {}!", self.name)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let mut engine = Engine::new();
|
||||
/// let db_instance = Arc::new(MyDb::new());
|
||||
///
|
||||
/// // The generated function is called here
|
||||
/// User::register_rhai_bindings_for_user(&mut engine, db_instance);
|
||||
///
|
||||
/// // Now you can use User in Rhai scripts
|
||||
/// // let user = User::new(1, "Test User");
|
||||
/// // print(user.name);
|
||||
/// // print(user.greet());
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Macro Attributes
|
||||
///
|
||||
/// - `db_type = "path::to::YourDbType"`: **Required**. Specifies the fully qualified
|
||||
/// Rust type of your database connection/handler that will be passed to the
|
||||
/// generated registration function and used for database operations.
|
||||
/// The type should be enclosed in string quotes, e.g., `"Arc<crate::db::MyDatabase>"`.
|
||||
///
|
||||
/// - `collection_name = "your_collection_name"`: **Optional**. Specifies the name of the
|
||||
/// database collection associated with this model. If not provided, it defaults
|
||||
/// to the struct name converted to snake_case and pluralized (e.g., `User` -> `"users"`).
|
||||
/// Enclose in string quotes, e.g., `"user_profiles"`.
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MacroArgs {
|
||||
db_type: syn::Type,
|
||||
collection_name: Option<String>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for MacroArgs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut db_type_str: Option<LitStr> = None;
|
||||
let mut collection_name_str: Option<LitStr> = None;
|
||||
|
||||
while !input.is_empty() {
|
||||
let ident: Ident = input.parse()?;
|
||||
let _eq_token: syn::token::Eq = input.parse()?;
|
||||
if ident == "db_type" {
|
||||
db_type_str = Some(input.parse()?);
|
||||
}
|
||||
else if ident == "collection_name" {
|
||||
collection_name_str = Some(input.parse()?);
|
||||
}
|
||||
else {
|
||||
return Err(syn::Error::new(ident.span(), "Unknown attribute argument"));
|
||||
}
|
||||
if !input.is_empty() {
|
||||
let _comma: syn::token::Comma = input.parse()?;
|
||||
}
|
||||
}
|
||||
|
||||
let db_type_str = db_type_str.ok_or_else(|| syn::Error::new(input.span(), "Missing required attribute `db_type`"))?;
|
||||
let db_type: syn::Type = syn::parse_str(&db_type_str.value())?;
|
||||
let collection_name = collection_name_str.map(|s| s.value());
|
||||
|
||||
Ok(MacroArgs { db_type, collection_name })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn rhai_model_export(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let macro_args = parse_macro_input!(attr as MacroArgs);
|
||||
let input_struct = parse_macro_input!(item as ItemStruct);
|
||||
|
||||
let struct_name = &input_struct.ident;
|
||||
let struct_name_str = struct_name.to_string();
|
||||
let struct_name_lowercase = struct_name_str.to_lowercase();
|
||||
|
||||
let db_type = macro_args.db_type;
|
||||
let collection_name = macro_args.collection_name.unwrap_or_else(|| {
|
||||
// Basic pluralization, could use `heck` or `inflector` for better results
|
||||
if struct_name_lowercase.ends_with('y') {
|
||||
format!("{}ies", &struct_name_lowercase[..struct_name_lowercase.len()-1])
|
||||
} else if struct_name_lowercase.ends_with('s') {
|
||||
format!("{}es", struct_name_lowercase)
|
||||
} else {
|
||||
format!("{}s", struct_name_lowercase)
|
||||
}
|
||||
});
|
||||
|
||||
let generated_registration_fn_name = format_ident!("register_rhai_bindings_for_{}", struct_name_lowercase);
|
||||
|
||||
// Placeholder for generated getters and method registrations
|
||||
let mut field_getters = Vec::new();
|
||||
if let Fields::Named(fields) = &input_struct.fields {
|
||||
for field in fields.named.iter() {
|
||||
if let (Some(field_name), Visibility::Public(_)) = (&field.ident, &field.vis) {
|
||||
let field_name_str = field_name.to_string();
|
||||
let getter_fn = quote! {
|
||||
engine.register_get(#field_name_str, |s: &mut #struct_name| s.#field_name.clone());
|
||||
// TODO: Handle non-cloneable types or provide options
|
||||
};
|
||||
field_getters.push(getter_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Parse methods from the struct's impl blocks (this is complex)
|
||||
// TODO: Generate wrappers for DB operations using rhai_wrapper macros
|
||||
|
||||
let output = quote! {
|
||||
#input_struct // Re-emit the original struct definition
|
||||
|
||||
impl #struct_name {
|
||||
pub fn #generated_registration_fn_name(
|
||||
engine: &mut rhai::Engine,
|
||||
db: #db_type
|
||||
) {
|
||||
// Bring rhai_wrapper into scope for generated DB functions later
|
||||
// Users might need to ensure rhai_wrapper is a dependency of their crate.
|
||||
// use rhai_wrapper::*;
|
||||
|
||||
engine.build_type::<#struct_name>();
|
||||
|
||||
// Register getters
|
||||
#(#field_getters)*
|
||||
|
||||
// Placeholder for constructor registration
|
||||
// Example: engine.register_fn("new_user", User::new);
|
||||
|
||||
// Placeholder for method registration
|
||||
// Example: engine.register_fn("greet", User::greet);
|
||||
|
||||
// Placeholder for DB function registrations
|
||||
// e.g., get_by_id, get_all, save, delete
|
||||
// let get_by_id_fn_name = format!("get_{}_by_id", #struct_name_lowercase);
|
||||
// engine.register_fn(&get_by_id_fn_name, ...wrap_option_method_result!(...));
|
||||
|
||||
println!("Registered {} with Rhai (collection: '{}') using DB type {}",
|
||||
#struct_name_str,
|
||||
#collection_name,
|
||||
stringify!(#db_type)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(output)
|
||||
}
|
242
rhai_wrapper/Cargo.lock
generated
242
rhai_wrapper/Cargo.lock
generated
@ -16,6 +16,21 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
@ -28,12 +43,42 @@ version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
@ -54,6 +99,12 @@ dependencies = [
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
@ -83,6 +134,30 @@ dependencies = [
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
@ -92,12 +167,28 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@ -187,10 +278,44 @@ dependencies = [
|
||||
name = "rhai_wrapper"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"rhai",
|
||||
"rhai_macros_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
@ -267,6 +392,123 @@ dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
|
@ -6,7 +6,17 @@ edition = "2021"
|
||||
description = "A wrapper to make generic Rust functions Rhai-compatible."
|
||||
|
||||
[dependencies]
|
||||
rhai = "1.21.0"
|
||||
rhai = "1.18.0"
|
||||
rhai_macros_derive = { path = "../rhai_macros_derive" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[[example]]
|
||||
name = "user_management_example"
|
||||
path = "examples/user_management_example.rs"
|
||||
|
||||
[[example]]
|
||||
name = "rust_rhai_wrapper_example"
|
||||
path = "examples/rust_rhai_wrapper_example.rs"
|
||||
|
176
rhai_wrapper/examples/rust_rhai_wrapper_example.rs
Normal file
176
rhai_wrapper/examples/rust_rhai_wrapper_example.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use rhai::Engine;
|
||||
use rhai_wrapper::rust_rhai_wrapper;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
// Define a custom error type for Result examples
|
||||
#[derive(Debug, Clone)]
|
||||
struct MyError(String);
|
||||
|
||||
impl fmt::Display for MyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for MyError {}
|
||||
|
||||
impl From<String> for MyError {
|
||||
fn from(s: String) -> Self {
|
||||
MyError(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Create a Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// 1. Basic example: Add two numbers
|
||||
// Define the original Rust function
|
||||
fn add(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
// Register it with Rhai
|
||||
engine.register_fn("add_rhai", add);
|
||||
|
||||
// Create a wrapper function that calls Rhai which calls the Rust function
|
||||
rust_rhai_wrapper!(add_via_rhai, "add_rhai", (i32, i32) -> i32);
|
||||
|
||||
// Test the full circle
|
||||
let result = add_via_rhai(&mut engine, 5, 3);
|
||||
println!("add_via_rhai(5, 3) = {}", result);
|
||||
|
||||
// 2. String manipulation example
|
||||
fn concat(s1: String, s2: String) -> String {
|
||||
format!("{} {}", s1, s2)
|
||||
}
|
||||
|
||||
engine.register_fn("concat_rhai", concat);
|
||||
rust_rhai_wrapper!(concat_via_rhai, "concat_rhai", (String, String) -> String);
|
||||
|
||||
let result = concat_via_rhai(&mut engine, "Hello".to_string(), "World".to_string());
|
||||
println!("concat_via_rhai(\"Hello\", \"World\") = {}", result);
|
||||
|
||||
// 3. Function with no arguments
|
||||
fn get_random() -> i32 {
|
||||
42 // Not so random, but it's just an example
|
||||
}
|
||||
|
||||
engine.register_fn("get_random_rhai", get_random);
|
||||
rust_rhai_wrapper!(get_random_via_rhai, "get_random_rhai", () -> i32);
|
||||
|
||||
let result = get_random_via_rhai(&mut engine);
|
||||
println!("get_random_via_rhai() = {}", result);
|
||||
|
||||
// 4. Function with more arguments
|
||||
fn calculate(a: i32, b: i32, c: i32, d: i32) -> i32 {
|
||||
a + b * c - d
|
||||
}
|
||||
|
||||
engine.register_fn("calculate_rhai", calculate);
|
||||
rust_rhai_wrapper!(calculate_via_rhai, "calculate_rhai", (i32, i32, i32, i32) -> i32);
|
||||
|
||||
let result = calculate_via_rhai(&mut engine, 5, 3, 2, 1);
|
||||
println!("calculate_via_rhai(5, 3, 2, 1) = {}", result);
|
||||
|
||||
// 5. Function that handles errors with a custom return type
|
||||
fn divide(a: i32, b: i32) -> Result<i32, String> {
|
||||
if b == 0 {
|
||||
Err("Division by zero".to_string())
|
||||
} else {
|
||||
Ok(a / b)
|
||||
}
|
||||
}
|
||||
|
||||
// Register a safe division function that returns an array with success flag and result/error
|
||||
engine.register_fn("safe_divide", |a: i32, b: i32| -> rhai::Array {
|
||||
if b == 0 {
|
||||
// Return [false, error message]
|
||||
let mut arr = rhai::Array::new();
|
||||
arr.push(rhai::Dynamic::from(false));
|
||||
arr.push(rhai::Dynamic::from("Division by zero"));
|
||||
arr
|
||||
} else {
|
||||
// Return [true, result]
|
||||
let mut arr = rhai::Array::new();
|
||||
arr.push(rhai::Dynamic::from(true));
|
||||
arr.push(rhai::Dynamic::from(a / b));
|
||||
arr
|
||||
}
|
||||
});
|
||||
|
||||
// Create a wrapper for the safe_divide function
|
||||
rust_rhai_wrapper!(safe_divide_via_rhai, "safe_divide", (i32, i32) -> rhai::Array);
|
||||
|
||||
// Test success case
|
||||
let result = safe_divide_via_rhai(&mut engine, 10, 2);
|
||||
println!("safe_divide_via_rhai(10, 2) = {:?}", result);
|
||||
|
||||
// Test error case
|
||||
let result = safe_divide_via_rhai(&mut engine, 10, 0);
|
||||
println!("safe_divide_via_rhai(10, 0) = {:?}", result);
|
||||
|
||||
// Process the result
|
||||
let success = result[0].as_bool().unwrap();
|
||||
if success {
|
||||
println!("Division result: {}", result[1].as_int().unwrap());
|
||||
} else {
|
||||
println!("Division error: {}", result[1].clone().into_string().unwrap());
|
||||
}
|
||||
|
||||
// 6. Complex example: Using a custom type with Rhai
|
||||
#[derive(Debug, Clone)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
}
|
||||
|
||||
// Register type and methods with Rhai
|
||||
engine.register_type::<Person>();
|
||||
engine.register_fn("new_person", |name: String, age: i32| -> Person {
|
||||
Person { name, age }
|
||||
});
|
||||
engine.register_fn("get_name", |p: &mut Person| -> String {
|
||||
p.name.clone()
|
||||
});
|
||||
engine.register_fn("get_age", |p: &mut Person| -> i32 {
|
||||
p.age
|
||||
});
|
||||
engine.register_fn("is_adult", |p: &mut Person| -> bool {
|
||||
p.age >= 18
|
||||
});
|
||||
|
||||
// Register a function that creates a person and checks if they're an adult
|
||||
engine.register_fn("create_and_check_person", |name: String, age: i32| -> rhai::Array {
|
||||
let person = Person { name, age };
|
||||
let is_adult = person.age >= 18;
|
||||
|
||||
// Create an array with the person and the is_adult flag
|
||||
let mut arr = rhai::Array::new();
|
||||
|
||||
// Convert the person to a map for Rhai
|
||||
let mut map = rhai::Map::new();
|
||||
map.insert("name".into(), rhai::Dynamic::from(person.name));
|
||||
map.insert("age".into(), rhai::Dynamic::from(person.age));
|
||||
|
||||
arr.push(rhai::Dynamic::from(map));
|
||||
arr.push(rhai::Dynamic::from(is_adult));
|
||||
arr
|
||||
});
|
||||
|
||||
// Create a wrapper for the Rhai function
|
||||
rust_rhai_wrapper!(create_person_via_rhai, "create_and_check_person", (String, i32) -> rhai::Array);
|
||||
|
||||
// Test the wrapper
|
||||
let result = create_person_via_rhai(&mut engine, "Alice".to_string(), 25);
|
||||
println!("create_person_via_rhai(\"Alice\", 25) = {:?}", result);
|
||||
|
||||
// Extract data from the Rhai array
|
||||
let person_map = result[0].clone().try_cast::<rhai::Map>().expect("Expected a Map");
|
||||
let is_adult = result[1].clone().as_bool().expect("Expected a boolean");
|
||||
|
||||
println!("Person: {:?}, Is Adult: {}", person_map, is_adult);
|
||||
|
||||
println!("All examples completed successfully!");
|
||||
}
|
312
rhai_wrapper/examples/user_management_example.rs
Normal file
312
rhai_wrapper/examples/user_management_example.rs
Normal file
@ -0,0 +1,312 @@
|
||||
use rhai::{Engine, INT, CustomType, TypeBuilder};
|
||||
use rhai_wrapper::{wrap_option_return, wrap_vec_return};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::path::Path;
|
||||
use chrono;
|
||||
|
||||
// --- Mock heromodels_core ---
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
|
||||
pub struct BaseModelData {
|
||||
pub id: u32,
|
||||
pub created_at: i64, // Using i64 for timestamp simplicity
|
||||
pub updated_at: i64,
|
||||
pub comment_ids: Vec<u32>,
|
||||
}
|
||||
|
||||
impl BaseModelData {
|
||||
pub fn new(id: u32) -> Self {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
Self {
|
||||
id,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
comment_ids: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// No &mut self for getters if struct is Clone and passed by value in Rhai, or if Rhai handles it.
|
||||
// For CustomType, Rhai typically passes &mut T to getters/setters.
|
||||
pub fn get_id(&mut self) -> u32 { self.id }
|
||||
pub fn get_created_at(&mut self) -> i64 { self.created_at }
|
||||
pub fn get_updated_at(&mut self) -> i64 { self.updated_at }
|
||||
pub fn get_comment_ids(&mut self) -> Vec<u32> { self.comment_ids.clone() }
|
||||
|
||||
pub fn add_comment_internal(&mut self, comment_id: u32) { // Renamed to avoid clash if also exposed
|
||||
self.comment_ids.push(comment_id);
|
||||
self.updated_at = chrono::Utc::now().timestamp();
|
||||
}
|
||||
}
|
||||
|
||||
// --- User Struct and Methods (Adapted for Rhai) ---
|
||||
|
||||
/// Represents a user in the system
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
|
||||
pub struct User {
|
||||
/// Base model data
|
||||
pub base_data: BaseModelData,
|
||||
|
||||
/// User's username
|
||||
pub username: String,
|
||||
|
||||
/// User's email address
|
||||
pub email: String,
|
||||
|
||||
/// User's full name
|
||||
pub full_name: String,
|
||||
|
||||
/// Whether the user is active
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
impl User {
|
||||
// This is the "builder" entry point
|
||||
pub fn user_builder(id: INT) -> Self {
|
||||
Self {
|
||||
base_data: BaseModelData::new(id as u32),
|
||||
username: String::new(),
|
||||
email: String::new(),
|
||||
full_name: String::new(),
|
||||
is_active: true, // Default, can be changed by .is_active(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Fluent setters returning Self
|
||||
pub fn username(mut self, username: String) -> Self {
|
||||
self.username = username;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn email(mut self, email: String) -> Self {
|
||||
self.email = email;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn full_name(mut self, full_name: String) -> Self {
|
||||
self.full_name = full_name;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
// Naming this 'set_is_active' to distinguish from potential getter 'is_active'
|
||||
// or the script can use direct field access if setter is also registered for 'is_active'
|
||||
// For fluent chain .is_active(bool_val)
|
||||
pub fn is_active(mut self, active_status: bool) -> Self {
|
||||
self.is_active = active_status;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
// Method to add a comment, distinct from direct field manipulation
|
||||
pub fn add_comment(mut self, comment_id: INT) -> Self {
|
||||
self.base_data.add_comment_internal(comment_id as u32);
|
||||
self
|
||||
}
|
||||
|
||||
// Explicit activate/deactivate methods returning Self for chaining if needed
|
||||
pub fn activate(mut self) -> Self {
|
||||
self.is_active = true;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn deactivate(mut self) -> Self {
|
||||
self.is_active = false;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
// Getters for direct field access from Rhai: register with .register_get()
|
||||
// Rhai passes &mut User to these
|
||||
pub fn get_id_rhai(&mut self) -> INT { self.base_data.id as INT }
|
||||
pub fn get_username_rhai(&mut self) -> String { self.username.clone() }
|
||||
pub fn get_email_rhai(&mut self) -> String { self.email.clone() }
|
||||
pub fn get_full_name_rhai(&mut self) -> String { self.full_name.clone() }
|
||||
pub fn get_is_active_rhai(&mut self) -> bool { self.is_active }
|
||||
pub fn get_comment_ids_rhai(&mut self) -> Vec<u32> { self.base_data.comment_ids.clone() }
|
||||
}
|
||||
|
||||
|
||||
// --- Comment Struct and Methods (Adapted for Rhai) ---
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
|
||||
pub struct Comment {
|
||||
pub id: INT,
|
||||
pub user_id: INT, // Assuming comments are linked to users by ID
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl Comment {
|
||||
pub fn comment_builder(id: INT) -> Self {
|
||||
Self {
|
||||
id,
|
||||
user_id: 0, // Default
|
||||
content: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Fluent setters
|
||||
pub fn user_id(mut self, user_id: INT) -> Self {
|
||||
self.user_id = user_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn content(mut self, content: String) -> Self {
|
||||
self.content = content;
|
||||
self
|
||||
}
|
||||
|
||||
// Getters for Rhai
|
||||
pub fn get_id_rhai(&mut self) -> INT { self.id }
|
||||
pub fn get_user_id_rhai(&mut self) -> INT { self.user_id }
|
||||
pub fn get_content_rhai(&mut self) -> String { self.content.clone() }
|
||||
}
|
||||
|
||||
// --- Mock Database ---
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct DbState {
|
||||
users: HashMap<INT, User>,
|
||||
comments: HashMap<INT, Comment>,
|
||||
}
|
||||
|
||||
type OurDB = Arc<Mutex<DbState>>;
|
||||
|
||||
fn set_user(db_arc: OurDB, user: User) {
|
||||
let mut db = db_arc.lock().unwrap();
|
||||
db.users.insert(user.base_data.id as INT, user);
|
||||
}
|
||||
|
||||
fn get_user_by_id(db_arc: OurDB, id: INT) -> Option<User> {
|
||||
let db = db_arc.lock().unwrap();
|
||||
db.users.get(&id).cloned()
|
||||
}
|
||||
|
||||
fn get_all_users(db_arc: OurDB) -> Vec<User> {
|
||||
let db = db_arc.lock().unwrap();
|
||||
db.users.values().cloned().collect()
|
||||
}
|
||||
|
||||
fn delete_user_by_id(db_arc: OurDB, id: INT) {
|
||||
let mut db = db_arc.lock().unwrap();
|
||||
db.users.remove(&id);
|
||||
}
|
||||
|
||||
fn set_comment(db_arc: OurDB, comment: Comment) {
|
||||
let mut db = db_arc.lock().unwrap();
|
||||
db.comments.insert(comment.id, comment);
|
||||
}
|
||||
|
||||
fn get_comment_by_id(db_arc: OurDB, id: INT) -> Option<Comment> {
|
||||
let db = db_arc.lock().unwrap();
|
||||
db.comments.get(&id).cloned()
|
||||
}
|
||||
|
||||
fn get_users_by_activity_status_optional(db_arc: OurDB, is_active_filter: bool) -> Option<Vec<User>> {
|
||||
let db = db_arc.lock().unwrap();
|
||||
let users: Vec<User> = db.users.values()
|
||||
.filter(|u| u.is_active == is_active_filter)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if users.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(users)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let db_instance: OurDB = Arc::new(Mutex::new(DbState::default()));
|
||||
|
||||
// Register User type and its methods/getters/setters
|
||||
engine
|
||||
.register_type_with_name::<User>("User")
|
||||
// Fluent methods - these are registered as functions that take User and return User
|
||||
.register_fn("user_builder", User::user_builder)
|
||||
.register_fn("username", User::username)
|
||||
.register_fn("email", User::email)
|
||||
.register_fn("full_name", User::full_name)
|
||||
.register_fn("is_active", User::is_active) // This is the fluent setter
|
||||
.register_fn("add_comment", User::add_comment)
|
||||
.register_fn("activate", User::activate)
|
||||
.register_fn("deactivate", User::deactivate)
|
||||
// Getters for direct field-like access: user.id, user.username etc.
|
||||
.register_get("id", User::get_id_rhai)
|
||||
.register_get("username", User::get_username_rhai)
|
||||
.register_get("email", User::get_email_rhai)
|
||||
.register_get("full_name", User::get_full_name_rhai)
|
||||
.register_get("is_active", User::get_is_active_rhai) // This is the getter for direct field access
|
||||
.register_get("comment_ids", User::get_comment_ids_rhai);
|
||||
|
||||
// Register Comment type and its methods/getters/setters
|
||||
engine
|
||||
.register_type_with_name::<Comment>("Comment")
|
||||
.register_fn("comment_builder", Comment::comment_builder)
|
||||
.register_fn("user_id", Comment::user_id)
|
||||
.register_fn("content", Comment::content)
|
||||
.register_get("id", Comment::get_id_rhai)
|
||||
.register_get("user_id", Comment::get_user_id_rhai)
|
||||
.register_get("content", Comment::get_content_rhai);
|
||||
|
||||
// DB functions - now directly registered
|
||||
let db_clone_for_get_db = db_instance.clone();
|
||||
engine.register_fn("get_db", move || db_clone_for_get_db.clone());
|
||||
|
||||
let db_clone_for_set_user = db_instance.clone();
|
||||
engine.register_fn("set_user", move |user: User| set_user(db_clone_for_set_user.clone(), user));
|
||||
|
||||
let db_clone_for_get_user_by_id = db_instance.clone();
|
||||
engine.register_fn("get_user_by_id", move |id: INT| {
|
||||
wrap_option_return!(get_user_by_id, OurDB, INT => User)(db_clone_for_get_user_by_id.clone(), id)
|
||||
});
|
||||
|
||||
let db_clone_for_get_all_users = db_instance.clone();
|
||||
engine.register_fn(
|
||||
"get_all_users",
|
||||
move || {
|
||||
(wrap_vec_return!(get_all_users, OurDB => User))(db_clone_for_get_all_users.clone())
|
||||
}
|
||||
);
|
||||
|
||||
let db_clone_for_delete_user = db_instance.clone();
|
||||
engine.register_fn("delete_user_by_id", move |id: INT| delete_user_by_id(db_clone_for_delete_user.clone(), id));
|
||||
|
||||
let db_clone_for_set_comment = db_instance.clone();
|
||||
engine.register_fn("set_comment", move |comment: Comment| set_comment(db_clone_for_set_comment.clone(), comment));
|
||||
|
||||
let db_clone_for_get_comment_by_id = db_instance.clone();
|
||||
engine.register_fn("get_comment_by_id", move |id: INT| {
|
||||
wrap_option_return!(get_comment_by_id, OurDB, INT => Comment)(db_clone_for_get_comment_by_id.clone(), id)
|
||||
});
|
||||
|
||||
let db_clone_for_optional_vec = db_instance.clone();
|
||||
engine.register_fn(
|
||||
"get_users_by_activity_status_optional",
|
||||
move |is_active_filter: bool| {
|
||||
(wrap_option_vec_return!(get_users_by_activity_status_optional, OurDB, bool => User))(
|
||||
db_clone_for_optional_vec.clone(),
|
||||
is_active_filter
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
engine.register_fn("println", |s: &str| println!("{}", s));
|
||||
engine.register_fn("print", |s: &str| print!("{}", s));
|
||||
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("examples")
|
||||
.join("user_script.rhai");
|
||||
|
||||
println!("Loading Rhai script from: {}", script_path.display());
|
||||
|
||||
match engine.eval_file::<()>(script_path) {
|
||||
Ok(_) => println!("Script executed successfully"),
|
||||
Err(e) => eprintln!("Error executing script: {}\nAt: {:?}", e, e.position()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
175
rhai_wrapper/examples/user_script.rhai
Normal file
175
rhai_wrapper/examples/user_script.rhai
Normal file
@ -0,0 +1,175 @@
|
||||
// Hero Models - Rhai Example Script
|
||||
println("Hero Models - Rhai Usage Example");
|
||||
println("================================");
|
||||
|
||||
// Get the DB instance
|
||||
let db = get_db();
|
||||
|
||||
// Create a new user using the builder pattern
|
||||
println("Creating users...");
|
||||
|
||||
// Create user 1
|
||||
let user1 = user_builder(1)
|
||||
.username("johndoe")
|
||||
.email("john.doe@example.com")
|
||||
.full_name("John Doe")
|
||||
.is_active(false);
|
||||
set_user(user1);
|
||||
|
||||
// Create user 2
|
||||
let user2 = user_builder(2)
|
||||
.username("janesmith")
|
||||
.email("jane.smith@example.com")
|
||||
.full_name("Jane Smith")
|
||||
.is_active(true);
|
||||
set_user(user2);
|
||||
|
||||
// Create user 3
|
||||
let user3 = user_builder(3)
|
||||
.username("willism")
|
||||
.email("willis.masters@example.com")
|
||||
.full_name("Willis Masters")
|
||||
.is_active(true);
|
||||
set_user(user3);
|
||||
|
||||
// Create user 4
|
||||
let user4 = user_builder(4)
|
||||
.username("carrols")
|
||||
.email("carrol.smith@example.com")
|
||||
.full_name("Carrol Smith")
|
||||
.is_active(false);
|
||||
|
||||
set_user(user4);
|
||||
|
||||
// Get user by ID
|
||||
println("\nRetrieving user by ID...");
|
||||
let retrieved_user = get_user_by_id(1);
|
||||
if retrieved_user != () { // In Rhai, functions returning Option<T> yield () for None
|
||||
println("Found user: " + retrieved_user.full_name + " (ID: " + retrieved_user.id + ")");
|
||||
println("Username: " + retrieved_user.username);
|
||||
println("Email: " + retrieved_user.email);
|
||||
println("Active: " + retrieved_user.is_active);
|
||||
} else {
|
||||
println("User not found");
|
||||
}
|
||||
|
||||
// Get users by active status
|
||||
println("\nRetrieving active users...");
|
||||
let all_users_for_active_check = get_all_users();
|
||||
let active_users = [];
|
||||
|
||||
// Filter active users
|
||||
for user_item in all_users_for_active_check {
|
||||
if user_item.is_active == true {
|
||||
active_users.push(user_item);
|
||||
}
|
||||
}
|
||||
|
||||
println("Found " + active_users.len() + " active users:");
|
||||
for user_item in active_users {
|
||||
println("- " + user_item.full_name + " (ID: " + user_item.id + ")");
|
||||
}
|
||||
|
||||
// Delete a user
|
||||
println("\nDeleting user...");
|
||||
delete_user_by_id(2);
|
||||
|
||||
// Get active users again
|
||||
println("\nRetrieving active users after deletion...");
|
||||
let all_users_after_delete = get_all_users();
|
||||
let active_users_after_delete = [];
|
||||
|
||||
// Filter active users
|
||||
for user_item in all_users_after_delete {
|
||||
if user_item.is_active == true {
|
||||
active_users_after_delete.push(user_item);
|
||||
}
|
||||
}
|
||||
|
||||
println("Found " + active_users_after_delete.len() + " active users:");
|
||||
for user_item in active_users_after_delete {
|
||||
println("- " + user_item.full_name + " (ID: " + user_item.id + ")");
|
||||
}
|
||||
|
||||
// Get inactive users
|
||||
println("\nRetrieving inactive users...");
|
||||
let all_users_for_inactive_check = get_all_users();
|
||||
let inactive_users = [];
|
||||
|
||||
// Filter inactive users
|
||||
for user_item in all_users_for_inactive_check {
|
||||
if user_item.is_active == false {
|
||||
inactive_users.push(user_item);
|
||||
}
|
||||
}
|
||||
|
||||
println("Found " + inactive_users.len() + " inactive users:");
|
||||
for user_item in inactive_users {
|
||||
println("- " + user_item.full_name + " (ID: " + user_item.id + ")");
|
||||
}
|
||||
|
||||
// Create a comment for user 1
|
||||
println("\nCreating a comment...");
|
||||
let comment1 = comment_builder(5)
|
||||
.user_id(1)
|
||||
.content("This is a comment on the user");
|
||||
set_comment(comment1);
|
||||
|
||||
// Get the comment
|
||||
println("\nRetrieving comment...");
|
||||
let retrieved_comment = get_comment_by_id(5);
|
||||
if retrieved_comment != () { // In Rhai, functions returning Option<T> yield () for None
|
||||
println("Found comment: " + retrieved_comment.content);
|
||||
println("Comment ID: " + retrieved_comment.id);
|
||||
println("User ID: " + retrieved_comment.user_id);
|
||||
} else {
|
||||
println("Comment not found");
|
||||
}
|
||||
|
||||
println("\nRetrieving optional active users (should be Some(Array) or Unit)...");
|
||||
let optional_active_users = get_users_by_activity_status_optional(true);
|
||||
if optional_active_users == () { // Check for unit (None)
|
||||
println("No active users found (returned unit).");
|
||||
} else {
|
||||
println("Found optional active users:");
|
||||
for user in optional_active_users {
|
||||
println("- " + user.full_name + " (ID: " + user.id + ")");
|
||||
}
|
||||
}
|
||||
|
||||
println("\nRetrieving optional inactive users (should be Some(Array) or Unit)...");
|
||||
let optional_inactive_users = get_users_by_activity_status_optional(false);
|
||||
if optional_inactive_users == () { // Check for unit (None)
|
||||
println("No inactive users found (returned unit).");
|
||||
} else {
|
||||
println("Found optional inactive users:");
|
||||
for user in optional_inactive_users {
|
||||
println("- " + user.full_name + " (ID: " + user.id + ")");
|
||||
}
|
||||
}
|
||||
|
||||
// To specifically test the None case from our Rust logic (empty vec for a status returns None)
|
||||
println("\nTesting None case for optional vector retrieval...");
|
||||
println("Deleting all users to ensure empty states...");
|
||||
let all_users_before_delete_all = get_all_users();
|
||||
for user_to_delete in all_users_before_delete_all {
|
||||
delete_user_by_id(user_to_delete.id);
|
||||
}
|
||||
|
||||
let optional_active_after_delete_all = get_users_by_activity_status_optional(true);
|
||||
if optional_active_after_delete_all == () {
|
||||
println("Correctly received unit for active users after deleting all.");
|
||||
} else {
|
||||
println("ERROR: Expected unit for active users after deleting all, but got an array.");
|
||||
print("Value received: " + optional_active_after_delete_all);
|
||||
}
|
||||
|
||||
let optional_inactive_after_delete_all = get_users_by_activity_status_optional(false);
|
||||
if optional_inactive_after_delete_all == () {
|
||||
println("Correctly received unit for inactive users after deleting all.");
|
||||
} else {
|
||||
println("ERROR: Expected unit for inactive users after deleting all, but got an array.");
|
||||
print("Value received: " + optional_inactive_after_delete_all);
|
||||
}
|
||||
|
||||
println("\nRhai example completed successfully!");
|
@ -208,3 +208,601 @@ macro_rules! wrap_for_rhai {
|
||||
$func
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro to wrap a Rust function that returns Option<T> for Rhai.
|
||||
/// It converts Some(T) to Dynamic::from(T) and None to Dynamic::UNIT.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_return {
|
||||
// Matches fn_name(arg1_type, arg2_type) -> Option<ReturnType>
|
||||
// Example: get_user_by_id(OurDB, INT) -> Option<User>
|
||||
// Macro call: wrap_option_return!(get_user_by_id, OurDB, INT => User)
|
||||
// Generated closure: |db: OurDB, id: INT| -> rhai::Dynamic { ... }
|
||||
($func:ident, $Arg1Type:ty, $Arg2Type:ty => $ReturnType:ident) => {
|
||||
|arg1: $Arg1Type, arg2: $Arg2Type| -> rhai::Dynamic {
|
||||
match $func(arg1, arg2) {
|
||||
Some(value) => {
|
||||
// Ensure the value is converted into a Dynamic.
|
||||
// If $ReturnType is already a CustomType, this should work directly.
|
||||
rhai::Dynamic::from(value)
|
||||
}
|
||||
None => rhai::Dynamic::UNIT,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add more arms here if functions with different numbers/types of arguments returning Option<T>
|
||||
// are needed.
|
||||
// For example, for a function with one argument:
|
||||
// ($func:ident, $Arg1Type:ty => $ReturnType:ident) => { ... };
|
||||
}
|
||||
|
||||
/// Macro to wrap a Rust function that returns Vec<T> for Rhai.
|
||||
/// It converts the Vec into a rhai::Array.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_vec_return {
|
||||
// For functions like fn(Arg1) -> Vec<InnerType>
|
||||
// Example: get_all_users(db: OurDB) -> Vec<User>
|
||||
// Macro call: wrap_vec_return!(get_all_users, OurDB => User)
|
||||
// Generated closure: |db: OurDB| -> rhai::Array { ... }
|
||||
($func:ident, $Arg1Type:ty => $InnerVecType:ty) => {
|
||||
|arg1_val: $Arg1Type| -> rhai::Array {
|
||||
let result_vec: std::vec::Vec<$InnerVecType> = $func(arg1_val);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// For functions like fn(Arg1, Arg2) -> Vec<InnerType>
|
||||
($func:ident, $Arg1Type:ty, $Arg2Type:ty => $InnerVecType:ty) => {
|
||||
|arg1_val: $Arg1Type, arg2_val: $Arg2Type| -> rhai::Array {
|
||||
let result_vec: std::vec::Vec<$InnerVecType> = $func(arg1_val, arg2_val);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// For functions like fn() -> Vec<InnerType>
|
||||
($func:ident, () => $InnerVecType:ty) => {
|
||||
|| -> rhai::Array {
|
||||
let result_vec: std::vec::Vec<$InnerVecType> = $func();
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_vec_return {
|
||||
// Case: fn_name(Arg1Type, Arg2Type) -> Option<Vec<InnerType>>
|
||||
// Generates a closure: |arg1_val: Arg1Type, arg2_val: Arg2Type| -> rhai::Dynamic
|
||||
($func:ident, $Arg1Type:ty, $Arg2Type:ty => $InnerVecType:ty) => {
|
||||
move |arg1_val: $Arg1Type, arg2_val: $Arg2Type| -> rhai::Dynamic {
|
||||
match $func(arg1_val, arg2_val) { // Call the original Rust function
|
||||
Some(vec_inner) => { // vec_inner is Vec<$InnerVecType>
|
||||
let rhai_array = vec_inner.into_iter()
|
||||
.map(rhai::Dynamic::from) // Each $InnerVecType must be convertible to Dynamic
|
||||
.collect::<rhai::Array>();
|
||||
rhai::Dynamic::from(rhai_array)
|
||||
}
|
||||
None => rhai::Dynamic::UNIT,
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add arms for different numbers of arguments if needed, e.g.:
|
||||
// ($func:ident, $Arg1Type:ty => $InnerVecType:ty) => { ... }
|
||||
// ($func:ident => $InnerVecType:ty) => { ... }
|
||||
}
|
||||
|
||||
/// Wraps a Rust function that returns `Result<Option<T>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements a `to_rhai_map(&self) -> rhai::Map` method.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_return_result {
|
||||
// Case: Function with DB connection and 1 additional argument
|
||||
(
|
||||
$func:path, // Path to the actual Rust function
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types for actual func
|
||||
$ErrorType:ty // Error type from actual func
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance, arg1_val) { // Call the actual function
|
||||
Ok(Some(value)) => {
|
||||
// Assumes ReturnType has a .to_rhai_map() method.
|
||||
Ok(rhai::Dynamic::from(value.to_rhai_map()))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // Requires ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Function with DB connection and 0 additional arguments
|
||||
(
|
||||
$func:path,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance) { // Call the actual function
|
||||
Ok(Some(value)) => {
|
||||
Ok(rhai::Dynamic::from(value.to_rhai_map()))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
/// Wraps a Rust function that returns `Result<Vec<T>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements a `to_rhai_map(&self) -> rhai::Map` method.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_vec_return_result {
|
||||
// Case: Function with DB connection and 1 additional argument
|
||||
(
|
||||
$func:path, // Path to the actual Rust function
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types for actual func
|
||||
$ErrorType:ty // Error type from actual func
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance, arg1_val) { // Call the actual function
|
||||
Ok(vec_of_values) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // Requires ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Function with DB connection and 0 additional arguments (e.g., get_all)
|
||||
(
|
||||
$func:path,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance) { // Call the actual function
|
||||
Ok(vec_of_values) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
/// Wraps a Rust function that returns `Result<Option<Vec<T>>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements a `to_rhai_map(&self) -> rhai::Map` method.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_vec_return_result {
|
||||
// Case: Function with DB connection and 1 additional argument
|
||||
(
|
||||
$func:path, // Path to the actual Rust function
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types for actual func
|
||||
$ErrorType:ty // Error type from actual func
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance, arg1_val) { // Call the actual function
|
||||
Ok(Some(vec_of_values)) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // Requires ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Function with DB connection and 0 additional arguments
|
||||
(
|
||||
$func:path,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance) { // Call the actual function
|
||||
Ok(Some(vec_of_values)) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
// --- Macros for methods returning Result<_, _> for fallible operations --- //
|
||||
|
||||
/// Wraps a Rust method that returns `Result<Option<T>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements `Clone` and is Rhai `CustomType`.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_method_result {
|
||||
// Case: Method with DB connection (self) and 1 additional argument
|
||||
(
|
||||
$method_name:ident, // Name of the method on the Collection
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type (e.g. &Collection), Arg types
|
||||
$ErrorType:ty // Error type from method
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name(arg1_val) { // Call the method
|
||||
Ok(Some(value)) => {
|
||||
Ok(rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Method with DB connection (self) and 0 additional arguments
|
||||
(
|
||||
$method_name:ident,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name() { // Call the method
|
||||
Ok(Some(value)) => {
|
||||
Ok(rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
/// Wraps a Rust method that returns `Result<Vec<T>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements `Clone` and is Rhai `CustomType`.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_vec_method_result {
|
||||
// Case: Method with DB connection (self) and 1 additional argument
|
||||
(
|
||||
$method_name:ident, // Name of the method on the Collection
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types
|
||||
$ErrorType:ty // Error type from method
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name(arg1_val) { // Call the method
|
||||
Ok(vec_of_values) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Method with DB connection (self) and 0 additional arguments (e.g., get_all)
|
||||
(
|
||||
$method_name:ident,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name() { // Call the method
|
||||
Ok(vec_of_values) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
/// Wraps a Rust method that returns `Result<Option<Vec<T>>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements `Clone` and is Rhai `CustomType`.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_vec_method_result {
|
||||
// Case: Method with DB connection (self) and 1 additional argument
|
||||
(
|
||||
$method_name:ident, // Name of the method on the Collection
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types
|
||||
$ErrorType:ty // Error type from method
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name(arg1_val) { // Call the method
|
||||
Ok(Some(vec_of_values)) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Method with DB connection (self) and 0 additional arguments
|
||||
(
|
||||
$method_name:ident,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name() { // Call the method
|
||||
Ok(Some(vec_of_values)) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
// TODO: Consider merging wrap_option_return, wrap_vec_return, and wrap_option_vec_return
|
||||
// into a more general wrap_for_rhai! macro if patterns become too numerous or complex.
|
||||
// For now, separate macros are clear for distinct return type patterns.
|
||||
|
||||
/// A macro that creates a Rust function that calls a Rhai engine to execute a Rhai function which wraps an underlying Rust function.
|
||||
/// This creates a full circle of Rust → Rhai → Rust function calls.
|
||||
///
|
||||
/// # Example Usage
|
||||
/// ```rust,ignore
|
||||
/// // Define a Rust function
|
||||
/// fn add(a: i32, b: i32) -> i32 {
|
||||
/// a + b
|
||||
/// }
|
||||
///
|
||||
/// // Register it with Rhai (assuming engine is already created)
|
||||
/// engine.register_fn("add_rhai", add);
|
||||
///
|
||||
/// // Create a wrapper function that takes an engine reference and calls the Rhai function
|
||||
/// rust_rhai_wrapper!(add_via_rhai, "add_rhai", (i32, i32) -> i32);
|
||||
///
|
||||
/// // Now you can call add_via_rhai which will call the Rhai function add_rhai which calls the Rust function add
|
||||
/// let result = add_via_rhai(&mut engine, 5, 3); // result = 8
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! rust_rhai_wrapper {
|
||||
// Basic case: function with no arguments
|
||||
($func_name:ident, $rhai_func_name:expr, () -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine) -> $return_type {
|
||||
let result = engine.eval::<$return_type>(
|
||||
&format!("{}()", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with one argument
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with two arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1, arg2)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with three arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty, $arg3_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type, arg3: $arg3_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
scope.push("arg3", arg3);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1, arg2, arg3)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with four arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty, $arg3_type:ty, $arg4_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type, arg3: $arg3_type, arg4: $arg4_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
scope.push("arg3", arg3);
|
||||
scope.push("arg4", arg4);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1, arg2, arg3, arg4)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with five arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty, $arg3_type:ty, $arg4_type:ty, $arg5_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type, arg3: $arg3_type, arg4: $arg4_type, arg5: $arg5_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
scope.push("arg3", arg3);
|
||||
scope.push("arg4", arg4);
|
||||
scope.push("arg5", arg5);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1, arg2, arg3, arg4, arg5)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with a Result return type and no arguments
|
||||
($func_name:ident, $rhai_func_name:expr, () -> Result<$ok_type:ty, $err_type:ty>) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine) -> Result<$ok_type, $err_type> {
|
||||
match engine.eval::<$ok_type>(&format!("{}()", $rhai_func_name)) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => Err($err_type::from(format!("Rhai error: {}", err))),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function with a Result return type and one argument
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty) -> Result<$ok_type:ty, $err_type:ty>) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type) -> Result<$ok_type, $err_type> {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
|
||||
match engine.eval_with_scope::<$ok_type>(&mut scope, &format!("{}(arg1)", $rhai_func_name)) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => Err($err_type::from(format!("Rhai error: {}", err))),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function with a Result return type and two arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty) -> Result<$ok_type:ty, $err_type:ty>) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type) -> Result<$ok_type, $err_type> {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
|
||||
match engine.eval_with_scope::<$ok_type>(&mut scope, &format!("{}(arg1, arg2)", $rhai_func_name)) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => Err($err_type::from(format!("Rhai error: {}", err))),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// into a more general wrap_for_rhai! macro if patterns become too numerous or complex.
|
||||
// For now, separate macros are clear for distinct return type patterns.
|
||||
|
@ -1,15 +1,23 @@
|
||||
use rhai_wrapper::wrap_for_rhai;
|
||||
use rhai_wrapper::{ToRhaiMap, FromRhaiMap};
|
||||
use rhai::{CustomType, TypeBuilder, Engine, INT, FLOAT, Array};
|
||||
use rhai_macros_derive::{ToRhaiMap as ToRhaiMapDerive, FromRhaiMap as FromRhaiMapDerive};
|
||||
use rhai_macros_derive::{ToRhaiMap as ToRhaiMapDerive, FromRhaiMap as FromRhaiMapDerive, export_fn};
|
||||
|
||||
#[export_fn(rhai_name = "add_rhai")]
|
||||
fn add(a: INT, b: INT) -> INT { a + b }
|
||||
#[export_fn(rhai_name = "mul_rhai")]
|
||||
fn mul(a: INT, b: INT) -> INT { a * b }
|
||||
#[export_fn(rhai_name = "greet_rhai")]
|
||||
fn greet(name: String) -> String { format!("Hello, {name}!") }
|
||||
#[export_fn(rhai_name = "get_forty_two_rhai")]
|
||||
fn get_forty_two() -> INT { 42 }
|
||||
#[export_fn(rhai_name = "shout_rhai")]
|
||||
fn shout() -> String { "HEY!".to_string() }
|
||||
#[export_fn(rhai_name = "add_float_rhai")]
|
||||
fn add_float(a: FLOAT, b: FLOAT) -> FLOAT { a + b }
|
||||
#[export_fn(rhai_name = "is_even_rhai")]
|
||||
fn is_even(n: INT) -> bool { n % 2 == 0 }
|
||||
#[export_fn(rhai_name = "maybe_add_rhai")]
|
||||
fn maybe_add(a: INT, b: INT, do_add: bool) -> Option<INT> { if do_add { Some(a + b) } else { None } }
|
||||
|
||||
// Renamed from sum_vec, takes rhai::Array
|
||||
@ -110,69 +118,83 @@ fn get_polygon_id_and_num_vertices(poly: Polygon) -> String {
|
||||
#[test]
|
||||
fn test_add() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("add", wrap_for_rhai!(add));
|
||||
let result = engine.eval::<INT>("add(2, 3)").unwrap();
|
||||
engine.register_fn("add_rhai", add_rhai_wrapper);
|
||||
let result = engine.eval::<INT>("add_rhai(2, 3)").unwrap();
|
||||
assert_eq!(result, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mul() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("mul", wrap_for_rhai!(mul));
|
||||
let result = engine.eval::<INT>("mul(4, 5)").unwrap();
|
||||
engine.register_fn("mul_rhai", mul_rhai_wrapper);
|
||||
let result = engine.eval::<INT>("mul_rhai(4, 5)").unwrap();
|
||||
assert_eq!(result, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_greet() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("greet", wrap_for_rhai!(greet));
|
||||
let result = engine.eval::<String>(r#"greet("Alice")"#).unwrap();
|
||||
engine.register_fn("greet_rhai", greet_rhai_wrapper);
|
||||
let result = engine.eval::<String>(r#"greet_rhai("Alice")"#).unwrap();
|
||||
assert_eq!(result, "Hello, Alice!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_forty_two() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("get_forty_two", wrap_for_rhai!(get_forty_two));
|
||||
let result = engine.eval::<INT>("get_forty_two()").unwrap();
|
||||
engine.register_fn("get_forty_two_rhai", get_forty_two_rhai_wrapper);
|
||||
let result = engine.eval::<INT>("get_forty_two_rhai()").unwrap();
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shout() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("shout", wrap_for_rhai!(shout));
|
||||
let result = engine.eval::<String>("shout()").unwrap();
|
||||
engine.register_fn("shout_rhai", shout_rhai_wrapper);
|
||||
let result = engine.eval::<String>("shout_rhai()").unwrap();
|
||||
assert_eq!(result, "HEY!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_float() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("add_float", wrap_for_rhai!(add_float));
|
||||
let result = engine.eval::<FLOAT>("add_float(1.5, 2.25)").unwrap();
|
||||
assert!((result - 3.75).abs() < 1e-8);
|
||||
engine.register_fn("add_float_rhai", add_float_rhai_wrapper);
|
||||
let result = engine.eval::<FLOAT>("add_float_rhai(2.5, 3.5)").unwrap();
|
||||
assert_eq!(result, 6.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_even() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("is_even", wrap_for_rhai!(is_even));
|
||||
let result = engine.eval::<bool>("is_even(4)").unwrap();
|
||||
assert!(result);
|
||||
let result = engine.eval::<bool>("is_even(5)").unwrap();
|
||||
assert!(!result);
|
||||
engine.register_fn("is_even_rhai", is_even_rhai_wrapper);
|
||||
let result_true = engine.eval::<bool>("is_even_rhai(4)").unwrap();
|
||||
assert_eq!(result_true, true);
|
||||
let result_false = engine.eval::<bool>("is_even_rhai(3)").unwrap();
|
||||
assert_eq!(result_false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_maybe_add() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("maybe_add", wrap_for_rhai!(maybe_add));
|
||||
let result = engine.eval::<Option<INT>>("maybe_add(2, 3, true)").unwrap();
|
||||
assert_eq!(result, Some(5));
|
||||
let result = engine.eval::<Option<INT>>("maybe_add(2, 3, false)").unwrap();
|
||||
assert_eq!(result, None);
|
||||
engine.register_fn("maybe_add_rhai", maybe_add_rhai_wrapper);
|
||||
|
||||
// Test case where None is returned (expecting an error or specific handling in Rhai)
|
||||
// Rhai treats Option::None as an empty Dynamic, which can lead to type mismatch if not handled.
|
||||
// For now, let's check if the script produces a specific type or if it can be evaluated to Dynamic.
|
||||
// If the function returns None, eval might return an error if trying to cast to INT.
|
||||
// Let's eval to Dynamic and check if it's empty (Rhai's representation of None).
|
||||
let result_none = engine.eval::<rhai::Dynamic>("maybe_add_rhai(2, 3, false)").unwrap();
|
||||
|
||||
// Debug prints
|
||||
println!("Debug [test_maybe_add]: result_none = {:?}", result_none);
|
||||
println!("Debug [test_maybe_add]: result_none.type_name() = {}", result_none.type_name());
|
||||
println!("Debug [test_maybe_add]: result_none.is::<()>() = {}", result_none.is::<()>());
|
||||
|
||||
assert!(result_none.is_unit(), "Expected Rhai None (unit Dynamic)");
|
||||
|
||||
// Test case where Some is returned
|
||||
let result_some = engine.eval::<INT>("maybe_add_rhai(2, 3, true)").unwrap();
|
||||
assert_eq!(result_some, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -502,21 +524,20 @@ mod new_export_fn_tests {
|
||||
assert_eq!(result, 15);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_export_fn_custom_type_arg_return() { // This test was commented out, keeping as is for now
|
||||
// let mut engine = Engine::new();
|
||||
// engine.build_type::<Point>();
|
||||
// // engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
||||
#[test]
|
||||
fn test_export_fn_custom_type_arg_return_new() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>();
|
||||
engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
||||
|
||||
// // let script = r#"
|
||||
// // let p = #{ x: 10, y: 20 };
|
||||
// // let p_offset = offset_simple_point(p, 5);
|
||||
// // p_offset.x
|
||||
// // "#;
|
||||
// // let result = engine.eval::<INT>(script).unwrap();
|
||||
// // assert_eq!(result, 15);
|
||||
|
||||
// }
|
||||
let script = r#"
|
||||
let p = #{ x: 10, y: 20 };
|
||||
let p_offset = offset_simple_point(p, 5);
|
||||
p_offset.x
|
||||
"#;
|
||||
let result = engine.eval::<INT>(script).unwrap();
|
||||
assert_eq!(result, 15);
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, FromRhaiMapDerive, ToRhaiMapDerive, CustomType)]
|
||||
@ -556,17 +577,4 @@ mod new_export_fn_tests {
|
||||
assert_eq!(result_struct.optional_nested_vec.as_ref().unwrap().len(), 1);
|
||||
assert_eq!(result_struct.optional_nested_vec.as_ref().unwrap()[0], SampleStruct { value: 3, name: "n3".to_string() });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_export_fn_custom_type_arg_return_new() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>();
|
||||
engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
||||
|
||||
let script = r#"
|
||||
42
|
||||
"#;
|
||||
let result = engine.eval::<INT>(script).unwrap();
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
}
|
||||
|
709
rhai_wrapper/tests/wrapper_macros_test.rs
Normal file
709
rhai_wrapper/tests/wrapper_macros_test.rs
Normal file
@ -0,0 +1,709 @@
|
||||
use rhai::{Engine, INT, FLOAT, Dynamic, Map, Array, EvalAltResult, CustomType, TypeBuilder};
|
||||
use rhai_wrapper::{
|
||||
wrap_option_return, wrap_vec_return, wrap_option_vec_return,
|
||||
wrap_option_return_result, wrap_vec_return_result, wrap_option_vec_return_result,
|
||||
wrap_option_method_result, wrap_vec_method_result, wrap_option_vec_method_result,
|
||||
ToRhaiMap, FromRhaiMap
|
||||
};
|
||||
use rhai_macros_derive::{ToRhaiMap as ToRhaiMapDerive, FromRhaiMap as FromRhaiMapDerive};
|
||||
use std::fmt;
|
||||
|
||||
// Test structs
|
||||
#[derive(Debug, Clone, PartialEq, CustomType, ToRhaiMapDerive, FromRhaiMapDerive)]
|
||||
struct User {
|
||||
id: INT,
|
||||
name: String,
|
||||
age: INT,
|
||||
}
|
||||
|
||||
impl User {
|
||||
fn to_rhai_map(&self) -> Map {
|
||||
let mut map = Map::new();
|
||||
map.insert("id".into(), self.id.into());
|
||||
map.insert("name".into(), self.name.clone().into());
|
||||
map.insert("age".into(), self.age.into());
|
||||
map
|
||||
}
|
||||
|
||||
fn from_rhai_map(map: Map) -> Result<Self, String> {
|
||||
let id = map.get("id")
|
||||
.and_then(|d| d.as_int().ok())
|
||||
.ok_or_else(|| "Field 'id' not found or not an INT".to_string())?;
|
||||
|
||||
let name = map.get("name")
|
||||
.and_then(|d| d.clone().into_string().ok())
|
||||
.ok_or_else(|| "Field 'name' not found or not a String".to_string())?;
|
||||
|
||||
let age = map.get("age")
|
||||
.and_then(|d| d.as_int().ok())
|
||||
.ok_or_else(|| "Field 'age' not found or not an INT".to_string())?;
|
||||
|
||||
Ok(User { id, name, age })
|
||||
}
|
||||
}
|
||||
|
||||
// Mock DB connection type
|
||||
struct OurDB {
|
||||
// Some mock state
|
||||
error_mode: bool,
|
||||
}
|
||||
|
||||
impl OurDB {
|
||||
fn new(error_mode: bool) -> Self {
|
||||
OurDB { error_mode }
|
||||
}
|
||||
}
|
||||
|
||||
// Custom error type for testing
|
||||
#[derive(Debug)]
|
||||
struct DBError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for DBError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "DB Error: {}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
// Test functions for wrap_option_return
|
||||
fn get_user_by_id(_db: &OurDB, id: INT) -> Option<User> {
|
||||
if id > 0 {
|
||||
Some(User { id, name: format!("User {}", id), age: 30 })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Test functions for wrap_vec_return
|
||||
fn get_all_users(_db: &OurDB) -> Vec<User> {
|
||||
vec![
|
||||
User { id: 1, name: "User 1".to_string(), age: 30 },
|
||||
User { id: 2, name: "User 2".to_string(), age: 25 },
|
||||
]
|
||||
}
|
||||
|
||||
fn get_users_by_age(_db: &OurDB, min_age: INT) -> Vec<User> {
|
||||
vec![
|
||||
User { id: 1, name: "User 1".to_string(), age: 30 },
|
||||
User { id: 2, name: "User 2".to_string(), age: 25 },
|
||||
].into_iter().filter(|u| u.age >= min_age).collect()
|
||||
}
|
||||
|
||||
fn get_all_user_ids() -> Vec<INT> {
|
||||
vec![1, 2, 3]
|
||||
}
|
||||
|
||||
// Test functions for wrap_option_vec_return
|
||||
fn find_users_by_name(_db: &OurDB, name_part: String) -> Option<Vec<User>> {
|
||||
if name_part.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(vec![
|
||||
User { id: 1, name: format!("{} One", name_part), age: 30 },
|
||||
User { id: 2, name: format!("{} Two", name_part), age: 25 },
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// Test functions for result-returning wrappers
|
||||
fn get_user_by_id_result(db: &OurDB, id: INT) -> Result<Option<User>, DBError> {
|
||||
if db.error_mode {
|
||||
Err(DBError { message: "DB connection error".to_string() })
|
||||
} else if id > 0 {
|
||||
Ok(Some(User { id, name: format!("User {}", id), age: 30 }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_all_users_result(db: &OurDB) -> Result<Vec<User>, DBError> {
|
||||
if db.error_mode {
|
||||
Err(DBError { message: "DB connection error".to_string() })
|
||||
} else {
|
||||
Ok(vec![
|
||||
User { id: 1, name: "User 1".to_string(), age: 30 },
|
||||
User { id: 2, name: "User 2".to_string(), age: 25 },
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn find_users_by_name_result(db: &OurDB, name_part: String) -> Result<Option<Vec<User>>, DBError> {
|
||||
if db.error_mode {
|
||||
Err(DBError { message: "DB connection error".to_string() })
|
||||
} else if name_part.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(vec![
|
||||
User { id: 1, name: format!("{} One", name_part), age: 30 },
|
||||
User { id: 2, name: format!("{} Two", name_part), age: 25 },
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
// Test methods for method wrappers
|
||||
struct UserCollection {
|
||||
users: Vec<User>,
|
||||
error_mode: bool,
|
||||
}
|
||||
|
||||
impl UserCollection {
|
||||
fn new(error_mode: bool) -> Self {
|
||||
UserCollection {
|
||||
users: vec![
|
||||
User { id: 1, name: "User 1".to_string(), age: 30 },
|
||||
User { id: 2, name: "User 2".to_string(), age: 25 },
|
||||
],
|
||||
error_mode,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_by_id(&self, id: INT) -> Result<Option<User>, DBError> {
|
||||
if self.error_mode {
|
||||
Err(DBError { message: "Collection error".to_string() })
|
||||
} else {
|
||||
Ok(self.users.iter().find(|u| u.id == id).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_all(&self) -> Result<Vec<User>, DBError> {
|
||||
if self.error_mode {
|
||||
Err(DBError { message: "Collection error".to_string() })
|
||||
} else {
|
||||
Ok(self.users.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn find_by_name(&self, name_part: String) -> Result<Option<Vec<User>>, DBError> {
|
||||
if self.error_mode {
|
||||
Err(DBError { message: "Collection error".to_string() })
|
||||
} else if name_part.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(self.users.iter()
|
||||
.filter(|u| u.name.contains(&name_part))
|
||||
.cloned()
|
||||
.collect()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_return() {
|
||||
let mut engine = Engine::new();
|
||||
let db = OurDB::new(false);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_fn(
|
||||
"get_user_by_id_rhai",
|
||||
wrap_option_return!(get_user_by_id, &OurDB, INT => User)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with existing user
|
||||
let script1 = r#"
|
||||
let user = get_user_by_id_rhai(db, 1);
|
||||
user.id
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"user.id",
|
||||
(db.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 1);
|
||||
|
||||
// Test with non-existing user
|
||||
let script2 = r#"
|
||||
let user = get_user_by_id_rhai(db, 0);
|
||||
if user == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if user == () { 42 } else { 0 }",
|
||||
(db, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_vec_return() {
|
||||
let mut engine = Engine::new();
|
||||
let db = OurDB::new(false);
|
||||
|
||||
// Register the wrapped functions
|
||||
engine.register_fn(
|
||||
"get_all_users_rhai",
|
||||
wrap_vec_return!(get_all_users, &OurDB => User)
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"get_users_by_age_rhai",
|
||||
wrap_vec_return!(get_users_by_age, &OurDB, INT => User)
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"get_all_user_ids_rhai",
|
||||
wrap_vec_return!(get_all_user_ids, () => INT)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test get_all_users
|
||||
let script1 = r#"
|
||||
let users = get_all_users_rhai(db);
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(db.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test get_users_by_age
|
||||
let script2 = r#"
|
||||
let users = get_users_by_age_rhai(db, 30);
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"users.len()",
|
||||
(db, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 1);
|
||||
|
||||
// Test get_all_user_ids
|
||||
let script3 = r#"
|
||||
let ids = get_all_user_ids_rhai();
|
||||
ids.len()
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"ids.len()",
|
||||
()
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_vec_return() {
|
||||
let mut engine = Engine::new();
|
||||
let db = OurDB::new(false);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_fn(
|
||||
"find_users_by_name_rhai",
|
||||
wrap_option_vec_return!(find_users_by_name, &OurDB, String => User)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with found users
|
||||
let script1 = r#"
|
||||
let users = find_users_by_name_rhai(db, "User");
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(db.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with no users found
|
||||
let script2 = r#"
|
||||
let users = find_users_by_name_rhai(db, "");
|
||||
if users == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if users == () { 42 } else { 0 }",
|
||||
(db, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_return_result() {
|
||||
let mut engine = Engine::new();
|
||||
let db_ok = OurDB::new(false);
|
||||
let db_err = OurDB::new(true);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_result_fn(
|
||||
"get_user_by_id_result_rhai",
|
||||
wrap_option_return_result!(get_user_by_id_result, &OurDB, INT => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with existing user
|
||||
let script1 = r#"
|
||||
let user = get_user_by_id_result_rhai(db, 1);
|
||||
user.id
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"user.id",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 1);
|
||||
|
||||
// Test with non-existing user
|
||||
let script2 = r#"
|
||||
let user = get_user_by_id_result_rhai(db, 0);
|
||||
if user == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if user == () { 42 } else { 0 }",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
|
||||
// Test with error
|
||||
let script3 = r#"
|
||||
try {
|
||||
let user = get_user_by_id_result_rhai(db, 1);
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("DB connection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"try_catch_block",
|
||||
(db_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_vec_return_result() {
|
||||
let mut engine = Engine::new();
|
||||
let db_ok = OurDB::new(false);
|
||||
let db_err = OurDB::new(true);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_result_fn(
|
||||
"get_all_users_result_rhai",
|
||||
wrap_vec_return_result!(get_all_users_result, &OurDB => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with successful result
|
||||
let script1 = r#"
|
||||
let users = get_all_users_result_rhai(db);
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with error
|
||||
let script2 = r#"
|
||||
try {
|
||||
let users = get_all_users_result_rhai(db);
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("DB connection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"try_catch_block",
|
||||
(db_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_vec_return_result() {
|
||||
let mut engine = Engine::new();
|
||||
let db_ok = OurDB::new(false);
|
||||
let db_err = OurDB::new(true);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_result_fn(
|
||||
"find_users_by_name_result_rhai",
|
||||
wrap_option_vec_return_result!(find_users_by_name_result, &OurDB, String => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with found users
|
||||
let script1 = r#"
|
||||
let users = find_users_by_name_result_rhai(db, "User");
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with no users found
|
||||
let script2 = r#"
|
||||
let users = find_users_by_name_result_rhai(db, "");
|
||||
if users == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if users == () { 42 } else { 0 }",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
|
||||
// Test with error
|
||||
let script3 = r#"
|
||||
try {
|
||||
let users = find_users_by_name_result_rhai(db, "User");
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("DB connection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"try_catch_block",
|
||||
(db_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_method_result() {
|
||||
let mut engine = Engine::new();
|
||||
let collection_ok = UserCollection::new(false);
|
||||
let collection_err = UserCollection::new(true);
|
||||
|
||||
// Register the wrapped method
|
||||
engine.register_result_fn(
|
||||
"get_by_id_rhai",
|
||||
wrap_option_method_result!(get_by_id, &UserCollection, INT => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with existing user
|
||||
let script1 = r#"
|
||||
let user = get_by_id_rhai(collection, 1);
|
||||
user.id
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"user.id",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 1);
|
||||
|
||||
// Test with non-existing user
|
||||
let script2 = r#"
|
||||
let user = get_by_id_rhai(collection, 999);
|
||||
if user == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if user == () { 42 } else { 0 }",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
|
||||
// Test with error
|
||||
let script3 = r#"
|
||||
try {
|
||||
let user = get_by_id_rhai(collection, 1);
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("Collection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"try_catch_block",
|
||||
(collection_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_vec_method_result() {
|
||||
let mut engine = Engine::new();
|
||||
let collection_ok = UserCollection::new(false);
|
||||
let collection_err = UserCollection::new(true);
|
||||
|
||||
// Register the wrapped method
|
||||
engine.register_result_fn(
|
||||
"get_all_rhai",
|
||||
wrap_vec_method_result!(get_all, &UserCollection => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with successful result
|
||||
let script1 = r#"
|
||||
let users = get_all_rhai(collection);
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with error
|
||||
let script2 = r#"
|
||||
try {
|
||||
let users = get_all_rhai(collection);
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("Collection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"try_catch_block",
|
||||
(collection_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_vec_method_result() {
|
||||
let mut engine = Engine::new();
|
||||
let collection_ok = UserCollection::new(false);
|
||||
let collection_err = UserCollection::new(true);
|
||||
|
||||
// Register the wrapped method
|
||||
engine.register_result_fn(
|
||||
"find_by_name_rhai",
|
||||
wrap_option_vec_method_result!(find_by_name, &UserCollection, String => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with found users
|
||||
let script1 = r#"
|
||||
let users = find_by_name_rhai(collection, "User");
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with no users found
|
||||
let script2 = r#"
|
||||
let users = find_by_name_rhai(collection, "");
|
||||
if users == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if users == () { 42 } else { 0 }",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
|
||||
// Test with error
|
||||
let script3 = r#"
|
||||
try {
|
||||
let users = find_by_name_rhai(collection, "User");
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("Collection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"try_catch_block",
|
||||
(collection_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 99);
|
||||
}
|
Reference in New Issue
Block a user