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
37crate::impl_meta_methods!(Base64);
38
39impl Validator for Base64 {
40    fn meta(&self) -> &Meta {
41        &self.meta
42    }
43
44    fn meta_mut(&mut self) -> &mut Meta {
45        &mut self.meta
46    }
47
48    fn check(&self, value: &mut Value) -> Result<(), Error> {
49        let text = as_str(value)?;
50        match base64::engine::general_purpose::STANDARD.decode(text) {
51            Ok(_) => Ok(()),
52            Err(_) => Err(Error::new(ErrorKind::Format { expected: "base64" })),
53        }
54    }
55}
56
57/// (`encoding` feature) Accepts a hexadecimal string (even number of `0-9a-fA-F` digits).
58#[derive(Debug, Clone, Default)]
59pub struct Hex {
60    meta: Meta,
61}
62
63impl Hex {
64    pub fn new() -> Self {
65        Self {
66            meta: Meta::default(),
67        }
68    }
69
70    /// Attach human-facing metadata (name, description, examples, default, output conversion).
71    pub fn with_meta(mut self, meta: Meta) -> Self {
72        self.meta = meta;
73        self
74    }
75}
76
77crate::impl_meta_methods!(Hex);
78
79impl Validator for Hex {
80    fn meta(&self) -> &Meta {
81        &self.meta
82    }
83
84    fn meta_mut(&mut self) -> &mut Meta {
85        &mut self.meta
86    }
87
88    fn check(&self, value: &mut Value) -> Result<(), Error> {
89        let text = as_str(value)?;
90        let bytes = text.as_bytes();
91        if bytes.is_empty() || bytes.len() % 2 != 0 {
92            return Err(Error::new(ErrorKind::Format {
93                expected: "hexadecimal",
94            }));
95        }
96        for &byte in bytes {
97            if !byte.is_ascii_hexdigit() {
98                return Err(Error::new(ErrorKind::Format {
99                    expected: "hexadecimal",
100                }));
101            }
102        }
103        Ok(())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn base64_roundtrip() {
113        assert!(
114            Base64::new()
115                .validate(&mut Value::String("aGVsbG8=".into()))
116                .is_ok()
117        );
118        assert!(
119            Base64::new()
120                .validate(&mut Value::String("not base64!".into()))
121                .is_err()
122        );
123    }
124
125    #[test]
126    fn hex_digits() {
127        assert!(
128            Hex::new()
129                .validate(&mut Value::String("deadBEEF".into()))
130                .is_ok()
131        );
132        assert!(
133            Hex::new()
134                .validate(&mut Value::String("xyz".into()))
135                .is_err()
136        );
137        assert!(
138            Hex::new()
139                .validate(&mut Value::String("abc".into()))
140                .is_err()
141        );
142    }
143}