1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//! ETag Trait implementation for various types.
//!
//! For file's Metadata it uses following format `<modified>-<size>`
//!
//! For other types it uses `<len>-<hash>` that relies on `std::hash::Hash` and `DefaultHasher` of HashMap.
//!
//! ## Usage
//! ```rust
//! extern crate etag;
//!
//! use etag::Etag;
//!
//! fn main() {
//!     println!("ETag for string={}", "string".etag());
//! }
//! ```

use std::fs;
use std::time;
use std::hash::{
    Hash,
    Hasher
};
use std::collections::hash_map::DefaultHasher;

///Trait that provides calculation of ETag field for type.
pub trait Etag {
    ///Calculates ETag value.
    fn etag(&self) -> String;
}

impl Etag for fs::Metadata {
    ///Calculates ETag from Metadata in following format `<modified>-<size>`.
    ///
    ///Note that if modified is not available then it is omitted.
    fn etag(&self) -> String {
        if let Ok(modified) = self.modified() {
            let modified = modified.duration_since(time::UNIX_EPOCH).expect("Modified is earlier than time::UNIX_EPOCH!");
            format!("{}.{}-{}", modified.as_secs(), modified.subsec_nanos(), self.len())
        }
        else {
            format!("{}", self.len())
        }
    }
}

macro_rules! impl_with_hasher
{
    ($($t:ty), +) => {
        $(
            impl Etag for $t {
                fn etag(&self) -> String {
                    let mut hasher = DefaultHasher::new();
                    self.hash(&mut hasher);
                    format!("{}-{}", self.len(), hasher.finish())
                }
            }
        )+
    };
}

impl_with_hasher!(str, String, [u8]);