lfsx_server/page.rs
1use crate::locks::Lock;
2
3// The lock list is answered from a directory of files, so there is no server-side
4// cursor to hand out and nothing to keep between requests. The id of the last
5// lock returned is enough: the list is ordered by id, so resuming means skipping
6// past that one. A lock released in between simply is not there any more, which
7// is the same answer a client would get by asking again.
8pub const DEFAULT: usize = 100;
9pub const MAX: usize = 1000;
10
11pub struct Page {
12 pub locks: Vec<Lock>,
13 pub next_cursor: String,
14}
15
16pub fn paginate(mut locks: Vec<Lock>, cursor: Option<&str>, limit: Option<usize>) -> Page {
17 locks.sort_by(|one, other| one.id.cmp(&other.id));
18
19 let from = match cursor {
20 Some(cursor) => locks
21 .iter()
22 .position(|lock| lock.id.as_str() > cursor)
23 .unwrap_or(locks.len()),
24 None => 0,
25 };
26
27 // A limit the client did not ask for is still a limit: without one, a studio
28 // that has locked an art directory receives every lock it holds in a single
29 // body, and a client honouring the field it sent believes it has seen the
30 // whole list.
31 let limit = limit.unwrap_or(DEFAULT).clamp(1, MAX);
32 let mut page: Vec<Lock> = locks.into_iter().skip(from).take(limit + 1).collect();
33
34 // One more was fetched than asked for: if it is there, there is another page,
35 // and the cursor is the last id actually returned.
36 let next_cursor = match page.len() > limit {
37 true => {
38 page.truncate(limit);
39 page.last().map(|lock| lock.id.clone()).unwrap_or_default()
40 }
41 false => String::new(),
42 };
43
44 Page {
45 locks: page,
46 next_cursor,
47 }
48}
49
50#[cfg(test)]
51mod tests;