use std::fs;
use std::fmt;
use std::time;
use std::error;
use std::hash::{
Hash,
Hasher
};
use std::collections::hash_map::DefaultHasher;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EntityTag {
pub weak: bool,
tag: String,
}
impl EntityTag {
pub fn new(weak: bool, tag: String) -> Self {
assert!(tag.is_ascii());
EntityTag { weak, tag }
}
pub fn weak(tag: String) -> Self {
Self::new(true, tag)
}
pub fn strong(tag: String) -> Self {
Self::new(false, tag)
}
pub fn from_file_meta(metadata: &fs::Metadata) -> Self {
let tag = match metadata.modified().map(|modified| modified.duration_since(time::UNIX_EPOCH).expect("Modified is earlier than time::UNIX_EPOCH!")) {
Ok(modified) => format!("{}.{}-{}", modified.as_secs(), modified.subsec_nanos(), metadata.len()),
_ => format!("{}", metadata.len())
};
Self {
weak: true,
tag
}
}
pub fn from_hash(bytes: &[u8]) -> Self {
let mut hasher = DefaultHasher::default();
bytes.hash(&mut hasher);
let tag = format!("{}-{}", bytes.len(), hasher.finish());
Self {
weak: false,
tag
}
}
pub fn tag(&self) -> &str {
self.tag.as_ref()
}
pub fn set_tag(&mut self, tag: String) {
self.tag = tag
}
pub fn strong_eq(&self, other: &EntityTag) -> bool {
!self.weak && !other.weak && self.tag == other.tag
}
pub fn weak_eq(&self, other: &EntityTag) -> bool {
self.tag == other.tag
}
pub fn strong_ne(&self, other: &EntityTag) -> bool {
!self.strong_eq(other)
}
pub fn weak_ne(&self, other: &EntityTag) -> bool {
!self.weak_eq(other)
}
}
impl fmt::Display for EntityTag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.weak {
true => write!(f, "W/\"{}\"", self.tag),
false => write!(f, "\"{}\"", self.tag),
}
}
}
#[derive(Debug)]
pub enum ParseError {
InvalidFormat,
NotAscii
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseError::InvalidFormat => write!(f, "EntityTag uses invalid format"),
ParseError::NotAscii => write!(f, "EntityTag uses non-ASCII characters")
}
}
}
impl error::Error for ParseError {
fn description(&self) -> &str {
match self {
ParseError::InvalidFormat => "EntityTag uses invalid format",
ParseError::NotAscii => "EntityTag uses non-ASCII characters"
}
}
}
impl std::str::FromStr for EntityTag {
type Err = ParseError;
fn from_str(text: &str) -> Result<EntityTag, ParseError> {
let len = text.len();
let slice = &text[..];
if !slice.ends_with('"') || len < 2 {
return Err(ParseError::InvalidFormat);
}
if slice.starts_with('"') {
let slice = &slice[1..len-1];
match slice.is_ascii() {
true => Ok(EntityTag::strong(slice.to_string())),
false => Err(ParseError::NotAscii)
}
} else if len >= 4 && slice.starts_with("W/\"") {
let slice = &slice[3..len-1];
match slice.is_ascii() {
true => Ok(EntityTag::weak(slice.to_string())),
false => Err(ParseError::NotAscii)
}
} else {
Err(ParseError::InvalidFormat)
}
}
}