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
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Rust JSON-RPC Library
// Written in 2019 by
//   Andrew Poelstra <apoelstra@wpsoftware.net>
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//

use std::borrow::Cow;
use std::hash::{Hash, Hasher};

use serde_json::Value;

/// Newtype around `Value` which allows hashing for use as hashmap keys
/// This is needed for batch requests.
///
/// The reason `Value` does not support `Hash` or `Eq` by itself
/// is that it supports `f64` values; but for batch requests we
/// will only be hashing the "id" field of the request/response
/// pair, which should never need decimal precision and therefore
/// never use `f64`.
#[derive(Clone, PartialEq, Debug)]
pub struct HashableValue<'a>(pub Cow<'a, Value>);

impl<'a> Eq for HashableValue<'a> {}

impl<'a> Hash for HashableValue<'a> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match *self.0.as_ref() {
            Value::Null => "null".hash(state),
            Value::Bool(false) => "false".hash(state),
            Value::Bool(true) => "true".hash(state),
            Value::Number(ref n) => {
                "number".hash(state);
                if let Some(n) = n.as_i64() {
                    n.hash(state);
                } else if let Some(n) = n.as_u64() {
                    n.hash(state);
                } else {
                    n.to_string().hash(state);
                }
            },
            Value::String(ref s) => {
                "string".hash(state);
                s.hash(state);
            },
            Value::Array(ref v) => {
                "array".hash(state);
                v.len().hash(state);
                for obj in v {
                    HashableValue(Cow::Borrowed(obj)).hash(state);
                }
            },
            Value::Object(ref m) => {
                "object".hash(state);
                m.len().hash(state);
                for (key, val) in m {
                    key.hash(state);
                    HashableValue(Cow::Borrowed(val)).hash(state);
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::borrow::Cow;
    use std::collections::HashSet;
    use std::str::FromStr;

    use super::*;

    #[test]
    fn hash_value() {
        let val = HashableValue(Cow::Owned(Value::from_str("null").unwrap()));
        let t = HashableValue(Cow::Owned(Value::from_str("true").unwrap()));
        let f = HashableValue(Cow::Owned(Value::from_str("false").unwrap()));
        let ns = HashableValue(Cow::Owned(Value::from_str("[0, -0, 123.4567, -100000000]").unwrap()));
        let m = HashableValue(Cow::Owned(Value::from_str("{ \"field\": 0, \"field\": -0 }").unwrap()));

        let mut coll = HashSet::new();

        assert!(!coll.contains(&val));
        coll.insert(val.clone());
        assert!(coll.contains(&val));

        assert!(!coll.contains(&t));
        assert!(!coll.contains(&f));
        coll.insert(t.clone());
        assert!(coll.contains(&t));
        assert!(!coll.contains(&f));
        coll.insert(f.clone());
        assert!(coll.contains(&t));
        assert!(coll.contains(&f));

        assert!(!coll.contains(&ns));
        coll.insert(ns.clone());
        assert!(coll.contains(&ns));

        assert!(!coll.contains(&m));
        coll.insert(m.clone());
        assert!(coll.contains(&m));
    }
}