1#![doc = include_str!("../README.md")]
2mod encode;
3mod sha1;
4
5use std::fmt;
6
7#[derive(Debug, Clone, PartialEq)]
14pub enum Value {
15 Null,
17 Bool(bool),
19 Number(f64),
22 String(String),
24 Array(Vec<Value>),
26 Set(Vec<Value>),
28 Map(Vec<(String, Value)>),
30}
31
32impl Value {
33 #[must_use]
35 pub fn string(text: impl Into<String>) -> Self {
36 Self::String(text.into())
37 }
38
39 #[must_use]
41 pub fn number(number: impl Into<f64>) -> Self {
42 Self::Number(number.into())
43 }
44
45 pub(crate) fn is_scalar(&self) -> bool {
51 matches!(
52 self,
53 Self::Null | Self::Bool(_) | Self::Number(_) | Self::String(_)
54 )
55 }
56
57 #[must_use]
59 pub fn hash(&self) -> String {
60 hash(self)
61 }
62}
63
64impl fmt::Display for Value {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.write_str(&canonical(self))
67 }
68}
69
70impl From<bool> for Value {
71 fn from(value: bool) -> Self {
72 Self::Bool(value)
73 }
74}
75
76impl From<f64> for Value {
77 fn from(value: f64) -> Self {
78 Self::Number(value)
79 }
80}
81
82impl From<i64> for Value {
83 fn from(value: i64) -> Self {
84 Self::Number(value as f64)
85 }
86}
87
88impl From<i32> for Value {
89 fn from(value: i32) -> Self {
90 Self::Number(f64::from(value))
91 }
92}
93
94impl From<u32> for Value {
95 fn from(value: u32) -> Self {
96 Self::Number(f64::from(value))
97 }
98}
99
100impl From<&str> for Value {
101 fn from(value: &str) -> Self {
102 Self::String(value.to_string())
103 }
104}
105
106impl From<String> for Value {
107 fn from(value: String) -> Self {
108 Self::String(value)
109 }
110}
111
112impl<T: Into<Value>> From<Vec<T>> for Value {
113 fn from(value: Vec<T>) -> Self {
114 Self::Array(value.into_iter().map(Into::into).collect())
115 }
116}
117
118#[must_use]
123pub fn canonical(value: &Value) -> String {
124 encode::canonical(value)
125}
126
127#[must_use]
155pub fn hash(value: &Value) -> String {
156 sha1::hex(canonical(value).as_bytes())
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn a_digest_is_forty_hexadecimal_characters() {
165 let digest = hash(&Value::Null);
166 assert_eq!(digest.len(), 40);
167 assert!(digest.chars().all(|c| c.is_ascii_hexdigit()));
168 }
169
170 #[test]
171 fn equal_values_hash_the_same_and_different_values_do_not() {
172 assert_eq!(hash(&Value::Number(1.0)), hash(&Value::Number(1.0)));
173 assert_ne!(hash(&Value::Number(1.0)), hash(&Value::Number(2.0)));
174 assert_ne!(hash(&Value::Number(1.0)), hash(&Value::String("1".into())));
175 assert_ne!(hash(&Value::Null), hash(&Value::String("Null".into())));
176 }
177
178 #[test]
179 fn the_conversions_build_the_expected_values() {
180 assert_eq!(Value::from("a"), Value::String("a".into()));
181 assert_eq!(Value::from(1_i32), Value::Number(1.0));
182 assert_eq!(Value::from(true), Value::Bool(true));
183 assert_eq!(
184 Value::from(vec![1_i32, 2]),
185 Value::Array(vec![Value::Number(1.0), Value::Number(2.0)])
186 );
187 }
188
189 #[test]
190 fn the_display_form_is_the_canonical_form() {
191 assert_eq!(Value::Bool(true).to_string(), "bool:true");
192 assert_eq!(Value::Null.to_string(), "Null");
193 }
194}