spotify_cli/io/registry/
search.rs1use serde_json::Value;
4
5use super::PayloadFormatter;
6use crate::io::formatters;
7use crate::io::output::PayloadKind;
8
9pub struct CombinedSearchFormatter;
10
11impl PayloadFormatter for CombinedSearchFormatter {
12 fn name(&self) -> &'static str {
13 "combined_search"
14 }
15
16 fn supported_kinds(&self) -> &'static [PayloadKind] {
17 &[PayloadKind::CombinedSearch]
18 }
19
20 fn matches(&self, payload: &Value) -> bool {
21 payload.get("spotify").is_some()
22 }
23
24 fn format(&self, payload: &Value, _message: &str) {
25 formatters::format_search_results(payload);
26 }
27}
28
29pub struct SpotifySearchFormatter;
30
31impl PayloadFormatter for SpotifySearchFormatter {
32 fn name(&self) -> &'static str {
33 "spotify_search"
34 }
35
36 fn supported_kinds(&self) -> &'static [PayloadKind] {
37 &[PayloadKind::SearchResults]
38 }
39
40 fn matches(&self, payload: &Value) -> bool {
41 let is_playlist = payload.get("owner").is_some();
42 let is_album = payload.get("album_type").is_some();
43 !is_playlist
44 && !is_album
45 && ((payload.get("tracks").is_some() && payload["tracks"].get("items").is_some())
46 || (payload.get("albums").is_some() && payload["albums"].get("items").is_some())
47 || (payload.get("artists").is_some() && payload["artists"].get("items").is_some())
48 || (payload.get("playlists").is_some()
49 && payload["playlists"].get("items").is_some()))
50 }
51
52 fn format(&self, payload: &Value, _message: &str) {
53 formatters::format_search_results(payload);
54 }
55}
56
57pub struct PinsFormatter;
58
59impl PayloadFormatter for PinsFormatter {
60 fn name(&self) -> &'static str {
61 "pins"
62 }
63
64 fn supported_kinds(&self) -> &'static [PayloadKind] {
65 &[PayloadKind::Pins]
66 }
67
68 fn matches(&self, payload: &Value) -> bool {
69 payload.get("pins").is_some() && payload.get("spotify").is_none()
70 }
71
72 fn format(&self, payload: &Value, _message: &str) {
73 if let Some(pins) = payload.get("pins").and_then(|p| p.as_array()) {
74 formatters::format_pins(pins);
75 }
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use serde_json::json;
83
84 #[test]
85 fn combined_search_formatter_matches() {
86 let formatter = CombinedSearchFormatter;
87 let payload = json!({ "spotify": { "tracks": {} } });
88 assert!(formatter.matches(&payload));
89 assert!(!formatter.matches(&json!({})));
90 }
91
92 #[test]
93 fn pins_formatter_matches() {
94 let formatter = PinsFormatter;
95 let payload = json!({ "pins": [] });
96 assert!(formatter.matches(&payload));
97 let payload_with_spotify = json!({ "pins": [], "spotify": {} });
98 assert!(!formatter.matches(&payload_with_spotify));
99 }
100
101 #[test]
102 fn spotify_search_formatter_matches() {
103 let formatter = SpotifySearchFormatter;
104 let tracks = json!({ "tracks": { "items": [] } });
105 assert!(formatter.matches(&tracks));
106 let albums = json!({ "albums": { "items": [] } });
107 assert!(formatter.matches(&albums));
108 let artists = json!({ "artists": { "items": [] } });
109 assert!(formatter.matches(&artists));
110 let playlists = json!({ "playlists": { "items": [] } });
111 assert!(formatter.matches(&playlists));
112 let playlist = json!({ "owner": {}, "tracks": { "items": [] } });
114 assert!(!formatter.matches(&playlist));
115 let album = json!({ "album_type": "album", "tracks": { "items": [] } });
116 assert!(!formatter.matches(&album));
117 }
118
119 #[test]
120 fn formatter_names() {
121 assert_eq!(CombinedSearchFormatter.name(), "combined_search");
122 assert_eq!(SpotifySearchFormatter.name(), "spotify_search");
123 assert_eq!(PinsFormatter.name(), "pins");
124 }
125
126 #[test]
127 fn combined_search_format_runs() {
128 let formatter = CombinedSearchFormatter;
129 let payload = json!({
130 "spotify": {
131 "tracks": { "items": [] },
132 "artists": { "items": [] }
133 },
134 "pins": []
135 });
136 formatter.format(&payload, "Search Results");
137 }
138
139 #[test]
140 fn spotify_search_format_runs() {
141 let formatter = SpotifySearchFormatter;
142 let payload = json!({
143 "tracks": {
144 "items": [{"name": "Track", "artists": [{"name": "Artist"}]}]
145 }
146 });
147 formatter.format(&payload, "Tracks");
148 }
149
150 #[test]
151 fn pins_format_runs() {
152 let formatter = PinsFormatter;
153 let payload = json!({
154 "pins": [{
155 "alias": "my-song",
156 "resource_type": "track",
157 "id": "abc123"
158 }]
159 });
160 formatter.format(&payload, "Pins");
161 }
162
163 #[test]
164 fn combined_search_supports_kind() {
165 let formatter = CombinedSearchFormatter;
166 assert!(
167 formatter
168 .supported_kinds()
169 .contains(&PayloadKind::CombinedSearch)
170 );
171 }
172
173 #[test]
174 fn spotify_search_supports_kind() {
175 let formatter = SpotifySearchFormatter;
176 assert!(
177 formatter
178 .supported_kinds()
179 .contains(&PayloadKind::SearchResults)
180 );
181 }
182
183 #[test]
184 fn pins_supports_kind() {
185 let formatter = PinsFormatter;
186 assert!(formatter.supported_kinds().contains(&PayloadKind::Pins));
187 }
188
189 #[test]
190 fn pins_format_handles_empty_array() {
191 let formatter = PinsFormatter;
192 let payload = json!({ "pins": [] });
193 formatter.format(&payload, "No pins");
194 }
195}