nomoreide_remote_protocol/
terminal_bytes.rs1use base64::engine::general_purpose::STANDARD;
19use base64::Engine as _;
20use serde::de::Error as _;
21use serde::{Deserialize, Deserializer, Serialize, Serializer};
22
23#[derive(Debug, Clone, Default, PartialEq, Eq)]
25pub struct TerminalBytes(pub Vec<u8>);
26
27impl TerminalBytes {
28 pub fn new(data: impl Into<Vec<u8>>) -> Self {
29 Self(data.into())
30 }
31
32 pub fn as_slice(&self) -> &[u8] {
33 &self.0
34 }
35
36 pub fn len(&self) -> usize {
37 self.0.len()
38 }
39
40 pub fn is_empty(&self) -> bool {
41 self.0.is_empty()
42 }
43}
44
45impl Serialize for TerminalBytes {
46 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
47 serializer.serialize_str(&STANDARD.encode(&self.0))
48 }
49}
50
51impl<'de> Deserialize<'de> for TerminalBytes {
52 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
53 let encoded = String::deserialize(deserializer)?;
54 STANDARD
55 .decode(encoded.as_bytes())
56 .map(Self)
57 .map_err(|error| D::Error::custom(format!("terminal data is not base64: {error}")))
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
68 fn invalid_utf8_survives_the_round_trip() {
69 let raw = vec![0x1b, b'[', b'2', b'J', 0xff, 0xfe, 0x00, b'x'];
70 let json = serde_json::to_string(&TerminalBytes::new(raw.clone())).unwrap();
71 let back: TerminalBytes = serde_json::from_str(&json).unwrap();
72
73 assert_eq!(back.as_slice(), raw.as_slice());
74 assert_ne!(
75 String::from_utf8_lossy(&raw).as_bytes(),
76 raw.as_slice(),
77 "the bytes chosen must actually be the case a lossy decode would ruin"
78 );
79 }
80
81 #[test]
82 fn it_is_a_json_string_not_an_array_of_numbers() {
83 let json = serde_json::to_string(&TerminalBytes::new(b"hi".to_vec())).unwrap();
84 assert_eq!(json, "\"aGk=\"");
85 }
86
87 #[test]
88 fn anything_that_is_not_base64_is_refused_at_the_boundary() {
89 assert!(serde_json::from_str::<TerminalBytes>("\"not base64!!\"").is_err());
90 assert!(serde_json::from_str::<TerminalBytes>("[104,105]").is_err());
91 }
92}