1use std::{borrow::Cow, fmt};
11
12use base64::{Engine, engine::general_purpose::STANDARD};
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 bytes = serializer.serialize_struct("Bytes", 3)?;
36 bytes.serialize_field("data", &STANDARD.encode(&self.data))?;
37 bytes.serialize_field("size", &self.data.len())?;
38 bytes.serialize_field("mime", &self.mime)?;
39 bytes.end()
40 }
41}
42
43impl JsonSchema for Bytes {
44 fn schema_name() -> Cow<'static, str> {
45 "Bytes".into()
46 }
47
48 fn json_schema(_: &mut SchemaGenerator) -> Schema {
49 json_schema!({
50 "description": "Binary content.",
51 "type": "object",
52 "properties": {
53 "data": {
54 "description": "The content, in base64.",
55 "type": "string",
56 "contentEncoding": "base64",
57 },
58 "size": {
59 "description": "The size of the content, in bytes.",
60 "type": "integer",
61 "format": "uint64",
62 "minimum": 0,
63 },
64 "mime": {
65 "description": "The media type of the content.",
66 "type": "string",
67 },
68 },
69 "required": ["data", "size", "mime"],
70 })
71 }
72}
73
74#[derive(Serialize, JsonSchema)]
80#[serde(rename_all = "camelCase")]
81pub struct Paginated<T> {
82 #[serde(flatten)]
84 pub inner: T,
85 pub next_page: Option<String>,
87}
88
89impl<T: fmt::Display> fmt::Display for Paginated<T> {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(f, "{}", self.inner)?;
92
93 if let Some(cursor) = &self.next_page {
94 writeln!(f, "Next page: {cursor}")?;
95 }
96
97 Ok(())
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use serde_json::json;
104
105 use crate::{clap::commands::output_schema, output::*};
106
107 #[derive(Serialize, JsonSchema)]
108 struct Rows {
109 rows: Vec<u8>,
110 }
111
112 #[test]
113 fn bytes_print_as_base64_with_their_size() {
114 let bytes = Bytes {
115 data: b"\xff\x00a".to_vec(),
116 mime: "application/octet-stream".into(),
117 };
118
119 assert_eq!(
120 serde_json::to_value(bytes).unwrap(),
121 json!({ "data": "/wBh", "size": 3, "mime": "application/octet-stream" })
122 );
123 }
124
125 #[test]
126 fn a_complete_listing_prints_a_null_cursor() {
127 let page = Paginated {
128 inner: Rows { rows: vec![1] },
129 next_page: None,
130 };
131
132 assert_eq!(
133 serde_json::to_value(page).unwrap(),
134 json!({ "rows": [1], "nextPage": null })
135 );
136 }
137
138 #[test]
139 fn the_cursor_is_required_in_the_schema() {
140 let schema = output_schema::<Paginated<Rows>>();
141 let required = schema["required"].as_array().unwrap();
142
143 assert!(required.contains(&json!("nextPage")), "{schema}");
144 assert!(required.contains(&json!("rows")), "{schema}");
145 }
146}