use std::fmt::{self, Display};
use std::str::FromStr;
header! {
#[doc="`Content-Range` header, defined in"]
#[doc="[RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2)"]
(ContentRange, "Content-Range") => [ContentRangeSpec]
test_content_range {
test_header!(test_bytes,
vec![b"bytes 0-499/500"],
Some(ContentRange(ContentRangeSpec::Bytes {
range: Some((0, 499)),
instance_length: Some(500)
})));
test_header!(test_bytes_unknown_len,
vec![b"bytes 0-499/*"],
Some(ContentRange(ContentRangeSpec::Bytes {
range: Some((0, 499)),
instance_length: None
})));
test_header!(test_bytes_unknown_range,
vec![b"bytes */500"],
Some(ContentRange(ContentRangeSpec::Bytes {
range: None,
instance_length: Some(500)
})));
test_header!(test_unregistered,
vec![b"seconds 1-2"],
Some(ContentRange(ContentRangeSpec::Unregistered {
unit: "seconds".to_string(),
resp: "1-2".to_string()
})));
test_header!(test_no_len,
vec![b"bytes 0-499"],
None::<ContentRange>);
test_header!(test_only_unit,
vec![b"bytes"],
None::<ContentRange>);
test_header!(test_end_less_than_start,
vec![b"bytes 499-0/500"],
None::<ContentRange>);
test_header!(test_blank,
vec![b""],
None::<ContentRange>);
test_header!(test_bytes_many_spaces,
vec![b"bytes 1-2/500 3"],
None::<ContentRange>);
test_header!(test_bytes_many_slashes,
vec![b"bytes 1-2/500/600"],
None::<ContentRange>);
test_header!(test_bytes_many_dashes,
vec![b"bytes 1-2-3/500"],
None::<ContentRange>);
}
}
#[derive(PartialEq, Clone, Debug)]
pub enum ContentRangeSpec {
Bytes {
range: Option<(u64, u64)>,
instance_length: Option<u64>
},
Unregistered {
unit: String,
resp: String
}
}
fn split_in_two<'a>(s: &'a str, separator: char) -> Option<(&'a str, &'a str)> {
let mut iter = s.splitn(2, separator);
match (iter.next(), iter.next()) {
(Some(a), Some(b)) => Some((a, b)),
_ => None
}
}
impl FromStr for ContentRangeSpec {
type Err = ::Error;
fn from_str(s: &str) -> ::Result<Self> {
let res = match split_in_two(s, ' ') {
Some(("bytes", resp)) => {
let (range, instance_length) = try!(split_in_two(resp, '/').ok_or(::Error::Header));
let instance_length = if instance_length == "*" {
None
} else {
Some(try!(instance_length.parse().map_err(|_| ::Error::Header)))
};
let range = if range == "*" {
None
} else {
let (first_byte, last_byte) = try!(split_in_two(range, '-').ok_or(::Error::Header));
let first_byte = try!(first_byte.parse().map_err(|_| ::Error::Header));
let last_byte = try!(last_byte.parse().map_err(|_| ::Error::Header));
if last_byte < first_byte {
return Err(::Error::Header);
}
Some((first_byte, last_byte))
};
ContentRangeSpec::Bytes {
range: range,
instance_length: instance_length
}
}
Some((unit, resp)) => {
ContentRangeSpec::Unregistered {
unit: unit.to_string(),
resp: resp.to_string()
}
}
_ => return Err(::Error::Header)
};
Ok(res)
}
}
impl Display for ContentRangeSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ContentRangeSpec::Bytes { range, instance_length } => {
try!(f.write_str("bytes "));
match range {
Some((first_byte, last_byte)) => {
try!(write!(f, "{}-{}", first_byte, last_byte));
},
None => {
try!(f.write_str("*"));
}
};
try!(f.write_str("/"));
if let Some(v) = instance_length {
write!(f, "{}", v)
} else {
f.write_str("*")
}
}
ContentRangeSpec::Unregistered { ref unit, ref resp } => {
try!(f.write_str(&unit));
try!(f.write_str(" "));
f.write_str(&resp)
}
}
}
}