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}