Skip to main content

reinhardt_urls/proxy/
proxy.rs

1//! Core association proxy implementation
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use std::marker::PhantomData;
6
7use crate::proxy::builder::{GetterFn, SetterFn, ValidatorFn};
8use crate::proxy::{ProxyError, ProxyResult};
9
10/// Association proxy for transparent access to related object attributes
11///
12/// ## Example
13///
14/// ```rust,no_run
15/// # use reinhardt_urls::proxy::AssociationProxy;
16/// # #[derive(Clone)]
17/// # struct UserKeyword;
18/// # #[derive(Clone)]
19/// # struct User;
20/// # #[tokio::main]
21/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
22/// # let user = User;
23/// // Access keyword names through user_keywords relationship
24/// let proxy: AssociationProxy<UserKeyword, String> = AssociationProxy::new("user_keywords", "keyword");
25/// // let names = proxy.get_collection(&user).await?;
26/// # Ok(())
27/// # }
28/// ```
29pub struct AssociationProxy<T, U> {
30	/// Optional name/alias for this proxy
31	pub name: Option<String>,
32
33	/// Name of the relationship attribute
34	pub relationship: String,
35
36	/// Name of the attribute on the related object
37	pub attribute: String,
38
39	/// Optional creator function for new associations
40	pub creator: Option<fn(U) -> T>,
41
42	/// Optional custom getter function
43	pub getter: Option<GetterFn<T, U>>,
44
45	/// Optional custom setter function
46	pub setter: Option<SetterFn<T, U>>,
47
48	/// Optional validator function
49	pub validator: Option<ValidatorFn<U>>,
50
51	/// Optional transform function
52	pub transform: Option<fn(U) -> U>,
53
54	/// Phantom data for type parameters
55	_phantom: PhantomData<(T, U)>,
56}
57
58impl<T, U> AssociationProxy<T, U> {
59	/// Create a new association proxy
60	///
61	/// # Arguments
62	///
63	/// * `relationship` - Name of the relationship to traverse
64	/// * `attribute` - Name of the attribute to access on related objects
65	///
66	/// # Examples
67	///
68	/// ```
69	/// use reinhardt_urls::proxy::AssociationProxy;
70	///
71	/// let proxy: AssociationProxy<(), ()> = AssociationProxy::new("user_keywords", "keyword");
72	/// assert_eq!(proxy.relationship, "user_keywords");
73	/// assert_eq!(proxy.attribute, "keyword");
74	/// ```
75	pub fn new(relationship: &str, attribute: &str) -> Self {
76		Self {
77			name: None,
78			relationship: relationship.to_string(),
79			attribute: attribute.to_string(),
80			creator: None,
81			getter: None,
82			setter: None,
83			validator: None,
84			transform: None,
85			_phantom: PhantomData,
86		}
87	}
88	/// Set a creator function for new associations
89	///
90	/// The creator function is called when adding new items to the association.
91	/// It should create an association object from the target value.
92	///
93	/// # Examples
94	///
95	/// ```
96	/// use reinhardt_urls::proxy::AssociationProxy;
97	///
98	/// fn create_association(value: String) -> i32 { 42 }
99	///
100	/// let proxy = AssociationProxy::new("items", "value")
101	///     .with_creator(create_association);
102	/// assert!(proxy.creator.is_some());
103	/// ```
104	pub fn with_creator(mut self, creator: fn(U) -> T) -> Self {
105		self.creator = Some(creator);
106		self
107	}
108
109	/// Set a custom getter function
110	///
111	/// # Examples
112	///
113	/// ```
114	/// use reinhardt_urls::proxy::AssociationProxy;
115	///
116	/// fn custom_getter(_obj: &()) -> Result<(), reinhardt_urls::proxy::ProxyError> {
117	///     Ok(())
118	/// }
119	///
120	/// let proxy = AssociationProxy::new("data", "value")
121	///     .with_getter(custom_getter);
122	/// assert!(proxy.getter.is_some());
123	/// ```
124	pub fn with_getter(mut self, getter: fn(&T) -> Result<U, crate::proxy::ProxyError>) -> Self {
125		self.getter = Some(getter);
126		self
127	}
128
129	/// Set a custom setter function
130	///
131	/// # Examples
132	///
133	/// ```
134	/// use reinhardt_urls::proxy::AssociationProxy;
135	///
136	/// fn custom_setter(_obj: &mut (), _value: ()) -> Result<(), reinhardt_urls::proxy::ProxyError> {
137	///     Ok(())
138	/// }
139	///
140	/// let proxy = AssociationProxy::new("data", "value")
141	///     .with_setter(custom_setter);
142	/// assert!(proxy.setter.is_some());
143	/// ```
144	pub fn with_setter(
145		mut self,
146		setter: fn(&mut T, U) -> Result<(), crate::proxy::ProxyError>,
147	) -> Self {
148		self.setter = Some(setter);
149		self
150	}
151
152	/// Set a validator function
153	///
154	/// # Examples
155	///
156	/// ```
157	/// use reinhardt_urls::proxy::{AssociationProxy, ProxyError};
158	///
159	/// fn validate_value(_value: &()) -> Result<(), ProxyError> {
160	///     Ok(())
161	/// }
162	///
163	/// let proxy: AssociationProxy<(), ()> = AssociationProxy::new("data", "value")
164	///     .with_validator(validate_value);
165	/// assert!(proxy.validator.is_some());
166	/// ```
167	pub fn with_validator(
168		mut self,
169		validator: fn(&U) -> Result<(), crate::proxy::ProxyError>,
170	) -> Self {
171		self.validator = Some(validator);
172		self
173	}
174
175	/// Set a transform function
176	///
177	/// # Examples
178	///
179	/// ```
180	/// use reinhardt_urls::proxy::AssociationProxy;
181	///
182	/// fn transform_value(value: ()) -> () {
183	///     value
184	/// }
185	///
186	/// let proxy: AssociationProxy<(), ()> = AssociationProxy::new("data", "value")
187	///     .with_transform(transform_value);
188	/// assert!(proxy.transform.is_some());
189	/// ```
190	pub fn with_transform(mut self, transform: fn(U) -> U) -> Self {
191		self.transform = Some(transform);
192		self
193	}
194
195	/// Get the proxy name if set
196	///
197	/// # Examples
198	///
199	/// ```
200	/// use reinhardt_urls::proxy::AssociationProxy;
201	///
202	/// let mut proxy: AssociationProxy<(), ()> = AssociationProxy::new("rel", "attr");
203	/// proxy.name = Some("my_proxy".to_string());
204	/// assert_eq!(proxy.name(), Some("my_proxy"));
205	/// ```
206	pub fn name(&self) -> Option<&str> {
207		self.name.as_deref()
208	}
209
210	/// Get the relationship name
211	///
212	/// # Examples
213	///
214	/// ```
215	/// use reinhardt_urls::proxy::AssociationProxy;
216	///
217	/// let proxy: AssociationProxy<(), ()> = AssociationProxy::new("posts", "title");
218	/// assert_eq!(proxy.relationship(), "posts");
219	/// ```
220	pub fn relationship(&self) -> &str {
221		&self.relationship
222	}
223
224	/// Get the attribute name
225	///
226	/// # Examples
227	///
228	/// ```
229	/// use reinhardt_urls::proxy::AssociationProxy;
230	///
231	/// let proxy: AssociationProxy<(), ()> = AssociationProxy::new("posts", "title");
232	/// assert_eq!(proxy.attribute(), "title");
233	/// ```
234	pub fn attribute(&self) -> &str {
235		&self.attribute
236	}
237
238	/// Check if custom accessors (getter/setter) are configured
239	///
240	/// # Examples
241	///
242	/// ```
243	/// use reinhardt_urls::proxy::AssociationProxy;
244	///
245	/// fn custom_getter(_obj: &()) -> Result<(), reinhardt_urls::proxy::ProxyError> {
246	///     Ok(())
247	/// }
248	///
249	/// let proxy = AssociationProxy::new("data", "value")
250	///     .with_getter(custom_getter);
251	/// assert!(proxy.has_custom_accessors());
252	/// ```
253	pub fn has_custom_accessors(&self) -> bool {
254		self.getter.is_some() || self.setter.is_some()
255	}
256
257	/// Check if a validator is configured
258	///
259	/// # Examples
260	///
261	/// ```
262	/// use reinhardt_urls::proxy::{AssociationProxy, ProxyError};
263	///
264	/// fn validate(_value: &()) -> Result<(), ProxyError> {
265	///     Ok(())
266	/// }
267	///
268	/// let proxy: AssociationProxy<(), ()> = AssociationProxy::new("data", "value")
269	///     .with_validator(validate);
270	/// assert!(proxy.has_validator());
271	/// ```
272	pub fn has_validator(&self) -> bool {
273		self.validator.is_some()
274	}
275
276	/// Check if a transform function is configured
277	///
278	/// # Examples
279	///
280	/// ```
281	/// use reinhardt_urls::proxy::AssociationProxy;
282	///
283	/// fn transform(value: () ) -> () { value }
284	///
285	/// let proxy: AssociationProxy<(), ()> = AssociationProxy::new("data", "value")
286	///     .with_transform(transform);
287	/// assert!(proxy.has_transform());
288	/// ```
289	pub fn has_transform(&self) -> bool {
290		self.transform.is_some()
291	}
292}
293
294/// Trait for accessing proxy targets
295#[async_trait]
296pub trait ProxyAccessor<T> {
297	/// Get the target value(s) from the source object
298	async fn get(&self, source: &T) -> ProxyResult<ProxyTarget>;
299
300	/// Set the target value(s) on the source object
301	async fn set(&self, source: &mut T, value: ProxyTarget) -> ProxyResult<()>;
302}
303
304/// Represents the target of a proxy operation
305#[derive(Debug, Clone, Serialize, Deserialize)]
306#[serde(untagged)]
307pub enum ProxyTarget {
308	/// Single scalar value
309	Scalar(ScalarValue),
310
311	/// Collection of values
312	Collection(Vec<ScalarValue>),
313
314	/// No value (None)
315	None,
316}
317
318/// Scalar value types supported by proxies
319#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
320#[serde(untagged)]
321pub enum ScalarValue {
322	/// A string value.
323	String(String),
324	/// A 64-bit integer value.
325	Integer(i64),
326	/// A 64-bit floating-point value.
327	Float(f64),
328	/// A boolean value.
329	Boolean(bool),
330	/// A null (absent) value.
331	Null,
332}
333
334impl From<String> for ScalarValue {
335	fn from(s: String) -> Self {
336		ScalarValue::String(s)
337	}
338}
339
340impl From<&str> for ScalarValue {
341	fn from(s: &str) -> Self {
342		ScalarValue::String(s.to_string())
343	}
344}
345
346impl From<i64> for ScalarValue {
347	fn from(i: i64) -> Self {
348		ScalarValue::Integer(i)
349	}
350}
351
352impl From<f64> for ScalarValue {
353	fn from(f: f64) -> Self {
354		ScalarValue::Float(f)
355	}
356}
357
358impl From<bool> for ScalarValue {
359	fn from(b: bool) -> Self {
360		ScalarValue::Boolean(b)
361	}
362}
363
364impl ScalarValue {
365	/// Try to convert to String
366	///
367	/// # Examples
368	///
369	/// ```
370	/// use reinhardt_urls::proxy::ScalarValue;
371	///
372	/// let value = ScalarValue::String("hello".to_string());
373	/// assert_eq!(value.as_string().unwrap(), "hello");
374	///
375	/// let int_value = ScalarValue::Integer(42);
376	/// assert!(int_value.as_string().is_err());
377	/// ```
378	pub fn as_string(&self) -> ProxyResult<String> {
379		match self {
380			ScalarValue::String(s) => Ok(s.clone()),
381			_ => Err(ProxyError::TypeMismatch {
382				expected: "String".to_string(),
383				actual: format!("{:?}", self),
384			}),
385		}
386	}
387	/// Try to convert to i64
388	///
389	/// # Examples
390	///
391	/// ```
392	/// use reinhardt_urls::proxy::ScalarValue;
393	///
394	/// let value = ScalarValue::Integer(42);
395	/// assert_eq!(value.as_integer().unwrap(), 42);
396	///
397	/// let str_value = ScalarValue::String("test".to_string());
398	/// assert!(str_value.as_integer().is_err());
399	/// ```
400	pub fn as_integer(&self) -> ProxyResult<i64> {
401		match self {
402			ScalarValue::Integer(i) => Ok(*i),
403			_ => Err(ProxyError::TypeMismatch {
404				expected: "Integer".to_string(),
405				actual: format!("{:?}", self),
406			}),
407		}
408	}
409	/// Try to convert to f64
410	///
411	/// # Examples
412	///
413	/// ```
414	/// use reinhardt_urls::proxy::ScalarValue;
415	///
416	/// let value = ScalarValue::Float(3.15);
417	/// assert_eq!(value.as_float().unwrap(), 3.15);
418	///
419	/// let bool_value = ScalarValue::Boolean(true);
420	/// assert!(bool_value.as_float().is_err());
421	/// ```
422	pub fn as_float(&self) -> ProxyResult<f64> {
423		match self {
424			ScalarValue::Float(f) => Ok(*f),
425			_ => Err(ProxyError::TypeMismatch {
426				expected: "Float".to_string(),
427				actual: format!("{:?}", self),
428			}),
429		}
430	}
431	/// Try to convert to bool
432	///
433	/// # Examples
434	///
435	/// ```
436	/// use reinhardt_urls::proxy::ScalarValue;
437	///
438	/// let value = ScalarValue::Boolean(true);
439	/// assert!(value.as_boolean().unwrap());
440	///
441	/// let int_value = ScalarValue::Integer(1);
442	/// assert!(int_value.as_boolean().is_err());
443	/// ```
444	pub fn as_boolean(&self) -> ProxyResult<bool> {
445		match self {
446			ScalarValue::Boolean(b) => Ok(*b),
447			_ => Err(ProxyError::TypeMismatch {
448				expected: "Boolean".to_string(),
449				actual: format!("{:?}", self),
450			}),
451		}
452	}
453	/// Check if value is null
454	///
455	/// # Examples
456	///
457	/// ```
458	/// use reinhardt_urls::proxy::ScalarValue;
459	///
460	/// let null_value = ScalarValue::Null;
461	/// assert!(null_value.is_null());
462	///
463	/// let string_value = ScalarValue::String("test".to_string());
464	/// assert!(!string_value.is_null());
465	/// ```
466	pub fn is_null(&self) -> bool {
467		matches!(self, ScalarValue::Null)
468	}
469}
470
471#[cfg(test)]
472mod tests {
473	use super::*;
474
475	#[test]
476	fn test_proxy_scalar_conversions_unit() {
477		let s = ScalarValue::String("test".to_string());
478		assert_eq!(s.as_string().unwrap(), "test");
479
480		let i = ScalarValue::Integer(42);
481		assert_eq!(i.as_integer().unwrap(), 42);
482
483		let f = ScalarValue::Float(3.15);
484		assert_eq!(f.as_float().unwrap(), 3.15);
485
486		let b = ScalarValue::Boolean(true);
487		assert!(b.as_boolean().unwrap());
488	}
489
490	#[test]
491	fn test_proxy_scalar_type_mismatch_unit() {
492		let s = ScalarValue::String("test".to_string());
493		assert!(s.as_integer().is_err());
494	}
495}