Skip to main content

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}