1use std::{borrow::Cow, fmt};
11
12use base64_codec::codec::Base64Codec;
13use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
14use serde::{Serialize, Serializer, ser::SerializeStruct};
15
16#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct Bytes {
20 pub data: Vec<u8>,
22 pub mime: String,
25}
26
27impl fmt::Display for Bytes {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 writeln!(f, "{} bytes of {}", self.data.len(), self.mime)
30 }
31}
32
33impl Serialize for Bytes {
34 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
35 let mut data = Vec::new();
36 Base64Codec::default().encode_vec(&self.data, &mut data);
37 let data = String::from_utf8(data).unwrap_or_default();
39
40 let mut bytes = serializer.serialize_struct("Bytes", 3)?;
41 bytes.serialize_field("data", &data)?;
42 bytes.serialize_field("size", &self.data.len())?;
43 bytes.serialize_field("mime", &self.mime)?;
44 bytes.end()
45 }
46}
47
48impl JsonSchema for Bytes {
49 fn schema_name() -> Cow<'static, str> {
50 "Bytes".into()
51 }
52
53 fn json_schema(_: &mut SchemaGenerator) -> Schema {
54 json_schema!({
55 "description": "Binary content.",
56 "type": "object",
57 "properties": {
58 "data": {
59 "description": "The content, in base64.",
60 "type": "string",
61 "contentEncoding": "base64",
62 },
63 "size": {
64 "description": "The size of the content, in bytes.",
65 "type": "integer",
66 "format": "uint64",
67 "minimum": 0,
68 },
69 "mime": {
70 "description": "The media type of the content.",
71 "type": "string",
72 },
73 },
74 "required": ["data", "size", "mime"],
75 })
76 }
77}
78
79#[derive(Serialize, JsonSchema)]
85#[serde(rename_all = "camelCase")]
86pub struct Paginated<T> {
87 #[serde(flatten)]
89 pub inner: T,
90 pub next_page: Option<String>,
92}
93
94impl<T: fmt::Display> fmt::Display for Paginated<T> {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(f, "{}", self.inner)?;
97
98 if let Some(cursor) = &self.next_page {
99 writeln!(f, "Next page: {cursor}")?;
100 }
101
102 Ok(())
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use serde_json::json;
109
110 use crate::{clap::commands::output_schema, output::*};
111
112 #[derive(Serialize, JsonSchema)]
113 struct Rows {
114 rows: Vec<u8>,
115 }
116
117 #[test]
118 fn bytes_print_as_base64_with_their_size() {
119 let bytes = Bytes {
120 data: b"\xff\x00a".to_vec(),
121 mime: "application/octet-stream".into(),
122 };
123
124 assert_eq!(
125 serde_json::to_value(bytes).unwrap(),
126 json!({ "data": "/wBh", "size": 3, "mime": "application/octet-stream" })
127 );
128 }
129
130 #[test]
131 fn a_complete_listing_prints_a_null_cursor() {
132 let page = Paginated {
133 inner: Rows { rows: vec![1] },
134 next_page: None,
135 };
136
137 assert_eq!(
138 serde_json::to_value(page).unwrap(),
139 json!({ "rows": [1], "nextPage": null })
140 );
141 }
142
143 #[test]
144 fn the_cursor_is_required_in_the_schema() {
145 let schema = output_schema::<Paginated<Rows>>();
146 let required = schema["required"].as_array().unwrap();
147
148 assert!(required.contains(&json!("nextPage")), "{schema}");
149 assert!(required.contains(&json!("rows")), "{schema}");
150 }
151}