Skip to main content

tanzim_validate/
encoding.rs

1use crate::error::{Error, ErrorKind};
2use crate::{Meta, Validator};
3use base64::Engine;
4use tanzim_value::{Value, ValueType};
5
6/// Borrow the inner string, or produce a `Type` error expecting a string.
7fn as_str(value: &mut Value) -> Result<&str, Error> {
8    match value {
9        Value::String(text) => Ok(text),
10        other => Err(Error::new(ErrorKind::Type {
11            expected: ValueType::String,
12            found: other.type_name(),
13        })),
14    }
15}
16
17/// (`encoding` feature) Accepts a standard (RFC 4648) base64-encoded string.
18#[derive(Debug, Clone, Default)]
19pub struct Base64 {
20    meta: Meta,
21}
22
23impl Base64 {
24    pub fn new() -> Self {
25        Self {
26            meta: Meta::default(),
27        }
28    }
29
30    /// Attach human-facing metadata (name, description, examples, default, output conversion).
31    pub fn with_meta(mut self, meta: Meta) -> Self {
32        self.meta = meta;
33        self
34    }
35}
36
37impl Validator for Base64 {
38    fn meta(&self) -> &Meta {
39        &self.meta
40    }
41
42    fn meta_mut(&mut self) -> &mut Meta {
43        &mut self.meta
44    }
45
46    fn check(&self, value: &mut Value) -> Result<(), Error> {
47        let text = as_str(value)?;
48        match base64::engine::general_purpose::STANDARD.decode(text) {
49            Ok(_) => Ok(()),
50            Err(_) => Err(Error::new(ErrorKind::Format { expected: "base64" })),
51        }
52    }
53}
54
55/// (`encoding` feature) Accepts a hexadecimal string (even number of `0-9a-fA-F` digits).
56#[derive(Debug, Clone, Default)]
57pub struct Hex {
58    meta: Meta,
59}
60
61impl Hex {
62    pub fn new() -> Self {
63        Self {
64            meta: Meta::default(),
65        }
66    }
67
68    /// Attach human-facing metadata (name, description, examples, default, output conversion).
69    pub fn with_meta(mut self, meta: Meta) -> Self {
70        self.meta = meta;
71        self
72    }
73}
74
75impl Validator for Hex {
76    fn meta(&self) -> &Meta {
77        &self.meta
78    }
79
80    fn meta_mut(&mut self) -> &mut Meta {
81        &mut self.meta
82    }
83
84    fn check(&self, value: &mut Value) -> Result<(), Error> {
85        let text = as_str(value)?;
86        let bytes = text.as_bytes();
87        if bytes.is_empty() || bytes.len() % 2 != 0 {
88            return Err(Error::new(ErrorKind::Format {
89                expected: "hexadecimal",
90            }));
91        }
92        for &byte in bytes {
93            if !byte.is_ascii_hexdigit() {
94                return Err(Error::new(ErrorKind::Format {
95                    expected: "hexadecimal",
96                }));
97            }
98        }
99        Ok(())
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn base64_roundtrip() {
109        assert!(
110            Base64::new()
111                .validate(&mut Value::String("aGVsbG8=".into()))
112                .is_ok()
113        );
114        assert!(
115            Base64::new()
116                .validate(&mut Value::String("not base64!".into()))
117                .is_err()
118        );
119    }
120
121    #[test]
122    fn hex_digits() {
123        assert!(
124            Hex::new()
125                .validate(&mut Value::String("deadBEEF".into()))
126                .is_ok()
127        );
128        assert!(
129            Hex::new()
130                .validate(&mut Value::String("xyz".into()))
131                .is_err()
132        );
133        assert!(
134            Hex::new()
135                .validate(&mut Value::String("abc".into()))
136                .is_err()
137        );
138    }
139}