#![doc = include_str!("../README.md")]
mod encode;
mod md5;
mod sha1;
mod sha256;
mod sha512;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Algorithm {
#[default]
Sha1,
Sha256,
Sha224,
Sha512,
Sha384,
Md5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Encoding {
#[default]
Hex,
Base64,
Raw,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<Value>),
Set(Vec<Value>),
Map(Vec<(String, Value)>),
Object(Vec<(String, Value)>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Options {
pub exclude_values: bool,
pub unordered_arrays: bool,
pub unordered_sets: bool,
pub unordered_objects: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
exclude_values: false,
unordered_arrays: false,
unordered_sets: true,
unordered_objects: true,
}
}
}
impl Options {
#[must_use]
pub fn keys() -> Self {
Self {
exclude_values: true,
..Self::default()
}
}
#[must_use]
pub fn unordered_arrays(self) -> Self {
Self {
unordered_arrays: true,
..self
}
}
}
impl From<Options> for encode::WriteOptions {
fn from(options: Options) -> Self {
Self {
exclude_values: options.exclude_values,
unordered_arrays: options.unordered_arrays,
unordered_sets: options.unordered_sets,
unordered_objects: options.unordered_objects,
}
}
}
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 {
canonical_with(value, &Options::default())
}
#[must_use]
pub fn canonical_with(value: &Value, options: &Options) -> String {
encode::canonical(value, &(*options).into())
}
#[must_use]
pub fn hash(value: &Value) -> String {
hash_with(value, Algorithm::Sha1)
}
#[must_use]
pub fn hash_with(value: &Value, algorithm: Algorithm) -> String {
digest(&canonical(value), algorithm)
}
#[must_use]
pub fn hash_with_options(value: &Value, options: Options) -> String {
hash_with_options_and(value, options, Algorithm::Sha1)
}
#[must_use]
pub fn hash_with_options_and(value: &Value, options: Options, algorithm: Algorithm) -> String {
digest(&canonical_with(value, &options), algorithm)
}
#[must_use]
pub fn hash_with_encoding(value: &Value, algorithm: Algorithm, encoding: Encoding) -> String {
hash_with_options_encoding(value, Options::default(), algorithm, encoding)
}
#[must_use]
pub fn hash_with_options_encoding(
value: &Value,
options: Options,
algorithm: Algorithm,
encoding: Encoding,
) -> String {
encode_digest(
&raw_digest(canonical_with(value, &options).as_bytes(), algorithm),
encoding,
)
}
#[must_use]
pub fn raw_hash_with(value: &Value, algorithm: Algorithm) -> Vec<u8> {
raw_digest(canonical(value).as_bytes(), algorithm)
}
#[must_use]
pub fn raw_hash_with_options(value: &Value, options: Options, algorithm: Algorithm) -> Vec<u8> {
raw_digest(canonical_with(value, &options).as_bytes(), algorithm)
}
fn digest(canonical: &str, algorithm: Algorithm) -> String {
encode_digest(&raw_digest(canonical.as_bytes(), algorithm), Encoding::Hex)
}
fn raw_digest(data: &[u8], algorithm: Algorithm) -> Vec<u8> {
match algorithm {
Algorithm::Sha1 => sha1::digest(data).to_vec(),
Algorithm::Sha256 => sha256::digest(data).to_vec(),
Algorithm::Sha224 => sha256::digest224(data).to_vec(),
Algorithm::Sha512 => sha512::digest(data).to_vec(),
Algorithm::Sha384 => sha512::digest384(data).to_vec(),
Algorithm::Md5 => md5::digest(data).to_vec(),
}
}
fn encode_digest(bytes: &[u8], encoding: Encoding) -> String {
match encoding {
Encoding::Hex => {
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
out.push(char::from_digit(u32::from(byte >> 4), 16).expect("nibble"));
out.push(char::from_digit(u32::from(byte & 0x0f), 16).expect("nibble"));
}
out
}
Encoding::Base64 => base64(bytes),
Encoding::Raw => bytes.iter().map(|b| char::from(*b)).collect(),
}
}
fn base64(bytes: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
for chunk in bytes.chunks(3) {
let b0 = u32::from(chunk[0]);
let b1 = chunk.get(1).copied().map_or(0, u32::from);
let b2 = chunk.get(2).copied().map_or(0, u32::from);
let triple = (b0 << 16) | (b1 << 8) | b2;
out.push(char::from(ALPHABET[(triple >> 18) as usize & 0x3f]));
out.push(char::from(ALPHABET[(triple >> 12) as usize & 0x3f]));
out.push(if chunk.len() > 1 {
char::from(ALPHABET[(triple >> 6) as usize & 0x3f])
} else {
'='
});
out.push(if chunk.len() > 2 {
char::from(ALPHABET[triple as usize & 0x3f])
} else {
'='
});
}
out
}
#[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");
}
}