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