serde_evaluate/extractor.rs
1use crate::error::EvaluateError;
2use crate::serializer::FieldValueExtractorSerializer;
3use crate::value::FieldScalarValue;
4use serde::Serialize;
5
6// =============================================================================
7// Path Validation Helper
8// =============================================================================
9
10/// Validates and converts path segments to a Vec<String>.
11///
12/// Returns an error if the path is empty or any segment is empty.
13fn validate_path<S: AsRef<str>>(segments: &[S]) -> Result<Vec<String>, EvaluateError> {
14 if segments.is_empty() {
15 return Err(EvaluateError::InvalidPath(
16 "Path cannot be empty".to_string(),
17 ));
18 }
19
20 let segments: Vec<String> = segments.iter().map(|s| s.as_ref().to_string()).collect();
21
22 if segments.iter().any(|s| s.is_empty()) {
23 return Err(EvaluateError::InvalidPath(
24 "Path segments cannot be empty".to_string(),
25 ));
26 }
27
28 Ok(segments)
29}
30
31// =============================================================================
32// Scalar Extractors
33// =============================================================================
34
35/// Facilitates the extraction of a scalar value from a specified field within a `Serialize`able struct.
36///
37/// This struct holds the configuration for the extraction, namely the target field name.
38/// The primary way to use this is via the associated function [`FieldExtractor::evaluate`].
39#[derive(Debug, Clone)]
40pub struct FieldExtractor {
41 field_name: String,
42}
43
44impl FieldExtractor {
45 /// Creates a new `FieldExtractor` configured to target the specified field name.
46 ///
47 /// Accepts any type that can be converted into a `String`, such as `&str`.
48 pub fn new<S: Into<String>>(field_name: S) -> Self {
49 FieldExtractor {
50 field_name: field_name.into(),
51 }
52 }
53
54 /// Extracts the scalar value of the configured `field_name` from the given `record`.
55 ///
56 /// This method drives the custom serialization process to capture the field's value.
57 ///
58 /// # Arguments
59 ///
60 /// * `record`: A reference to a struct that implements `serde::Serialize`.
61 ///
62 /// # Errors
63 ///
64 /// Returns `EvaluateError` if:
65 /// * The `field_name` is not found in the `record` ([`EvaluateError::FieldNotFound`]).
66 /// * The `field_name`'s value is not a supported scalar type ([`EvaluateError::UnsupportedType`]).
67 /// * Any other Serde serialization error occurs.
68 pub fn evaluate<T: Serialize>(&self, record: &T) -> Result<FieldScalarValue, EvaluateError> {
69 let mut serializer = FieldValueExtractorSerializer::new(&self.field_name);
70 // Attempt to serialize the record using our custom serializer.
71 record.serialize(&mut serializer)?;
72
73 // After serialization, check if the serializer captured a result.
74 serializer
75 .into_result()
76 .ok_or_else(|| EvaluateError::FieldNotFound {
77 field_name: self.field_name.clone(),
78 })
79 }
80}
81
82/// Extracts a potentially nested scalar field value using a pre-defined path.
83///
84/// This struct allows specifying a path as a sequence of field names.
85/// It uses the `FieldValueExtractorSerializer` internally to traverse the structure.
86#[derive(Debug, Clone)]
87pub struct NestedFieldExtractor {
88 /// The sequence of field names representing the path to the target value.
89 path_segments: Vec<String>,
90}
91
92impl NestedFieldExtractor {
93 /// Creates a new `NestedFieldExtractor` from a slice of path segments.
94 ///
95 /// Each element in the input slice represents a step in the path.
96 ///
97 /// # Arguments
98 ///
99 /// * `path_segments`: A slice where each element can be converted into a `&str`
100 /// (e.g., `&str`, `String`).
101 ///
102 /// # Errors
103 ///
104 /// Returns `EvaluateError::InvalidPath` if the input slice is empty or if any
105 /// segment converts to an empty string.
106 pub fn new_from_path<S: AsRef<str>>(path_segments: &[S]) -> Result<Self, EvaluateError> {
107 Ok(NestedFieldExtractor {
108 path_segments: validate_path(path_segments)?,
109 })
110 }
111
112 /// Evaluates the extractor against the given serializable value using the configured path.
113 ///
114 /// This triggers the serialization process, traversing the nested structure according
115 /// to `path_segments` and intercepting the target field's value.
116 ///
117 /// # Arguments
118 ///
119 /// * `value` - A reference to a value that implements `serde::Serialize`.
120 ///
121 /// # Returns
122 ///
123 /// * `Ok(FieldScalarValue)` if the field at the specified path is found and is a supported scalar type.
124 /// * `Err(EvaluateError)` if the path is invalid, an intermediate field is not a struct,
125 /// the final field is not found or has an unsupported type, or a serialization error occurs.
126 pub fn evaluate<T: Serialize>(&self, value: &T) -> Result<FieldScalarValue, EvaluateError> {
127 // Clone the path segments because new_nested takes ownership, but evaluate only has &self.
128 let mut serializer = FieldValueExtractorSerializer::new_nested(self.path_segments.clone());
129
130 // Attempt to serialize the record using our custom serializer.
131 value.serialize(&mut serializer)?;
132
133 // After serialization, check if the serializer captured a result.
134 serializer
135 .into_result()
136 .ok_or_else(|| EvaluateError::NestedFieldNotFound {
137 path: self.path_segments.clone(),
138 failed_at_index: None, // Index unknown at this point
139 })
140 }
141}
142
143// =============================================================================
144// Composite Extractor
145// =============================================================================
146
147/// Extracts multiple independent scalar fields from a single `Serialize`able record,
148/// returning them as an ordered `Vec<FieldScalarValue>`.
149///
150/// This is useful for building composite index keys where multiple field values
151/// are combined into a single ordered key.
152///
153/// # Example
154///
155/// ```rust
156/// use serde::Serialize;
157/// use serde_evaluate::{CompositeFieldExtractor, FieldScalarValue, EvaluateError};
158///
159/// #[derive(Serialize)]
160/// struct Record {
161/// last_name: String,
162/// first_name: String,
163/// age: u32,
164/// }
165///
166/// fn main() -> Result<(), EvaluateError> {
167/// let record = Record {
168/// last_name: "Smith".to_string(),
169/// first_name: "John".to_string(),
170/// age: 30,
171/// };
172///
173/// let extractor = CompositeFieldExtractor::new(&["last_name", "first_name", "age"])?;
174/// let values = extractor.evaluate(&record)?;
175///
176/// assert_eq!(values, vec![
177/// FieldScalarValue::String("Smith".to_string()),
178/// FieldScalarValue::String("John".to_string()),
179/// FieldScalarValue::U32(30),
180/// ]);
181/// Ok(())
182/// }
183/// ```
184#[derive(Debug, Clone)]
185pub struct CompositeFieldExtractor {
186 extractors: Vec<NestedFieldExtractor>,
187}
188
189impl CompositeFieldExtractor {
190 /// Creates a new `CompositeFieldExtractor` for top-level field names.
191 ///
192 /// Each field name is treated as a single-segment path.
193 ///
194 /// # Errors
195 ///
196 /// Returns `EvaluateError::InvalidPath` if the list is empty or any name is empty.
197 pub fn new<S: AsRef<str>>(field_names: &[S]) -> Result<Self, EvaluateError> {
198 if field_names.is_empty() {
199 return Err(EvaluateError::InvalidPath(
200 "Composite extractor requires at least one field".to_string(),
201 ));
202 }
203
204 let extractors = field_names
205 .iter()
206 .map(|name| NestedFieldExtractor::new_from_path(&[name.as_ref()]))
207 .collect::<Result<Vec<_>, _>>()?;
208
209 Ok(CompositeFieldExtractor { extractors })
210 }
211
212 /// Creates a new `CompositeFieldExtractor` from a slice of field paths.
213 ///
214 /// Each inner slice represents the path segments to a field, supporting
215 /// both top-level (single-segment) and nested (multi-segment) paths.
216 ///
217 /// # Example
218 ///
219 /// ```rust
220 /// use serde::Serialize;
221 /// use serde_evaluate::{CompositeFieldExtractor, FieldScalarValue, EvaluateError};
222 ///
223 /// #[derive(Serialize)]
224 /// struct Record {
225 /// name: String,
226 /// address: Address,
227 /// }
228 ///
229 /// #[derive(Serialize)]
230 /// struct Address {
231 /// zip: String,
232 /// }
233 ///
234 /// fn main() -> Result<(), EvaluateError> {
235 /// let record = Record {
236 /// name: "Alice".to_string(),
237 /// address: Address { zip: "90210".to_string() },
238 /// };
239 ///
240 /// let extractor = CompositeFieldExtractor::new_from_paths(&[
241 /// &["name"],
242 /// &["address", "zip"],
243 /// ])?;
244 /// let values = extractor.evaluate(&record)?;
245 ///
246 /// assert_eq!(values, vec![
247 /// FieldScalarValue::String("Alice".to_string()),
248 /// FieldScalarValue::String("90210".to_string()),
249 /// ]);
250 /// Ok(())
251 /// }
252 /// ```
253 ///
254 /// # Errors
255 ///
256 /// Returns `EvaluateError::InvalidPath` if the list is empty, any path is empty,
257 /// or any path segment is empty.
258 pub fn new_from_paths<S: AsRef<str>>(paths: &[&[S]]) -> Result<Self, EvaluateError> {
259 if paths.is_empty() {
260 return Err(EvaluateError::InvalidPath(
261 "Composite extractor requires at least one field".to_string(),
262 ));
263 }
264
265 let extractors = paths
266 .iter()
267 .map(|path| NestedFieldExtractor::new_from_path(path))
268 .collect::<Result<Vec<_>, _>>()?;
269
270 Ok(CompositeFieldExtractor { extractors })
271 }
272
273 /// Extracts scalar values for all configured fields from the given record.
274 ///
275 /// Returns values in the same order as the fields were specified during construction.
276 /// Fails fast on the first extraction error.
277 ///
278 /// # Arguments
279 ///
280 /// * `record`: A reference to a value that implements `serde::Serialize`.
281 ///
282 /// # Errors
283 ///
284 /// Returns the first `EvaluateError` encountered during extraction.
285 pub fn evaluate<T: Serialize>(
286 &self,
287 record: &T,
288 ) -> Result<Vec<FieldScalarValue>, EvaluateError> {
289 self.extractors
290 .iter()
291 .map(|extractor| extractor.evaluate(record))
292 .collect()
293 }
294}
295
296// =============================================================================
297// List Extractors (FanOut-style)
298// =============================================================================
299
300/// Extracts a list of scalar values from a `Vec<T>` field where T is a scalar type.
301///
302/// This enables FanOut-style extraction where each element of a list is returned
303/// separately, useful for indexing scenarios where each element needs to be
304/// processed individually.
305///
306/// # Example
307///
308/// ```rust
309/// use serde::Serialize;
310/// use serde_evaluate::{ListFieldExtractor, FieldScalarValue, EvaluateError};
311///
312/// #[derive(Serialize)]
313/// struct Record {
314/// id: u32,
315/// tags: Vec<String>,
316/// }
317///
318/// fn main() -> Result<(), EvaluateError> {
319/// let record = Record {
320/// id: 1,
321/// tags: vec!["rust".to_string(), "serde".to_string()],
322/// };
323///
324/// let extractor = ListFieldExtractor::new("tags");
325/// let values = extractor.evaluate(&record)?;
326///
327/// assert_eq!(values, vec![
328/// FieldScalarValue::String("rust".to_string()),
329/// FieldScalarValue::String("serde".to_string()),
330/// ]);
331/// Ok(())
332/// }
333/// ```
334#[derive(Debug, Clone)]
335pub struct ListFieldExtractor {
336 field_name: String,
337}
338
339impl ListFieldExtractor {
340 /// Creates a new `ListFieldExtractor` configured to target the specified field name.
341 ///
342 /// Accepts any type that can be converted into a `String`, such as `&str`.
343 pub fn new<S: Into<String>>(field_name: S) -> Self {
344 ListFieldExtractor {
345 field_name: field_name.into(),
346 }
347 }
348
349 /// Extracts all scalar elements from a `Vec<T>` field.
350 ///
351 /// # Arguments
352 ///
353 /// * `record`: A reference to a struct that implements `serde::Serialize`.
354 ///
355 /// # Returns
356 ///
357 /// * `Ok(Vec<FieldScalarValue>)` containing each element as a scalar value.
358 /// * `Ok(vec![])` for empty lists or `Option<Vec<T>>` with `None`.
359 ///
360 /// # Errors
361 ///
362 /// Returns `EvaluateError` if:
363 /// * The `field_name` is not found in the `record` ([`EvaluateError::FieldNotFound`]).
364 /// * The list elements are not scalar types ([`EvaluateError::UnsupportedType`]).
365 pub fn evaluate<T: Serialize>(
366 &self,
367 record: &T,
368 ) -> Result<Vec<FieldScalarValue>, EvaluateError> {
369 let mut serializer = FieldValueExtractorSerializer::new_list(&self.field_name);
370 record.serialize(&mut serializer)?;
371
372 serializer
373 .into_list_result()
374 .ok_or_else(|| EvaluateError::FieldNotFound {
375 field_name: self.field_name.clone(),
376 })
377 }
378}
379
380/// Extracts a list of scalar values from a nested `Vec<T>` field using a path.
381///
382/// This enables FanOut-style extraction for lists within nested structures.
383///
384/// # Example
385///
386/// ```rust
387/// use serde::Serialize;
388/// use serde_evaluate::{NestedListFieldExtractor, FieldScalarValue, EvaluateError};
389///
390/// #[derive(Serialize)]
391/// struct Record {
392/// metadata: Metadata,
393/// }
394///
395/// #[derive(Serialize)]
396/// struct Metadata {
397/// labels: Vec<String>,
398/// }
399///
400/// fn main() -> Result<(), EvaluateError> {
401/// let record = Record {
402/// metadata: Metadata {
403/// labels: vec!["label1".to_string(), "label2".to_string()],
404/// },
405/// };
406///
407/// let extractor = NestedListFieldExtractor::new_from_path(&["metadata", "labels"])?;
408/// let values = extractor.evaluate(&record)?;
409///
410/// assert_eq!(values, vec![
411/// FieldScalarValue::String("label1".to_string()),
412/// FieldScalarValue::String("label2".to_string()),
413/// ]);
414/// Ok(())
415/// }
416/// ```
417#[derive(Debug, Clone)]
418pub struct NestedListFieldExtractor {
419 path_segments: Vec<String>,
420}
421
422impl NestedListFieldExtractor {
423 /// Creates a new `NestedListFieldExtractor` from a slice of path segments.
424 ///
425 /// Each element in the input slice represents a step in the path.
426 ///
427 /// # Arguments
428 ///
429 /// * `path_segments`: A slice where each element can be converted into a `&str`
430 /// (e.g., `&str`, `String`).
431 ///
432 /// # Errors
433 ///
434 /// Returns `EvaluateError::InvalidPath` if the input slice is empty or if any
435 /// segment converts to an empty string.
436 pub fn new_from_path<S: AsRef<str>>(path_segments: &[S]) -> Result<Self, EvaluateError> {
437 Ok(NestedListFieldExtractor {
438 path_segments: validate_path(path_segments)?,
439 })
440 }
441
442 /// Extracts all scalar elements from a nested `Vec<T>` field.
443 ///
444 /// # Arguments
445 ///
446 /// * `value` - A reference to a value that implements `serde::Serialize`.
447 ///
448 /// # Returns
449 ///
450 /// * `Ok(Vec<FieldScalarValue>)` containing each element as a scalar value.
451 /// * `Ok(vec![])` for empty lists or `Option<Vec<T>>` with `None`.
452 ///
453 /// # Errors
454 ///
455 /// Returns `EvaluateError` if:
456 /// * The path is not found ([`EvaluateError::NestedFieldNotFound`]).
457 /// * The list elements are not scalar types ([`EvaluateError::UnsupportedType`]).
458 pub fn evaluate<T: Serialize>(
459 &self,
460 value: &T,
461 ) -> Result<Vec<FieldScalarValue>, EvaluateError> {
462 let mut serializer =
463 FieldValueExtractorSerializer::new_nested_list(self.path_segments.clone());
464 value.serialize(&mut serializer)?;
465
466 serializer
467 .into_list_result()
468 .ok_or_else(|| EvaluateError::NestedFieldNotFound {
469 path: self.path_segments.clone(),
470 failed_at_index: None, // Index unknown at this point
471 })
472 }
473}