Skip to main content

tanzim_parse/
closure.rs

1//! Custom parser backed by a closure.
2//!
3//! # Example
4//!
5//! ```
6//! use tanzim_parse::{closure::Closure, Deserialize};
7//! use tanzim_value::{LocatedValue, Location, Value};
8//!
9//! let parser = Closure::new(
10//!     "upper",
11//!     "txt",
12//!     Box::new(|source, resource, bytes| {
13//!         Ok(LocatedValue {
14//!             value: Value::String(String::from_utf8_lossy(bytes).to_uppercase()),
15//!             location: Location::at(source, resource, None, None, None),
16//!         })
17//!     }),
18//! );
19//! let value = parser.parse("file", "test.txt", b"hello").unwrap();
20//! assert_eq!(value.value.as_string().unwrap(), "HELLO");
21//! ```
22
23use crate::Deserialize;
24use tanzim_value::{Error, LocatedValue};
25
26pub type BoxedParseFn = Box<dyn Fn(&str, &str, &[u8]) -> Result<LocatedValue, Error>>;
27pub type BoxedValidatorFn = Box<dyn Fn(&[u8]) -> Option<bool>>;
28
29pub struct Closure {
30    name: String,
31    parser: BoxedParseFn,
32    validator: BoxedValidatorFn,
33    supported_format_list: Vec<String>,
34}
35
36impl Closure {
37    pub fn new<N: AsRef<str>, F: AsRef<str>>(
38        name: N,
39        supported_format: F,
40        parser: BoxedParseFn,
41    ) -> Self {
42        Self {
43            name: name.as_ref().to_string(),
44            parser,
45            validator: Box::new(|_| None),
46            supported_format_list: vec![supported_format.as_ref().to_string()],
47        }
48    }
49
50    pub fn with_validator(mut self, validator: BoxedValidatorFn) -> Self {
51        self.validator = validator;
52        self
53    }
54
55    pub fn with_format_list<N: AsRef<str>>(mut self, format_list: &[N]) -> Self {
56        let mut formats = Vec::new();
57        for format in format_list {
58            formats.push(format.as_ref().to_string());
59        }
60        self.supported_format_list = formats;
61        self
62    }
63}
64
65impl Deserialize for Closure {
66    fn name(&self) -> &str {
67        self.name.as_str()
68    }
69
70    fn supported_format_list(&self) -> Vec<String> {
71        self.supported_format_list.clone()
72    }
73
74    fn parse(&self, source: &str, resource: &str, bytes: &[u8]) -> Result<LocatedValue, Error> {
75        (self.parser)(source, resource, bytes)
76    }
77
78    fn is_format_supported(&self, bytes: &[u8]) -> Option<bool> {
79        (self.validator)(bytes)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use tanzim_value::{Location, Value};
87
88    #[test]
89    fn closure_parser_delegates_to_function() {
90        let parser = Closure::new(
91            "upper",
92            "txt",
93            Box::new(|source, resource, bytes| {
94                Ok(LocatedValue {
95                    value: Value::String(String::from_utf8_lossy(bytes).to_uppercase()),
96                    location: Location::at(source, resource, None, None, None),
97                })
98            }),
99        )
100        .with_validator(Box::new(|bytes| Some(!bytes.is_empty())));
101        let parsed = parser.parse("file", "test.txt", b"hello").unwrap();
102        assert_eq!(parsed.value.as_string().unwrap(), "HELLO");
103        assert_eq!(parser.is_format_supported(b"x"), Some(true));
104        assert_eq!(parser.is_format_supported(b""), Some(false));
105    }
106}