rust_yaml/
resolver.rs

1//! YAML resolver for tag resolution and implicit typing
2
3use std::collections::HashMap;
4
5/// Trait for YAML resolvers that handle tag resolution
6pub trait Resolver {
7    /// Resolve a tag for implicit typing
8    fn resolve_tag(&self, value: &str, implicit: bool) -> Option<String>;
9
10    /// Add an implicit resolver pattern
11    fn add_implicit_resolver(&mut self, tag: String, pattern: String);
12
13    /// Reset the resolver state
14    fn reset(&mut self);
15}
16
17/// Basic resolver with standard YAML 1.2 implicit typing
18#[derive(Debug)]
19pub struct BasicResolver {
20    implicit_resolvers: HashMap<String, String>,
21}
22
23impl BasicResolver {
24    /// Create a new resolver with standard YAML 1.2 resolvers
25    pub fn new() -> Self {
26        let mut resolver = Self {
27            implicit_resolvers: HashMap::new(),
28        };
29
30        // Add standard YAML 1.2 implicit resolvers
31        resolver.add_standard_resolvers();
32        resolver
33    }
34
35    fn add_standard_resolvers(&mut self) {
36        // Boolean values
37        self.implicit_resolvers
38            .insert("true".to_string(), "tag:yaml.org,2002:bool".to_string());
39        self.implicit_resolvers
40            .insert("True".to_string(), "tag:yaml.org,2002:bool".to_string());
41        self.implicit_resolvers
42            .insert("TRUE".to_string(), "tag:yaml.org,2002:bool".to_string());
43        self.implicit_resolvers
44            .insert("false".to_string(), "tag:yaml.org,2002:bool".to_string());
45        self.implicit_resolvers
46            .insert("False".to_string(), "tag:yaml.org,2002:bool".to_string());
47        self.implicit_resolvers
48            .insert("FALSE".to_string(), "tag:yaml.org,2002:bool".to_string());
49
50        // Null values
51        self.implicit_resolvers
52            .insert("null".to_string(), "tag:yaml.org,2002:null".to_string());
53        self.implicit_resolvers
54            .insert("Null".to_string(), "tag:yaml.org,2002:null".to_string());
55        self.implicit_resolvers
56            .insert("NULL".to_string(), "tag:yaml.org,2002:null".to_string());
57        self.implicit_resolvers
58            .insert("~".to_string(), "tag:yaml.org,2002:null".to_string());
59    }
60
61    /// Check if a string represents an integer
62    pub fn is_int(&self, value: &str) -> bool {
63        value.parse::<i64>().is_ok()
64    }
65
66    /// Check if a string represents a float
67    pub fn is_float(&self, value: &str) -> bool {
68        value.parse::<f64>().is_ok()
69    }
70}
71
72impl Default for BasicResolver {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl Resolver for BasicResolver {
79    fn resolve_tag(&self, value: &str, implicit: bool) -> Option<String> {
80        if !implicit {
81            return None;
82        }
83
84        // Check explicit mappings first
85        if let Some(tag) = self.implicit_resolvers.get(value) {
86            return Some(tag.clone());
87        }
88
89        // Check numeric types
90        if self.is_int(value) {
91            return Some("tag:yaml.org,2002:int".to_string());
92        }
93
94        if self.is_float(value) {
95            return Some("tag:yaml.org,2002:float".to_string());
96        }
97
98        // Default to string
99        Some("tag:yaml.org,2002:str".to_string())
100    }
101
102    fn add_implicit_resolver(&mut self, tag: String, pattern: String) {
103        self.implicit_resolvers.insert(pattern, tag);
104    }
105
106    fn reset(&mut self) {
107        // Keep the standard resolvers, don't clear them
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_resolver_creation() {
117        let resolver = BasicResolver::new();
118        assert!(!resolver.implicit_resolvers.is_empty());
119    }
120
121    #[test]
122    fn test_boolean_resolution() {
123        let resolver = BasicResolver::new();
124
125        assert_eq!(
126            resolver.resolve_tag("true", true),
127            Some("tag:yaml.org,2002:bool".to_string())
128        );
129        assert_eq!(
130            resolver.resolve_tag("false", true),
131            Some("tag:yaml.org,2002:bool".to_string())
132        );
133    }
134
135    #[test]
136    fn test_null_resolution() {
137        let resolver = BasicResolver::new();
138
139        assert_eq!(
140            resolver.resolve_tag("null", true),
141            Some("tag:yaml.org,2002:null".to_string())
142        );
143        assert_eq!(
144            resolver.resolve_tag("~", true),
145            Some("tag:yaml.org,2002:null".to_string())
146        );
147    }
148
149    #[test]
150    fn test_numeric_resolution() {
151        let resolver = BasicResolver::new();
152
153        assert_eq!(
154            resolver.resolve_tag("42", true),
155            Some("tag:yaml.org,2002:int".to_string())
156        );
157        assert_eq!(
158            resolver.resolve_tag("3.14", true),
159            Some("tag:yaml.org,2002:float".to_string())
160        );
161    }
162
163    #[test]
164    fn test_string_resolution() {
165        let resolver = BasicResolver::new();
166
167        assert_eq!(
168            resolver.resolve_tag("hello", true),
169            Some("tag:yaml.org,2002:str".to_string())
170        );
171    }
172
173    #[test]
174    fn test_explicit_tag_resolution() {
175        let resolver = BasicResolver::new();
176
177        // When not implicit, should return None
178        assert_eq!(resolver.resolve_tag("true", false), None);
179    }
180
181    #[test]
182    fn test_custom_resolver() {
183        let mut resolver = BasicResolver::new();
184
185        resolver.add_implicit_resolver(
186            "tag:example.com,2002:custom".to_string(),
187            "CUSTOM".to_string(),
188        );
189
190        assert_eq!(
191            resolver.resolve_tag("CUSTOM", true),
192            Some("tag:example.com,2002:custom".to_string())
193        );
194    }
195}