sunk/search.rs
1//! Methods and containers for searching and search results.
2//!
3//! The Subsonic API works on the concept of paging, something not uncommon in
4//! RESTful APIs. A search will return a number of results up to a
5//! specification. The client then has a virtual "page" number they will send,
6//! to offset a search by a multiple of the return number.
7//!
8//! # Example
9//!
10//! Suppose a Subsonic server has 50 albums stored on it.
11//!
12//! ```no_run
13//! extern crate sunk;
14//! use sunk::{Album, Client, ListType};
15//! use sunk::search::{self, SearchPage};
16//!
17//! # fn run() -> sunk::Result<()> {
18//! # let site = "https://demo.subsonic.org";
19//! # let username = "guest3";
20//! # let password = "guest";
21//! let client = Client::new(site, username, password)?;
22//! let mut page = SearchPage::new();
23//! let list = ListType::default();
24//!
25//! let results = Album::list(&client, list, page, 0)?;
26//! assert_eq!(results.len(), 20);
27//! #
28//! # page.next();
29//! # let more_results = Album::list(&client, list, page, 0)?;
30//! # assert_eq!(more_results.len(), 20);
31//! #
32//! # page.next();
33//! # let last_results = Album::list(&client, list, page, 0)?;
34//! # assert_eq!(last_results.len(), 10);
35//! #
36//! # let exact = SearchPage::new().with_size(50);
37//! # let exact_results = Album::list(&client, list, exact, 0)?;
38//! # assert_eq!(exact_results.len(), 50);
39//! #
40//! # let all = search::ALL;
41//! # let all_results = Album::list(&client, list, all, 0)?;
42//! # assert_eq!(all_results.len(), 50);
43//! #
44//! # Ok(())
45//! # }
46//! # fn main() { }
47//! ```
48//!
49//! How do we get the remaining 30 songs from the server? By paging.
50//!
51//! ```no_run
52//! # extern crate sunk;
53//! # use sunk::{Album, Client, ListType};
54//! # use sunk::search::{self, SearchPage};
55//! #
56//! # fn run() -> sunk::Result<()> {
57//! # let site = "https://demo.subsonic.org";
58//! # let username = "guest3";
59//! # let password = "guest";
60//! # let client = Client::new(site, username, password)?;
61//! # let mut page = SearchPage::new();
62//! # let list = ListType::default();
63//! #
64//! # let results = Album::list(&client, list, page, 0)?;
65//! # assert_eq!(results.len(), 20);
66//! #
67//! page.next();
68//! let more_results = Album::list(&client, list, page, 0)?;
69//! assert_eq!(more_results.len(), 20);
70//!
71//! page.next();
72//! let last_results = Album::list(&client, list, page, 0)?;
73//! assert_eq!(last_results.len(), 10);
74//! #
75//! # let exact = SearchPage::new().with_size(50);
76//! # let exact_results = Album::list(&client, list, exact, 0)?;
77//! # assert_eq!(exact_results.len(), 50);
78//! #
79//! # let all = search::ALL;
80//! # let all_results = Album::list(&client, list, all, 0)?;
81//! # assert_eq!(all_results.len(), 50);
82//! #
83//! # Ok(())
84//! # }
85//! # fn main() { }
86//! ```
87//!
88//! Notice that the last set of results only returns *up to* the count in the
89//! `SearchPage`.
90//!
91//! Of course, if we knew beforehand how many results there would be, we could
92//! request exactly fifty albums.
93//!
94//! ```no_run
95//! # extern crate sunk;
96//! # use sunk::{Album, Client, ListType};
97//! # use sunk::search::{self, SearchPage};
98//! #
99//! # fn run() -> sunk::Result<()> {
100//! # let site = "https://demo.subsonic.org";
101//! # let username = "guest3";
102//! # let password = "guest";
103//! # let client = Client::new(site, username, password)?;
104//! # let mut page = SearchPage::new();
105//! # let list = ListType::default();
106//! #
107//! # let results = Album::list(&client, list, page, 0)?;
108//! # assert_eq!(results.len(), 20);
109//! #
110//! # page.next();
111//! # let more_results = Album::list(&client, list, page, 0)?;
112//! # assert_eq!(more_results.len(), 20);
113//! #
114//! # page.next();
115//! # let last_results = Album::list(&client, list, page, 0)?;
116//! # assert_eq!(last_results.len(), 10);
117//! #
118//! let exact = SearchPage::new().with_size(50);
119//! let exact_results = Album::list(&client, list, exact, 0)?;
120//! assert_eq!(exact_results.len(), 50);
121//! #
122//! # let all = search::ALL;
123//! # let all_results = Album::list(&client, list, all, 0)?;
124//! # assert_eq!(all_results.len(), 50);
125//! #
126//! # Ok(())
127//! # }
128//! # fn main() { }
129//! ```
130//!
131//! However, if we didn't, there's a convinent constant in place to return up
132//! to 500 results. This is set at 500 because most Subsonic functions only
133//! accept up to returning 500 results. It's still possible to page through
134//! results if you have to.
135//!
136//! ```no_run
137//! # extern crate sunk;
138//! # use sunk::{Album, Client, ListType};
139//! # use sunk::search::{self, SearchPage};
140//! #
141//! # fn run() -> sunk::Result<()> {
142//! # let site = "https://demo.subsonic.org";
143//! # let username = "guest3";
144//! # let password = "guest";
145//! # let client = Client::new(site, username, password)?;
146//! # let mut page = SearchPage::new();
147//! # let list = ListType::default();
148//! #
149//! # let results = Album::list(&client, list, page, 0)?;
150//! # assert_eq!(results.len(), 20);
151//! #
152//! # page.next();
153//! # let more_results = Album::list(&client, list, page, 0)?;
154//! # assert_eq!(more_results.len(), 20);
155//! #
156//! # page.next();
157//! # let last_results = Album::list(&client, list, page, 0)?;
158//! # assert_eq!(last_results.len(), 10);
159//! #
160//! # let exact = SearchPage::new().with_size(50);
161//! # let exact_results = Album::list(&client, list, exact, 0)?;
162//! # assert_eq!(exact_results.len(), 50);
163//! #
164//! let all = search::ALL;
165//! let all_results = Album::list(&client, list, all, 0)?;
166//! assert_eq!(all_results.len(), 50);
167//! #
168//! # Ok(())
169//! # }
170//! # fn main() { }
171//! ```
172
173use song::Song;
174use std::fmt;
175use {Album, Artist};
176
177/// The maximum number of results most searches will accept.
178pub const ALL: SearchPage = SearchPage {
179 count: 500,
180 offset: 0,
181};
182
183/// Effectively makes a search ignore the field.
184pub const NONE: SearchPage = SearchPage {
185 count: 0,
186 offset: 0,
187};
188
189/// A holding struct for a search configuration.
190///
191/// See the [module-level documentation](./index.html) for more information.
192#[derive(Debug, Copy, Clone)]
193pub struct SearchPage {
194 /// The number of results to return.
195 pub count: usize,
196 /// The page offset.
197 pub offset: usize,
198}
199
200impl SearchPage {
201 /// Creates a new search page configuration.
202 pub fn new() -> SearchPage {
203 SearchPage {
204 offset: 0,
205 count: 20,
206 }
207 }
208
209 /// Creates the configuration at the provided page.
210 pub fn at_page(offset: usize) -> SearchPage {
211 SearchPage { offset, count: 20 }
212 }
213
214 /// Sets the configuration to the given size.
215 pub fn with_size(self, count: usize) -> SearchPage {
216 SearchPage {
217 offset: self.offset,
218 count,
219 }
220 }
221
222 /// Advances the page.
223 pub fn next(&mut self) {
224 self.offset += 1;
225 }
226
227 /// Decrements the page.
228 pub fn prev(&mut self) {
229 self.offset -= 1;
230 }
231}
232
233impl Default for SearchPage {
234 fn default() -> SearchPage {
235 SearchPage::new()
236 }
237}
238
239impl fmt::Display for SearchPage {
240 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
241 write!(
242 f,
243 "search range {}-{}",
244 self.count * self.offset,
245 (self.count + 1) * self.offset - 1
246 )
247 }
248}
249
250/// A holder struct for a search result.
251#[derive(Debug, Deserialize, Clone)]
252pub struct SearchResult {
253 /// Artists found in the search.
254 #[serde(rename = "artist")]
255 #[serde(default)]
256 pub artists: Vec<Artist>,
257 /// Albums found in the search.
258 #[serde(rename = "album")]
259 #[serde(default)]
260 pub albums: Vec<Album>,
261 /// Songs found in the search.
262 #[serde(rename = "song")]
263 #[serde(default)]
264 pub songs: Vec<Song>,
265}