jsonrpc_async/
util.rs

1// Rust JSON-RPC Library
2// Written in 2019 by
3//   Andrew Poelstra <apoelstra@wpsoftware.net>
4//
5// To the extent possible under law, the author(s) have dedicated all
6// copyright and related and neighboring rights to this software to
7// the public domain worldwide. This software is distributed without
8// any warranty.
9//
10// You should have received a copy of the CC0 Public Domain Dedication
11// along with this software.
12// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
13//
14
15use std::borrow::Cow;
16use std::hash::{Hash, Hasher};
17
18use serde_json::Value;
19
20/// Newtype around `Value` which allows hashing for use as hashmap keys
21/// This is needed for batch requests.
22///
23/// The reason `Value` does not support `Hash` or `Eq` by itself
24/// is that it supports `f64` values; but for batch requests we
25/// will only be hashing the "id" field of the request/response
26/// pair, which should never need decimal precision and therefore
27/// never use `f64`.
28#[derive(Clone, PartialEq, Debug)]
29pub struct HashableValue<'a>(pub Cow<'a, Value>);
30
31impl<'a> Eq for HashableValue<'a> {}
32
33impl<'a> Hash for HashableValue<'a> {
34    fn hash<H: Hasher>(&self, state: &mut H) {
35        match *self.0.as_ref() {
36            Value::Null => "null".hash(state),
37            Value::Bool(false) => "false".hash(state),
38            Value::Bool(true) => "true".hash(state),
39            Value::Number(ref n) => {
40                "number".hash(state);
41                if let Some(n) = n.as_i64() {
42                    n.hash(state);
43                } else if let Some(n) = n.as_u64() {
44                    n.hash(state);
45                } else {
46                    n.to_string().hash(state);
47                }
48            }
49            Value::String(ref s) => {
50                "string".hash(state);
51                s.hash(state);
52            }
53            Value::Array(ref v) => {
54                "array".hash(state);
55                v.len().hash(state);
56                for obj in v {
57                    HashableValue(Cow::Borrowed(obj)).hash(state);
58                }
59            }
60            Value::Object(ref m) => {
61                "object".hash(state);
62                m.len().hash(state);
63                for (key, val) in m {
64                    key.hash(state);
65                    HashableValue(Cow::Borrowed(val)).hash(state);
66                }
67            }
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use std::borrow::Cow;
75    use std::collections::HashSet;
76    use std::str::FromStr;
77
78    use super::*;
79
80    #[test]
81    fn hash_value() {
82        let val = HashableValue(Cow::Owned(Value::from_str("null").unwrap()));
83        let t = HashableValue(Cow::Owned(Value::from_str("true").unwrap()));
84        let f = HashableValue(Cow::Owned(Value::from_str("false").unwrap()));
85        let ns =
86            HashableValue(Cow::Owned(Value::from_str("[0, -0, 123.4567, -100000000]").unwrap()));
87        let m =
88            HashableValue(Cow::Owned(Value::from_str("{ \"field\": 0, \"field\": -0 }").unwrap()));
89
90        let mut coll = HashSet::new();
91
92        assert!(!coll.contains(&val));
93        coll.insert(val.clone());
94        assert!(coll.contains(&val));
95
96        assert!(!coll.contains(&t));
97        assert!(!coll.contains(&f));
98        coll.insert(t.clone());
99        assert!(coll.contains(&t));
100        assert!(!coll.contains(&f));
101        coll.insert(f.clone());
102        assert!(coll.contains(&t));
103        assert!(coll.contains(&f));
104
105        assert!(!coll.contains(&ns));
106        coll.insert(ns.clone());
107        assert!(coll.contains(&ns));
108
109        assert!(!coll.contains(&m));
110        coll.insert(m.clone());
111        assert!(coll.contains(&m));
112    }
113}