spotify_cli/io/formatters/
chapter.rs

1//! Chapter formatting functions
2
3use serde_json::Value;
4
5use crate::io::common::{DurationFormat, format_duration_as};
6
7pub fn format_chapter_detail(payload: &Value) {
8    let name = payload
9        .get("name")
10        .and_then(|v| v.as_str())
11        .unwrap_or("Unknown");
12    let audiobook_name = payload
13        .get("audiobook")
14        .and_then(|a| a.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 chapter_number = payload.get("chapter_number").and_then(|v| v.as_u64());
26    let uri = payload.get("uri").and_then(|v| v.as_str()).unwrap_or("");
27
28    println!("{}", name);
29    println!("  Audiobook: {}", audiobook_name);
30    if let Some(num) = chapter_number {
31        println!("  Chapter: {}", num);
32    }
33    println!(
34        "  Duration: {}",
35        format_duration_as(duration_ms, DurationFormat::LongWithSeconds)
36    );
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    if !uri.is_empty() {
46        println!("  URI: {}", uri);
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use serde_json::json;
54
55    #[test]
56    fn format_chapter_detail_full() {
57        let payload = json!({
58            "name": "Chapter 1: The Beginning",
59            "audiobook": { "name": "Great Audiobook" },
60            "description": "In this chapter we explore the beginning",
61            "duration_ms": 1800000,
62            "chapter_number": 1,
63            "uri": "spotify:chapter:abc123"
64        });
65        format_chapter_detail(&payload);
66    }
67
68    #[test]
69    fn format_chapter_detail_minimal() {
70        let payload = json!({});
71        format_chapter_detail(&payload);
72    }
73
74    #[test]
75    fn format_chapter_detail_long_description() {
76        let long_desc = "A".repeat(300);
77        let payload = json!({
78            "name": "Chapter",
79            "audiobook": { "name": "Book" },
80            "description": long_desc,
81            "duration_ms": 600000
82        });
83        format_chapter_detail(&payload);
84    }
85
86    #[test]
87    fn format_chapter_detail_no_chapter_number() {
88        let payload = json!({
89            "name": "Prologue",
90            "audiobook": { "name": "Mystery Book" },
91            "duration_ms": 300000
92        });
93        format_chapter_detail(&payload);
94    }
95
96    #[test]
97    fn format_chapter_detail_short_duration() {
98        let payload = json!({
99            "name": "Short Chapter",
100            "duration_ms": 45000,
101            "chapter_number": 5
102        });
103        format_chapter_detail(&payload);
104    }
105
106    #[test]
107    fn format_chapter_detail_no_uri() {
108        let payload = json!({
109            "name": "Chapter Without URI",
110            "audiobook": { "name": "Book" },
111            "duration_ms": 900000
112        });
113        format_chapter_detail(&payload);
114    }
115}