use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Range {
pub offset: i64,
pub limit_to: Option<i64>,
}
impl Default for Range {
fn default() -> Self {
Self::all()
}
}
impl Range {
pub fn all() -> Self {
Self {
offset: 0,
limit_to: None,
}
}
pub fn new(offset: i64, limit_to: i64) -> Self {
Self {
offset,
limit_to: Some(limit_to),
}
}
pub fn from_offset(offset: i64) -> Self {
Self {
offset,
limit_to: None,
}
}
pub fn limit_zero() -> Self {
Self {
offset: 0,
limit_to: Some(-1),
}
}
pub fn has_limit_zero(&self) -> bool {
self.limit_to == Some(-1)
}
pub fn limit(&self) -> Option<i64> {
self.limit_to.map(|upper| 1 + upper - self.offset)
}
pub fn offset(&self) -> i64 {
self.offset
}
pub fn is_all(&self) -> bool {
self.offset == 0 && self.limit_to.is_none()
}
pub fn is_empty_range(&self) -> bool {
match self.limit_to {
Some(upper) => self.offset > upper && !self.has_limit_zero(),
None => false,
}
}
pub fn restrict(&self, max_rows: Option<i64>) -> Self {
match max_rows {
None => *self,
Some(limit) => {
let new_upper = self.offset + limit - 1;
match self.limit_to {
Some(upper) => Self {
offset: self.offset,
limit_to: Some(upper.min(new_upper)),
},
None => Self {
offset: self.offset,
limit_to: Some(new_upper),
},
}
}
}
}
pub fn with_limit(&self, limit: i64) -> Self {
Self {
offset: self.offset,
limit_to: Some(self.offset + limit - 1),
}
}
pub fn with_offset(&self, offset: i64) -> Self {
Self {
offset,
limit_to: self.limit_to,
}
}
pub fn intersect(&self, other: &Range) -> Self {
let new_offset = self.offset.max(other.offset);
let new_upper = match (self.limit_to, other.limit_to) {
(Some(a), Some(b)) => Some(a.min(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
};
Self {
offset: new_offset,
limit_to: new_upper,
}
}
pub fn convert_to_limit_zero(&self, fallback: &Range) -> Self {
if self.has_limit_zero() {
Self::limit_zero()
} else {
*fallback
}
}
}
impl fmt::Display for Range {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.limit_to {
Some(upper) => write!(f, "{}-{}", self.offset, upper),
None => write!(f, "{}-", self.offset),
}
}
}
pub fn parse_range_header(header: &str) -> Option<Range> {
let range_str = header
.strip_prefix("items=")
.or_else(|| header.strip_prefix("Items="))?;
let (start_str, end_str) = range_str.split_once('-')?;
let start: i64 = start_str.parse().ok()?;
if end_str.is_empty() {
Some(Range::from_offset(start))
} else {
let end: i64 = end_str.parse().ok()?;
Some(Range::new(start, end))
}
}
pub fn content_range_header(lower: i64, upper: i64, total: Option<i64>) -> String {
let total_str = match total {
Some(t) => t.to_string(),
None => "*".to_string(),
};
let total_not_zero = total != Some(0);
let from_in_range = lower <= upper;
if total_not_zero && from_in_range {
format!("{}-{}/{}", lower, upper, total_str)
} else {
format!("*/{}", total_str)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_range_all() {
let r = Range::all();
assert_eq!(r.offset, 0);
assert_eq!(r.limit_to, None);
assert!(r.is_all());
assert!(!r.is_empty_range());
}
#[test]
fn test_range_new() {
let r = Range::new(0, 24);
assert_eq!(r.offset, 0);
assert_eq!(r.limit_to, Some(24));
assert!(!r.is_all());
assert_eq!(r.limit(), Some(25));
}
#[test]
fn test_range_from_offset() {
let r = Range::from_offset(10);
assert_eq!(r.offset, 10);
assert_eq!(r.limit_to, None);
assert!(!r.is_all());
}
#[test]
fn test_range_limit_zero() {
let r = Range::limit_zero();
assert!(r.has_limit_zero());
assert_eq!(r.limit(), Some(0));
}
#[test]
fn test_range_empty() {
let r = Range::new(10, 5); assert!(r.is_empty_range());
}
#[test]
fn test_range_restrict() {
let r = Range::all();
let restricted = r.restrict(Some(25));
assert_eq!(restricted.offset, 0);
assert_eq!(restricted.limit_to, Some(24));
let same = r.restrict(None);
assert_eq!(same, r);
}
#[test]
fn test_range_restrict_existing() {
let r = Range::new(0, 100);
let restricted = r.restrict(Some(25));
assert_eq!(restricted.limit_to, Some(24));
let larger = r.restrict(Some(200));
assert_eq!(larger.limit_to, Some(100));
}
#[test]
fn test_range_with_limit() {
let r = Range::from_offset(5);
let limited = r.with_limit(10);
assert_eq!(limited.offset, 5);
assert_eq!(limited.limit_to, Some(14));
assert_eq!(limited.limit(), Some(10));
}
#[test]
fn test_range_with_offset() {
let r = Range::new(0, 24);
let offset = r.with_offset(10);
assert_eq!(offset.offset, 10);
assert_eq!(offset.limit_to, Some(24));
}
#[test]
fn test_range_intersect() {
let a = Range::new(0, 100);
let b = Range::new(10, 50);
let c = a.intersect(&b);
assert_eq!(c.offset, 10);
assert_eq!(c.limit_to, Some(50));
let d = Range::all();
let e = a.intersect(&d);
assert_eq!(e, a);
}
#[test]
fn test_range_display() {
assert_eq!(Range::new(0, 24).to_string(), "0-24");
assert_eq!(Range::from_offset(10).to_string(), "10-");
}
#[test]
fn test_parse_range_header() {
let r = parse_range_header("items=0-24").unwrap();
assert_eq!(r.offset, 0);
assert_eq!(r.limit_to, Some(24));
let r = parse_range_header("items=10-").unwrap();
assert_eq!(r.offset, 10);
assert_eq!(r.limit_to, None);
let r = parse_range_header("Items=5-10").unwrap();
assert_eq!(r.offset, 5);
assert_eq!(r.limit_to, Some(10));
assert!(parse_range_header("bytes=0-24").is_none());
assert!(parse_range_header("items=abc-def").is_none());
assert!(parse_range_header("garbage").is_none());
}
#[test]
fn test_content_range_header() {
assert_eq!(content_range_header(0, 24, Some(100)), "0-24/100");
assert_eq!(content_range_header(0, 24, None), "0-24/*");
assert_eq!(content_range_header(10, 5, Some(100)), "*/100"); assert_eq!(content_range_header(0, 0, Some(0)), "*/0"); }
#[test]
fn test_range_default() {
let r = Range::default();
assert!(r.is_all());
}
#[test]
fn test_convert_to_limit_zero() {
let limit_range = Range::limit_zero();
let fallback = Range::new(0, 24);
let result = limit_range.convert_to_limit_zero(&fallback);
assert!(result.has_limit_zero());
let normal = Range::new(0, 10);
let result2 = normal.convert_to_limit_zero(&fallback);
assert_eq!(result2, fallback);
}
}