1#[derive(Debug, Clone, PartialEq, Eq, Default)]
9pub struct Bytes(pub Vec<u8>);
10
11impl From<Vec<u8>> for Bytes {
12 fn from(v: Vec<u8>) -> Self {
13 Bytes(v)
14 }
15}
16
17impl From<Bytes> for Vec<u8> {
18 fn from(b: Bytes) -> Self {
19 b.0
20 }
21}
22
23impl AsRef<[u8]> for Bytes {
24 fn as_ref(&self) -> &[u8] {
25 &self.0
26 }
27}
28
29impl serde::Serialize for Bytes {
30 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
31 serializer.serialize_bytes(&self.0)
32 }
33}
34
35impl<'de> serde::Deserialize<'de> for Bytes {
36 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
37 struct BytesVisitor;
38
39 impl serde::de::Visitor<'_> for BytesVisitor {
40 type Value = Bytes;
41
42 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43 f.write_str("a CBOR byte string (a {\"$bytes\":…} envelope on JSON)")
44 }
45
46 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Bytes, E> {
47 Ok(Bytes(v.to_vec()))
48 }
49
50 fn visit_byte_buf<E: serde::de::Error>(self, v: Vec<u8>) -> Result<Bytes, E> {
51 Ok(Bytes(v))
52 }
53 }
54
55 deserializer.deserialize_any(BytesVisitor)
58 }
59}
60
61impl schemars::JsonSchema for Bytes {
62 fn schema_name() -> std::borrow::Cow<'static, str> {
63 "Bytes".into()
64 }
65
66 fn inline_schema() -> bool {
69 true
70 }
71
72 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
73 schemars::json_schema!({
75 "type": "object",
76 "description": "Binary value as a base64 byte-string envelope.",
77 "properties": {
78 "$bytes": { "type": "string", "contentEncoding": "base64" }
79 },
80 "required": ["$bytes"],
81 "additionalProperties": false
82 })
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 fn cbor_of(v: &ciborium::value::Value) -> Vec<u8> {
91 let mut buf = Vec::new();
92 ciborium::into_writer(v, &mut buf).unwrap();
93 buf
94 }
95
96 #[test]
97 fn serializes_to_cbor_byte_string() {
98 let mut buf = Vec::new();
99 ciborium::into_writer(&Bytes(b"hello".to_vec()), &mut buf).unwrap();
100 let value: ciborium::value::Value = ciborium::from_reader(&buf[..]).unwrap();
101 assert_eq!(value, ciborium::value::Value::Bytes(b"hello".to_vec()));
102 }
103
104 #[test]
105 fn deserializes_from_cbor_byte_string() {
106 let cbor = cbor_of(&ciborium::value::Value::Bytes(b"hi".to_vec()));
107 let b: Bytes = ciborium::from_reader(&cbor[..]).unwrap();
108 assert_eq!(b.0, b"hi");
109 }
110
111 #[test]
112 fn rejects_text_string() {
113 let cbor = cbor_of(&ciborium::value::Value::Text("aGVsbG8=".into()));
115 let r: Result<Bytes, _> = ciborium::from_reader(&cbor[..]);
116 assert!(r.is_err());
117 }
118
119 #[test]
120 fn schema_is_bytes_envelope() {
121 let schema = schemars::schema_for!(Bytes);
122 let v = serde_json::to_value(&schema).unwrap();
123 assert_eq!(v.get("type").and_then(|x| x.as_str()), Some("object"));
124 assert_eq!(
125 v["properties"]["$bytes"]["contentEncoding"].as_str(),
126 Some("base64")
127 );
128 assert_eq!(v["required"][0].as_str(), Some("$bytes"));
129 }
130
131 #[derive(serde::Deserialize, schemars::JsonSchema)]
132 struct Params {
133 data: Bytes,
134 }
135
136 #[test]
137 fn bytes_field_composes_with_derive() {
138 let schema = schemars::schema_for!(Params);
139 let v = serde_json::to_value(&schema).unwrap();
140 assert_eq!(
142 v["properties"]["data"]["properties"]["$bytes"]["contentEncoding"],
143 "base64"
144 );
145
146 let cbor = cbor_of(&ciborium::value::Value::Map(vec![(
148 ciborium::value::Value::Text("data".into()),
149 ciborium::value::Value::Bytes(b"hi".to_vec()),
150 )]));
151 let p: Params = ciborium::from_reader(&cbor[..]).unwrap();
152 assert_eq!(p.data.0, b"hi");
153 }
154}