use crate::paging;
use std::cmp;
use url::Url;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Paging {
current: Url,
offset: i64,
limit: i64,
total: i64,
first: Url,
prev: Option<Url>,
next: Option<Url>,
last: Url,
}
impl Paging {
pub fn new(mut base_url: Url, paging: paging::Paging, service_id: Option<&str>) -> Self {
let limit = paging.limit.to_string();
base_url.query_pairs_mut().append_pair("limit", &limit);
if let Some(service_id) = service_id {
base_url
.query_pairs_mut()
.append_pair("service_id", service_id);
}
let generator = PageUrlGenerator { url: base_url };
let offsets = Offsets::new(&paging);
Paging {
current: generator.url_with_offset(paging.offset),
offset: paging.offset,
limit: paging.limit,
total: paging.total,
first: generator.url_with_offset(offsets.first),
prev: offsets.prev.map(|v| generator.url_with_offset(v)),
last: generator.url_with_offset(offsets.last),
next: offsets.next.map(|v| generator.url_with_offset(v)),
}
}
}
#[derive(Debug, Eq, PartialEq)]
struct Offsets {
first: i64,
prev: Option<i64>,
next: Option<i64>,
last: i64,
}
impl Offsets {
fn new(paging: &paging::Paging) -> Self {
let last_offset = cmp::max(((paging.total - 1) / paging.limit) * paging.limit, 0);
Offsets {
first: 0,
prev: if paging.offset == 0 {
None
} else if paging.offset > paging.total {
Some(last_offset)
} else {
Some(cmp::max(paging.offset - paging.limit, 0))
},
last: last_offset,
next: if paging.offset >= last_offset {
None
} else {
Some(if paging.offset + paging.limit > last_offset {
last_offset
} else {
paging.offset + paging.limit
})
},
}
}
}
struct PageUrlGenerator {
url: Url,
}
impl PageUrlGenerator {
fn url_with_offset<T: ToString>(&self, offset: T) -> Url {
let mut url = self.url.clone();
url.query_pairs_mut()
.append_pair("offset", &offset.to_string());
url
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_offset_second_page() {
assert_eq!(
Offsets::new(&paging::Paging {
offset: 20,
limit: 10,
total: 80
}),
Offsets {
first: 0,
prev: Some(10),
next: Some(30),
last: 70
}
);
}
#[test]
fn test_offset_first_page() {
assert_eq!(
Offsets::new(&paging::Paging {
offset: 0,
limit: 10,
total: 80
}),
Offsets {
first: 0,
prev: None,
next: Some(10),
last: 70
}
);
}
#[test]
fn test_offset_last_page() {
assert_eq!(
Offsets::new(&paging::Paging {
offset: 70,
limit: 10,
total: 80
}),
Offsets {
first: 0,
prev: Some(60),
next: None,
last: 70
}
);
}
#[test]
fn test_offset_beyond_limit_prev_is_offset_to_last_page() {
assert_eq!(
Offsets::new(&paging::Paging {
offset: 100,
limit: 10,
total: 80
}),
Offsets {
first: 0,
prev: Some(70),
next: None,
last: 70
}
);
}
#[test]
fn test_offset_not_aligned_with_limit() {
assert_eq!(
Offsets::new(&paging::Paging {
offset: 5,
limit: 10,
total: 80
}),
Offsets {
first: 0,
prev: Some(0),
next: Some(15),
last: 70
}
);
}
#[test]
fn test_offset_total_smaller_than_limit() {
assert_eq!(
Offsets::new(&paging::Paging {
offset: 5,
limit: 100,
total: 80
}),
Offsets {
first: 0,
prev: Some(0),
next: None,
last: 0
}
);
}
#[test]
fn test_paging_absolute_url() {
assert_eq!(
Paging::new(
Url::parse("http://base/").unwrap(),
paging::Paging {
offset: 20,
limit: 10,
total: 80
},
Some("fakeserviceid"),
),
Paging {
current: Url::parse("http://base/?limit=10&service_id=fakeserviceid&offset=20")
.unwrap(),
offset: 20,
limit: 10,
total: 80,
first: Url::parse("http://base/?limit=10&service_id=fakeserviceid&offset=0")
.unwrap(),
prev: Some(
Url::parse("http://base/?limit=10&service_id=fakeserviceid&offset=10").unwrap()
),
next: Some(
Url::parse("http://base/?limit=10&service_id=fakeserviceid&offset=30").unwrap()
),
last: Url::parse("http://base/?limit=10&service_id=fakeserviceid&offset=70")
.unwrap(),
}
);
}
#[test]
fn test_paging_query_params() {
assert_eq!(
Paging::new(
Url::parse("http://base/?unrelated_filter=9").unwrap(),
paging::Paging {
offset: 20,
limit: 10,
total: 80
},
Some("fakeserviceid"),
),
Paging {
current: Url::parse(
"http://base/?unrelated_filter=9&limit=10&service_id=fakeserviceid&offset=20"
).unwrap(),
offset: 20,
limit: 10,
total: 80,
first: Url::parse(
"http://base/?unrelated_filter=9&limit=10&service_id=fakeserviceid&offset=0"
).unwrap(),
prev: Some(Url::parse(
"http://base/?unrelated_filter=9&limit=10&service_id=fakeserviceid&offset=10"
).unwrap()),
next: Some(Url::parse(
"http://base/?unrelated_filter=9&limit=10&service_id=fakeserviceid&offset=30"
).unwrap()),
last: Url::parse(
"http://base/?unrelated_filter=9&limit=10&service_id=fakeserviceid&offset=70"
).unwrap(),
}
);
}
}