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