use std::marker::PhantomData;
type InstanceGetterFn<T, R> = Box<dyn Fn(&T) -> R + Send + Sync>;
type ExpressionGetterFn = Box<dyn Fn() -> String + Send + Sync>;
pub struct HybridProperty<T, R> {
instance_getter: InstanceGetterFn<T, R>,
expression_getter: Option<ExpressionGetterFn>,
_phantom: PhantomData<T>,
}
impl<T, R> HybridProperty<T, R> {
pub fn new<F>(instance_getter: F) -> Self
where
F: Fn(&T) -> R + Send + Sync + 'static,
{
Self {
instance_getter: Box::new(instance_getter),
expression_getter: None,
_phantom: PhantomData,
}
}
pub fn with_expression<F>(mut self, expression_getter: F) -> Self
where
F: Fn() -> String + Send + Sync + 'static,
{
self.expression_getter = Some(Box::new(expression_getter));
self
}
pub fn get(&self, instance: &T) -> R {
(self.instance_getter)(instance)
}
pub fn expression(&self) -> Option<String> {
self.expression_getter.as_ref().map(|f| f())
}
}
type InstanceMethodFn<T, A, R> = Box<dyn Fn(&T, A) -> R + Send + Sync>;
type ExpressionMethodFn<A> = Box<dyn Fn(A) -> String + Send + Sync>;
pub struct HybridMethod<T, A, R> {
instance_method: InstanceMethodFn<T, A, R>,
expression_method: Option<ExpressionMethodFn<A>>,
_phantom: PhantomData<(T, A)>,
}
impl<T, A, R> HybridMethod<T, A, R> {
pub fn new<F>(instance_method: F) -> Self
where
F: Fn(&T, A) -> R + Send + Sync + 'static,
{
Self {
instance_method: Box::new(instance_method),
expression_method: None,
_phantom: PhantomData,
}
}
pub fn with_expression<F>(mut self, expression_method: F) -> Self
where
F: Fn(A) -> String + Send + Sync + 'static,
{
self.expression_method = Some(Box::new(expression_method));
self
}
pub fn call(&self, instance: &T, arg: A) -> R {
(self.instance_method)(instance, arg)
}
pub fn expression(&self, arg: A) -> Option<String> {
self.expression_method.as_ref().map(|f| f(arg))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct User {
first_name: String,
last_name: String,
}
#[test]
fn test_hybrid_property_unit() {
let prop =
HybridProperty::new(|user: &User| format!("{} {}", user.first_name, user.last_name))
.with_expression(|| "CONCAT(first_name, ' ', last_name)".to_string());
let user = User {
first_name: "John".to_string(),
last_name: "Doe".to_string(),
};
assert_eq!(prop.get(&user), "John Doe");
assert!(prop.expression().is_some());
}
}