kittycad_modeling_cmds/
base64.rs1use std::{convert::TryFrom, fmt};
6
7use serde::{
8 de::{Error, Unexpected, Visitor},
9 Deserialize, Deserializer, Serialize, Serializer,
10};
11
12static ALLOWED_DECODING_FORMATS: &[data_encoding::Encoding] = &[
13 data_encoding::BASE64,
14 data_encoding::BASE64URL,
15 data_encoding::BASE64URL_NOPAD,
16 data_encoding::BASE64_MIME,
17 data_encoding::BASE64_NOPAD,
18];
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(
22 feature = "python",
23 pyo3::pyclass(from_py_object),
24 pyo3_stub_gen::derive::gen_stub_pyclass
25)]
26pub struct Base64Data(pub Vec<u8>);
29
30impl Base64Data {
31 pub fn is_empty(&self) -> bool {
33 self.0.is_empty()
34 }
35}
36
37impl fmt::Display for Base64Data {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "{}", data_encoding::BASE64URL_NOPAD.encode(&self.0))
40 }
41}
42
43impl From<Base64Data> for Vec<u8> {
44 fn from(data: Base64Data) -> Vec<u8> {
45 data.0
46 }
47}
48
49impl From<Vec<u8>> for Base64Data {
50 fn from(data: Vec<u8>) -> Base64Data {
51 Base64Data(data)
52 }
53}
54
55impl AsRef<[u8]> for Base64Data {
56 fn as_ref(&self) -> &[u8] {
57 &self.0
58 }
59}
60
61#[derive(Default, Debug)]
63pub struct CouldNotDecodeBase64Data;
64
65impl std::fmt::Display for CouldNotDecodeBase64Data {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 write!(f, "Could not decode base64 data")
68 }
69}
70
71impl std::error::Error for CouldNotDecodeBase64Data {}
72
73impl TryFrom<&str> for Base64Data {
74 type Error = CouldNotDecodeBase64Data;
75
76 fn try_from(v: &str) -> Result<Self, Self::Error> {
77 for config in ALLOWED_DECODING_FORMATS {
78 if let Ok(data) = config.decode(v.as_bytes()) {
79 return Ok(Base64Data(data));
80 }
81 }
82
83 Err(CouldNotDecodeBase64Data)
84 }
85}
86
87struct Base64DataVisitor;
88
89impl Visitor<'_> for Base64DataVisitor {
90 type Value = Base64Data;
91
92 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
93 write!(formatter, "a base64 encoded string")
94 }
95
96 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
97 where
98 E: Error,
99 {
100 for config in ALLOWED_DECODING_FORMATS {
102 if let Ok(data) = config.decode(v.as_bytes()) {
103 return Ok(Base64Data(data));
104 }
105 }
106
107 Err(serde::de::Error::invalid_value(Unexpected::Str(v), &self))
108 }
109}
110
111impl<'de> Deserialize<'de> for Base64Data {
112 fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
113 where
114 D: Deserializer<'de>,
115 {
116 deserializer.deserialize_str(Base64DataVisitor)
117 }
118}
119
120impl Serialize for Base64Data {
121 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122 where
123 S: Serializer,
124 {
125 let encoded = data_encoding::BASE64URL_NOPAD.encode(&self.0);
126 serializer.serialize_str(&encoded)
127 }
128}
129
130impl schemars::JsonSchema for Base64Data {
131 fn schema_name() -> String {
132 "Base64Data".to_string()
133 }
134
135 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
136 let mut obj = gen.root_schema_for::<String>().schema;
137 obj.format = Some("byte".to_string());
139 schemars::schema::Schema::Object(obj)
140 }
141
142 fn is_referenceable() -> bool {
143 false
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use std::convert::TryFrom;
150
151 use crate::base64::Base64Data;
152
153 #[test]
154 fn test_base64_try_from() {
155 assert!(Base64Data::try_from("aGVsbG8=").is_ok());
156 assert!(Base64Data::try_from("abcdefghij").is_err());
157 }
158}