spotify_cli/io/formatters/
episode.rs1use serde_json::Value;
4
5use crate::io::common::{DurationFormat, format_duration_as, print_table, truncate};
6
7pub fn format_episode_detail(payload: &Value) {
8 let name = payload
9 .get("name")
10 .and_then(|v| v.as_str())
11 .unwrap_or("Unknown");
12 let show_name = payload
13 .get("show")
14 .and_then(|s| s.get("name"))
15 .and_then(|v| v.as_str())
16 .unwrap_or("Unknown");
17 let description = payload
18 .get("description")
19 .and_then(|v| v.as_str())
20 .unwrap_or("");
21 let duration_ms = payload
22 .get("duration_ms")
23 .and_then(|v| v.as_u64())
24 .unwrap_or(0);
25 let release_date = payload
26 .get("release_date")
27 .and_then(|v| v.as_str())
28 .unwrap_or("");
29 let explicit = payload
30 .get("explicit")
31 .and_then(|v| v.as_bool())
32 .unwrap_or(false);
33 let uri = payload.get("uri").and_then(|v| v.as_str()).unwrap_or("");
34
35 println!("{}", name);
36 println!(" Show: {}", show_name);
37 if !description.is_empty() {
38 let desc = if description.len() > 200 {
39 format!("{}...", &description[..200])
40 } else {
41 description.to_string()
42 };
43 println!(" Description: {}", desc);
44 }
45 println!(
46 " Duration: {}",
47 format_duration_as(duration_ms, DurationFormat::Long)
48 );
49 if !release_date.is_empty() {
50 println!(" Released: {}", release_date);
51 }
52 if explicit {
53 println!(" Explicit: Yes");
54 }
55 if !uri.is_empty() {
56 println!(" URI: {}", uri);
57 }
58}
59
60pub fn format_episodes(items: &[Value], message: &str) {
61 println!("{}:", message);
62 println!();
63
64 let valid_items: Vec<_> = items
66 .iter()
67 .filter(|item| {
68 let episode = item.get("episode").unwrap_or(*item);
69 episode.get("id").and_then(|v| v.as_str()).is_some()
71 })
72 .collect();
73
74 let rows: Vec<Vec<String>> = valid_items
75 .iter()
76 .enumerate()
77 .map(|(i, item)| {
78 let episode = item.get("episode").unwrap_or(*item);
80 let name = episode
81 .get("name")
82 .and_then(|v| v.as_str())
83 .unwrap_or("Unknown");
84 let show = episode
85 .get("show")
86 .and_then(|s| s.get("name"))
87 .and_then(|v| v.as_str())
88 .unwrap_or("Unknown");
89 let duration_ms = episode
90 .get("duration_ms")
91 .and_then(|v| v.as_u64())
92 .unwrap_or(0);
93 vec![
94 (i + 1).to_string(),
95 truncate(name, 30),
96 truncate(show, 20),
97 format_duration_as(duration_ms, DurationFormat::Long),
98 ]
99 })
100 .collect();
101
102 print_table(
103 "Episodes",
104 &["#", "Name", "Show", "Duration"],
105 &rows,
106 &[3, 30, 20, 10],
107 );
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use serde_json::json;
114
115 #[test]
116 fn format_episode_detail_full() {
117 let payload = json!({
118 "name": "Episode Title",
119 "show": { "name": "Podcast Name" },
120 "description": "A detailed description of this episode",
121 "duration_ms": 3600000,
122 "release_date": "2024-01-15",
123 "explicit": true,
124 "uri": "spotify:episode:abc123"
125 });
126 format_episode_detail(&payload);
127 }
128
129 #[test]
130 fn format_episode_detail_minimal() {
131 let payload = json!({});
132 format_episode_detail(&payload);
133 }
134
135 #[test]
136 fn format_episode_detail_long_description() {
137 let long_desc = "A".repeat(300);
138 let payload = json!({
139 "name": "Episode",
140 "show": { "name": "Show" },
141 "description": long_desc,
142 "duration_ms": 1800000
143 });
144 format_episode_detail(&payload);
145 }
146
147 #[test]
148 fn format_episode_detail_not_explicit() {
149 let payload = json!({
150 "name": "Family Episode",
151 "show": { "name": "Family Show" },
152 "explicit": false,
153 "duration_ms": 900000
154 });
155 format_episode_detail(&payload);
156 }
157
158 #[test]
159 fn format_episodes_with_items() {
160 let items = vec![
161 json!({
162 "name": "Episode One",
163 "show": { "name": "Podcast A" },
164 "duration_ms": 3600000
165 }),
166 json!({
167 "name": "Episode Two",
168 "show": { "name": "Podcast B" },
169 "duration_ms": 1800000
170 }),
171 ];
172 format_episodes(&items, "Recent Episodes");
173 }
174
175 #[test]
176 fn format_episodes_empty() {
177 let items: Vec<Value> = vec![];
178 format_episodes(&items, "No Episodes");
179 }
180
181 #[test]
182 fn format_episodes_wrapped() {
183 let items = vec![json!({
184 "episode": {
185 "name": "Wrapped Episode",
186 "show": { "name": "Show" },
187 "duration_ms": 2700000
188 }
189 })];
190 format_episodes(&items, "Saved Episodes");
191 }
192
193 #[test]
194 fn format_episodes_minimal() {
195 let items = vec![json!({})];
196 format_episodes(&items, "Episodes");
197 }
198}