Skip to main content

reinhardt_urls/proxy/
scalar.rs

1//! Scalar association proxies for one-to-one and many-to-one relationships
2
3use serde::{Deserialize, Serialize};
4
5use super::reflection::downcast_relationship;
6use crate::proxy::ProxyResult;
7use crate::proxy::ScalarValue;
8
9/// Scalar proxy for accessing a single related object's attribute
10///
11/// Used for one-to-one and many-to-one relationships where the proxy
12/// returns a single scalar value.
13///
14/// ## Example
15///
16/// ```rust,no_run
17/// # use reinhardt_urls::proxy::ScalarProxy;
18/// # #[tokio::main]
19/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
20/// # #[derive(Clone)]
21/// # struct User;
22/// # let user = User;
23/// // User has one profile, access profile.bio directly
24/// let bio_proxy = ScalarProxy::new("profile", "bio");
25/// // let bio: Option<String> = bio_proxy.get_value(&user).await?;
26/// # Ok(())
27/// # }
28/// ```
29#[derive(Debug, Clone)]
30pub struct ScalarProxy {
31	/// Name of the relationship
32	pub relationship: String,
33
34	/// Name of the attribute on the related object
35	pub attribute: String,
36}
37
38impl ScalarProxy {
39	/// Create a new scalar proxy
40	///
41	/// # Examples
42	///
43	/// ```
44	/// use reinhardt_urls::proxy::ScalarProxy;
45	///
46	/// let proxy = ScalarProxy::new("profile", "bio");
47	/// assert_eq!(proxy.relationship, "profile");
48	/// assert_eq!(proxy.attribute, "bio");
49	/// ```
50	pub fn new(relationship: &str, attribute: &str) -> Self {
51		Self {
52			relationship: relationship.to_string(),
53			attribute: attribute.to_string(),
54		}
55	}
56	/// Get the scalar value from the related object
57	///
58	/// # Examples
59	///
60	/// ```
61	/// use reinhardt_urls::proxy::ScalarProxy;
62	///
63	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
64	/// let proxy = ScalarProxy::new("profile", "bio");
65	// Assuming `user` implements Reflectable
66	// let bio = proxy.get_value(&user).await?;
67	/// # Ok(())
68	/// # }
69	/// ```
70	pub async fn get_value<T>(&self, source: &T) -> ProxyResult<Option<ScalarValue>>
71	where
72		T: super::reflection::Reflectable,
73	{
74		// 1. Access the relationship on source
75		let relationship = match source.get_relationship(&self.relationship) {
76			Some(rel) => rel,
77			None => return Ok(None), // Relationship not found, return None
78		};
79
80		// 2. Downcast to Box<dyn Reflectable>
81		let related =
82			downcast_relationship::<Box<dyn super::reflection::Reflectable>>(relationship)?;
83
84		// 3. Get the attribute from the related object
85		let value = related.get_attribute(&self.attribute);
86
87		Ok(value)
88	}
89	/// Set the scalar value on the related object
90	///
91	/// # Examples
92	///
93	/// ```
94	/// use reinhardt_urls::proxy::{ScalarProxy, ScalarValue};
95	///
96	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
97	/// let proxy = ScalarProxy::new("profile", "bio");
98	/// let value = ScalarValue::String("New bio".to_string());
99	// Assuming `user` implements Reflectable
100	// proxy.set_value(&mut user, value).await?;
101	/// # Ok(())
102	/// # }
103	/// ```
104	pub async fn set_value<T>(&self, source: &mut T, value: ScalarValue) -> ProxyResult<()>
105	where
106		T: super::reflection::Reflectable,
107	{
108		// Use the new set_relationship_attribute method to avoid type casting issues
109		source.set_relationship_attribute(&self.relationship, &self.attribute, value)?;
110		Ok(())
111	}
112}
113
114/// Comparison operators for scalar proxies
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub enum ScalarComparison {
117	/// Equal to
118	Eq(ScalarValue),
119
120	/// Not equal to
121	Ne(ScalarValue),
122
123	/// Greater than
124	Gt(ScalarValue),
125
126	/// Greater than or equal to
127	Gte(ScalarValue),
128
129	/// Less than
130	Lt(ScalarValue),
131
132	/// Less than or equal to
133	Lte(ScalarValue),
134
135	/// In collection
136	In(Vec<ScalarValue>),
137
138	/// Not in collection
139	NotIn(Vec<ScalarValue>),
140
141	/// Is null
142	IsNull,
143
144	/// Is not null
145	IsNotNull,
146
147	/// Like pattern (for strings)
148	Like(String),
149
150	/// Not like pattern (for strings)
151	NotLike(String),
152}
153
154impl ScalarComparison {
155	/// Create an equality comparison
156	///
157	/// # Examples
158	///
159	/// ```
160	/// use reinhardt_urls::proxy::{ScalarComparison, ScalarValue};
161	///
162	/// let comparison = ScalarComparison::eq("test");
163	/// assert!(matches!(comparison, ScalarComparison::Eq(_)));
164	/// ```
165	pub fn eq(value: impl Into<ScalarValue>) -> Self {
166		ScalarComparison::Eq(value.into())
167	}
168	/// Create a not equal comparison
169	///
170	/// # Examples
171	///
172	/// ```
173	/// use reinhardt_urls::proxy::{ScalarComparison, ScalarValue};
174	///
175	/// let comparison = ScalarComparison::ne(42);
176	/// assert!(matches!(comparison, ScalarComparison::Ne(_)));
177	/// ```
178	pub fn ne(value: impl Into<ScalarValue>) -> Self {
179		ScalarComparison::Ne(value.into())
180	}
181	/// Create a greater than comparison
182	///
183	/// # Examples
184	///
185	/// ```
186	/// use reinhardt_urls::proxy::{ScalarComparison, ScalarValue};
187	///
188	/// let comparison = ScalarComparison::gt(100);
189	/// assert!(matches!(comparison, ScalarComparison::Gt(ScalarValue::Integer(100))));
190	/// ```
191	pub fn gt(value: impl Into<ScalarValue>) -> Self {
192		ScalarComparison::Gt(value.into())
193	}
194	/// Create a greater than or equal comparison
195	///
196	/// # Examples
197	///
198	/// ```
199	/// use reinhardt_urls::proxy::{ScalarComparison, ScalarValue};
200	///
201	/// let comparison = ScalarComparison::gte(50);
202	/// assert!(matches!(comparison, ScalarComparison::Gte(ScalarValue::Integer(50))));
203	/// ```
204	pub fn gte(value: impl Into<ScalarValue>) -> Self {
205		ScalarComparison::Gte(value.into())
206	}
207	/// Create a less than comparison
208	///
209	/// # Examples
210	///
211	/// ```
212	/// use reinhardt_urls::proxy::{ScalarComparison, ScalarValue};
213	///
214	/// let comparison = ScalarComparison::lt(25);
215	/// assert!(matches!(comparison, ScalarComparison::Lt(ScalarValue::Integer(25))));
216	/// ```
217	pub fn lt(value: impl Into<ScalarValue>) -> Self {
218		ScalarComparison::Lt(value.into())
219	}
220	/// Create a less than or equal comparison
221	///
222	/// # Examples
223	///
224	/// ```
225	/// use reinhardt_urls::proxy::{ScalarComparison, ScalarValue};
226	///
227	/// let comparison = ScalarComparison::lte(75);
228	/// assert!(matches!(comparison, ScalarComparison::Lte(ScalarValue::Integer(75))));
229	/// ```
230	pub fn lte(value: impl Into<ScalarValue>) -> Self {
231		ScalarComparison::Lte(value.into())
232	}
233	/// Create an IN comparison
234	///
235	/// # Examples
236	///
237	/// ```
238	/// use reinhardt_urls::proxy::{ScalarComparison, ScalarValue};
239	///
240	/// let values = vec![ScalarValue::Integer(1), ScalarValue::Integer(2)];
241	/// let comparison = ScalarComparison::in_values(values);
242	/// assert!(matches!(comparison, ScalarComparison::In(_)));
243	/// ```
244	pub fn in_values(values: Vec<ScalarValue>) -> Self {
245		ScalarComparison::In(values)
246	}
247	/// Create a NOT IN comparison
248	///
249	/// # Examples
250	///
251	/// ```
252	/// use reinhardt_urls::proxy::{ScalarComparison, ScalarValue};
253	///
254	/// let values = vec![ScalarValue::String("banned".to_string())];
255	/// let comparison = ScalarComparison::not_in_values(values);
256	/// assert!(matches!(comparison, ScalarComparison::NotIn(_)));
257	/// ```
258	pub fn not_in_values(values: Vec<ScalarValue>) -> Self {
259		ScalarComparison::NotIn(values)
260	}
261	/// Create an IS NULL comparison
262	///
263	/// # Examples
264	///
265	/// ```
266	/// use reinhardt_urls::proxy::ScalarComparison;
267	///
268	/// let comparison = ScalarComparison::is_null();
269	/// assert!(matches!(comparison, ScalarComparison::IsNull));
270	/// ```
271	pub fn is_null() -> Self {
272		ScalarComparison::IsNull
273	}
274	/// Create an IS NOT NULL comparison
275	///
276	/// # Examples
277	///
278	/// ```
279	/// use reinhardt_urls::proxy::ScalarComparison;
280	///
281	/// let comparison = ScalarComparison::is_not_null();
282	/// assert!(matches!(comparison, ScalarComparison::IsNotNull));
283	/// ```
284	pub fn is_not_null() -> Self {
285		ScalarComparison::IsNotNull
286	}
287	/// Create a LIKE comparison
288	///
289	/// # Examples
290	///
291	/// ```
292	/// use reinhardt_urls::proxy::ScalarComparison;
293	///
294	/// let comparison = ScalarComparison::like("%test%");
295	/// assert!(matches!(comparison, ScalarComparison::Like(_)));
296	/// ```
297	pub fn like(pattern: &str) -> Self {
298		ScalarComparison::Like(pattern.to_string())
299	}
300	/// Create a NOT LIKE comparison
301	///
302	/// # Examples
303	///
304	/// ```
305	/// use reinhardt_urls::proxy::ScalarComparison;
306	///
307	/// let comparison = ScalarComparison::not_like("%spam%");
308	/// assert!(matches!(comparison, ScalarComparison::NotLike(_)));
309	/// ```
310	pub fn not_like(pattern: &str) -> Self {
311		ScalarComparison::NotLike(pattern.to_string())
312	}
313}
314
315#[cfg(test)]
316mod tests {
317	use super::*;
318
319	#[test]
320	fn test_scalar_proxy_creation() {
321		let proxy = ScalarProxy::new("profile", "bio");
322		assert_eq!(proxy.relationship, "profile");
323		assert_eq!(proxy.attribute, "bio");
324	}
325
326	#[test]
327	fn test_proxy_scalar_comparison_unit() {
328		let eq = ScalarComparison::eq("test");
329		assert!(matches!(eq, ScalarComparison::Eq(_)));
330
331		let gt = ScalarComparison::gt(42);
332		assert!(matches!(gt, ScalarComparison::Gt(_)));
333
334		let is_null = ScalarComparison::is_null();
335		assert!(matches!(is_null, ScalarComparison::IsNull));
336
337		let like = ScalarComparison::like("%test%");
338		assert!(matches!(like, ScalarComparison::Like(_)));
339	}
340}