fuzzy_parser/
schema.rs

1//! Schema definitions for fuzzy repair
2//!
3//! This module provides schema types that callers use to define
4//! valid field names and type discriminators for fuzzy matching.
5
6/// Schema for a tagged enum (discriminated union)
7///
8/// Used for types with a discriminator field (e.g., tag: "type", "kind")
9///
10/// # Example
11///
12/// ```
13/// use fuzzy_parser::TaggedEnumSchema;
14///
15/// let schema = TaggedEnumSchema::new(
16///     "type",
17///     &["AddDerive", "RemoveDerive"],
18///     |tag| match tag {
19///         "AddDerive" | "RemoveDerive" => Some(&["target", "derives"][..]),
20///         _ => None,
21///     },
22/// )
23/// .with_enum_array("derives", &["Debug", "Clone", "Serialize"])
24/// .with_nested_object("config", &["timeout", "retries"]);
25/// ```
26pub struct TaggedEnumSchema<F>
27where
28    F: Fn(&str) -> Option<&'static [&'static str]>,
29{
30    /// The discriminator field name (e.g., "type", "kind")
31    pub tag_field: &'static str,
32    /// Valid tag values (e.g., ["AddDerive", "RenameIdent", ...])
33    pub valid_tags: &'static [&'static str],
34    /// Function to get valid fields for a given tag value
35    pub fields_for_tag: F,
36    /// Fields that contain arrays of enum values: (field_name, valid_values)
37    pub enum_arrays: Vec<(&'static str, &'static [&'static str])>,
38    /// Fields that contain nested objects: (field_name, valid_fields)
39    pub nested_objects: Vec<(&'static str, &'static [&'static str])>,
40}
41
42impl<F> TaggedEnumSchema<F>
43where
44    F: Fn(&str) -> Option<&'static [&'static str]>,
45{
46    /// Create a new tagged enum schema
47    pub fn new(
48        tag_field: &'static str,
49        valid_tags: &'static [&'static str],
50        fields_for_tag: F,
51    ) -> Self {
52        Self {
53            tag_field,
54            valid_tags,
55            fields_for_tag,
56            enum_arrays: Vec::new(),
57            nested_objects: Vec::new(),
58        }
59    }
60
61    /// Add an enum array field for repair
62    ///
63    /// Values in this array field will be fuzzy-matched against `valid_values`.
64    ///
65    /// # Example
66    ///
67    /// ```
68    /// use fuzzy_parser::TaggedEnumSchema;
69    ///
70    /// let schema = TaggedEnumSchema::new("type", &["AddDerive"], |_| Some(&["derives"][..]))
71    ///     .with_enum_array("derives", &["Debug", "Clone", "Serialize"]);
72    /// // Now "Debg" in derives array will be corrected to "Debug"
73    /// ```
74    pub fn with_enum_array(
75        mut self,
76        field: &'static str,
77        valid_values: &'static [&'static str],
78    ) -> Self {
79        self.enum_arrays.push((field, valid_values));
80        self
81    }
82
83    /// Add a nested object field for repair
84    ///
85    /// Field names in this nested object will be fuzzy-matched against `valid_fields`.
86    ///
87    /// # Example
88    ///
89    /// ```
90    /// use fuzzy_parser::TaggedEnumSchema;
91    ///
92    /// let schema = TaggedEnumSchema::new("type", &["Configure"], |_| Some(&["config"][..]))
93    ///     .with_nested_object("config", &["timeout", "retries", "enabled"]);
94    /// // Now "timout" in config object will be corrected to "timeout"
95    /// ```
96    pub fn with_nested_object(
97        mut self,
98        field: &'static str,
99        valid_fields: &'static [&'static str],
100    ) -> Self {
101        self.nested_objects.push((field, valid_fields));
102        self
103    }
104
105    /// Check if a tag value is valid
106    pub fn is_valid_tag(&self, tag: &str) -> bool {
107        self.valid_tags.contains(&tag)
108    }
109
110    /// Get valid fields for a tag value
111    pub fn get_fields(&self, tag: &str) -> Option<&'static [&'static str]> {
112        (self.fields_for_tag)(tag)
113    }
114
115    /// Get valid enum values for an array field
116    pub fn get_enum_array_values(&self, field: &str) -> Option<&'static [&'static str]> {
117        self.enum_arrays
118            .iter()
119            .find(|(f, _)| *f == field)
120            .map(|(_, v)| *v)
121    }
122
123    /// Get valid fields for a nested object
124    pub fn get_nested_object_fields(&self, field: &str) -> Option<&'static [&'static str]> {
125        self.nested_objects
126            .iter()
127            .find(|(f, _)| *f == field)
128            .map(|(_, v)| *v)
129    }
130}
131
132/// Schema for a simple object with known fields
133pub struct ObjectSchema {
134    /// Valid field names
135    pub valid_fields: &'static [&'static str],
136}
137
138impl ObjectSchema {
139    /// Create a new object schema
140    pub const fn new(valid_fields: &'static [&'static str]) -> Self {
141        Self { valid_fields }
142    }
143
144    /// Check if a field name is valid
145    pub fn is_valid_field(&self, field: &str) -> bool {
146        self.valid_fields.contains(&field)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_tagged_enum_schema() {
156        let schema =
157            TaggedEnumSchema::new("type", &["AddDerive", "RemoveDerive"], |tag| match tag {
158                "AddDerive" => Some(&["target", "derives"][..]),
159                "RemoveDerive" => Some(&["target", "derives"][..]),
160                _ => None,
161            });
162
163        assert!(schema.is_valid_tag("AddDerive"));
164        assert!(!schema.is_valid_tag("InvalidType"));
165        assert_eq!(
166            schema.get_fields("AddDerive"),
167            Some(&["target", "derives"][..])
168        );
169    }
170
171    #[test]
172    fn test_object_schema() {
173        let schema = ObjectSchema::new(&["name", "value", "is_pub"]);
174
175        assert!(schema.is_valid_field("name"));
176        assert!(!schema.is_valid_field("invalid"));
177    }
178}