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}