#![doc = include_str!("../README.md")]
mod encode;
mod sha1;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<Value>),
Set(Vec<Value>),
Map(Vec<(String, Value)>),
}
impl Value {
#[must_use]
pub fn string(text: impl Into<String>) -> Self {
Self::String(text.into())
}
#[must_use]
pub fn number(number: impl Into<f64>) -> Self {
Self::Number(number.into())
}
pub(crate) fn is_scalar(&self) -> bool {
matches!(
self,
Self::Null | Self::Bool(_) | Self::Number(_) | Self::String(_)
)
}
#[must_use]
pub fn hash(&self) -> String {
hash(self)
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&canonical(self))
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Self::Number(value)
}
}
impl From<i64> for Value {
fn from(value: i64) -> Self {
Self::Number(value as f64)
}
}
impl From<i32> for Value {
fn from(value: i32) -> Self {
Self::Number(f64::from(value))
}
}
impl From<u32> for Value {
fn from(value: u32) -> Self {
Self::Number(f64::from(value))
}
}
impl From<&str> for Value {
fn from(value: &str) -> Self {
Self::String(value.to_string())
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl<T: Into<Value>> From<Vec<T>> for Value {
fn from(value: Vec<T>) -> Self {
Self::Array(value.into_iter().map(Into::into).collect())
}
}
#[must_use]
pub fn canonical(value: &Value) -> String {
encode::canonical(value)
}
#[must_use]
pub fn hash(value: &Value) -> String {
sha1::hex(canonical(value).as_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_digest_is_forty_hexadecimal_characters() {
let digest = hash(&Value::Null);
assert_eq!(digest.len(), 40);
assert!(digest.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn equal_values_hash_the_same_and_different_values_do_not() {
assert_eq!(hash(&Value::Number(1.0)), hash(&Value::Number(1.0)));
assert_ne!(hash(&Value::Number(1.0)), hash(&Value::Number(2.0)));
assert_ne!(hash(&Value::Number(1.0)), hash(&Value::String("1".into())));
assert_ne!(hash(&Value::Null), hash(&Value::String("Null".into())));
}
#[test]
fn the_conversions_build_the_expected_values() {
assert_eq!(Value::from("a"), Value::String("a".into()));
assert_eq!(Value::from(1_i32), Value::Number(1.0));
assert_eq!(Value::from(true), Value::Bool(true));
assert_eq!(
Value::from(vec![1_i32, 2]),
Value::Array(vec![Value::Number(1.0), Value::Number(2.0)])
);
}
#[test]
fn the_display_form_is_the_canonical_form() {
assert_eq!(Value::Bool(true).to_string(), "bool:true");
assert_eq!(Value::Null.to_string(), "Null");
}
}