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",
|
"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]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -28,12 +43,42 @@ version = "2.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
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]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
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]]
|
[[package]]
|
||||||
name = "const-random"
|
name = "const-random"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
@ -54,6 +99,12 @@ dependencies = [
|
|||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crunchy"
|
name = "crunchy"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@ -83,6 +134,30 @@ dependencies = [
|
|||||||
"wasi 0.14.2+wasi-0.2.4",
|
"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]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
@ -92,12 +167,28 @@ dependencies = [
|
|||||||
"cfg-if",
|
"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]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.172"
|
version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@ -187,10 +278,44 @@ dependencies = [
|
|||||||
name = "rhai_wrapper"
|
name = "rhai_wrapper"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"rhai",
|
"rhai",
|
||||||
"rhai_macros_derive",
|
"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]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
@ -267,6 +392,123 @@ dependencies = [
|
|||||||
"wit-bindgen-rt",
|
"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]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "wit-bindgen-rt"
|
||||||
version = "0.39.0"
|
version = "0.39.0"
|
||||||
|
@ -6,7 +6,17 @@ edition = "2021"
|
|||||||
description = "A wrapper to make generic Rust functions Rhai-compatible."
|
description = "A wrapper to make generic Rust functions Rhai-compatible."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rhai = "1.21.0"
|
rhai = "1.18.0"
|
||||||
rhai_macros_derive = { path = "../rhai_macros_derive" }
|
rhai_macros_derive = { path = "../rhai_macros_derive" }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[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
|
$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::wrap_for_rhai;
|
||||||
use rhai_wrapper::{ToRhaiMap, FromRhaiMap};
|
use rhai_wrapper::{ToRhaiMap, FromRhaiMap};
|
||||||
use rhai::{CustomType, TypeBuilder, Engine, INT, FLOAT, Array};
|
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 }
|
fn add(a: INT, b: INT) -> INT { a + b }
|
||||||
|
#[export_fn(rhai_name = "mul_rhai")]
|
||||||
fn mul(a: INT, b: INT) -> INT { a * b }
|
fn mul(a: INT, b: INT) -> INT { a * b }
|
||||||
|
#[export_fn(rhai_name = "greet_rhai")]
|
||||||
fn greet(name: String) -> String { format!("Hello, {name}!") }
|
fn greet(name: String) -> String { format!("Hello, {name}!") }
|
||||||
|
#[export_fn(rhai_name = "get_forty_two_rhai")]
|
||||||
fn get_forty_two() -> INT { 42 }
|
fn get_forty_two() -> INT { 42 }
|
||||||
|
#[export_fn(rhai_name = "shout_rhai")]
|
||||||
fn shout() -> String { "HEY!".to_string() }
|
fn shout() -> String { "HEY!".to_string() }
|
||||||
|
#[export_fn(rhai_name = "add_float_rhai")]
|
||||||
fn add_float(a: FLOAT, b: FLOAT) -> FLOAT { a + b }
|
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 }
|
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 } }
|
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
|
// Renamed from sum_vec, takes rhai::Array
|
||||||
@ -110,69 +118,83 @@ fn get_polygon_id_and_num_vertices(poly: Polygon) -> String {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_add() {
|
fn test_add() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.register_fn("add", wrap_for_rhai!(add));
|
engine.register_fn("add_rhai", add_rhai_wrapper);
|
||||||
let result = engine.eval::<INT>("add(2, 3)").unwrap();
|
let result = engine.eval::<INT>("add_rhai(2, 3)").unwrap();
|
||||||
assert_eq!(result, 5);
|
assert_eq!(result, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mul() {
|
fn test_mul() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.register_fn("mul", wrap_for_rhai!(mul));
|
engine.register_fn("mul_rhai", mul_rhai_wrapper);
|
||||||
let result = engine.eval::<INT>("mul(4, 5)").unwrap();
|
let result = engine.eval::<INT>("mul_rhai(4, 5)").unwrap();
|
||||||
assert_eq!(result, 20);
|
assert_eq!(result, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_greet() {
|
fn test_greet() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.register_fn("greet", wrap_for_rhai!(greet));
|
engine.register_fn("greet_rhai", greet_rhai_wrapper);
|
||||||
let result = engine.eval::<String>(r#"greet("Alice")"#).unwrap();
|
let result = engine.eval::<String>(r#"greet_rhai("Alice")"#).unwrap();
|
||||||
assert_eq!(result, "Hello, Alice!");
|
assert_eq!(result, "Hello, Alice!");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_forty_two() {
|
fn test_get_forty_two() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.register_fn("get_forty_two", wrap_for_rhai!(get_forty_two));
|
engine.register_fn("get_forty_two_rhai", get_forty_two_rhai_wrapper);
|
||||||
let result = engine.eval::<INT>("get_forty_two()").unwrap();
|
let result = engine.eval::<INT>("get_forty_two_rhai()").unwrap();
|
||||||
assert_eq!(result, 42);
|
assert_eq!(result, 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shout() {
|
fn test_shout() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.register_fn("shout", wrap_for_rhai!(shout));
|
engine.register_fn("shout_rhai", shout_rhai_wrapper);
|
||||||
let result = engine.eval::<String>("shout()").unwrap();
|
let result = engine.eval::<String>("shout_rhai()").unwrap();
|
||||||
assert_eq!(result, "HEY!");
|
assert_eq!(result, "HEY!");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_float() {
|
fn test_add_float() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.register_fn("add_float", wrap_for_rhai!(add_float));
|
engine.register_fn("add_float_rhai", add_float_rhai_wrapper);
|
||||||
let result = engine.eval::<FLOAT>("add_float(1.5, 2.25)").unwrap();
|
let result = engine.eval::<FLOAT>("add_float_rhai(2.5, 3.5)").unwrap();
|
||||||
assert!((result - 3.75).abs() < 1e-8);
|
assert_eq!(result, 6.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_even() {
|
fn test_is_even() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.register_fn("is_even", wrap_for_rhai!(is_even));
|
engine.register_fn("is_even_rhai", is_even_rhai_wrapper);
|
||||||
let result = engine.eval::<bool>("is_even(4)").unwrap();
|
let result_true = engine.eval::<bool>("is_even_rhai(4)").unwrap();
|
||||||
assert!(result);
|
assert_eq!(result_true, true);
|
||||||
let result = engine.eval::<bool>("is_even(5)").unwrap();
|
let result_false = engine.eval::<bool>("is_even_rhai(3)").unwrap();
|
||||||
assert!(!result);
|
assert_eq!(result_false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_maybe_add() {
|
fn test_maybe_add() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.register_fn("maybe_add", wrap_for_rhai!(maybe_add));
|
engine.register_fn("maybe_add_rhai", maybe_add_rhai_wrapper);
|
||||||
let result = engine.eval::<Option<INT>>("maybe_add(2, 3, true)").unwrap();
|
|
||||||
assert_eq!(result, Some(5));
|
// Test case where None is returned (expecting an error or specific handling in Rhai)
|
||||||
let result = engine.eval::<Option<INT>>("maybe_add(2, 3, false)").unwrap();
|
// Rhai treats Option::None as an empty Dynamic, which can lead to type mismatch if not handled.
|
||||||
assert_eq!(result, None);
|
// 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]
|
#[test]
|
||||||
@ -502,21 +524,20 @@ mod new_export_fn_tests {
|
|||||||
assert_eq!(result, 15);
|
assert_eq!(result, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn test_export_fn_custom_type_arg_return() { // This test was commented out, keeping as is for now
|
fn test_export_fn_custom_type_arg_return_new() {
|
||||||
// let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
// engine.build_type::<Point>();
|
engine.build_type::<Point>();
|
||||||
// // engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
||||||
|
|
||||||
// // let script = r#"
|
let script = r#"
|
||||||
// // let p = #{ x: 10, y: 20 };
|
let p = #{ x: 10, y: 20 };
|
||||||
// // let p_offset = offset_simple_point(p, 5);
|
let p_offset = offset_simple_point(p, 5);
|
||||||
// // p_offset.x
|
p_offset.x
|
||||||
// // "#;
|
"#;
|
||||||
// // let result = engine.eval::<INT>(script).unwrap();
|
let result = engine.eval::<INT>(script).unwrap();
|
||||||
// // assert_eq!(result, 15);
|
assert_eq!(result, 15);
|
||||||
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, FromRhaiMapDerive, ToRhaiMapDerive, CustomType)]
|
#[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().len(), 1);
|
||||||
assert_eq!(result_struct.optional_nested_vec.as_ref().unwrap()[0], SampleStruct { value: 3, name: "n3".to_string() });
|
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