Skip to main content

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}