use rapidhash::v3::{RapidSecrets, rapidhash_v3_seeded};
use zeroize::Zeroize;
#[cfg(feature = "hashstrings")]
pub enum Sensitivity {
CaseInsensitive,
CaseSensitive,
}
#[cfg(feature = "hashstrings")]
pub struct Hashstring {
value: u64,
seed: u64,
sensitivity: Sensitivity,
}
#[cfg(feature = "hashstrings")]
impl Hashstring {
pub fn new(s: &str, seed: u64, sensitivity: Sensitivity) -> Self {
let rapid_secrets = RapidSecrets::seed_cpp(seed);
let value = match sensitivity {
Sensitivity::CaseInsensitive => {
let mut lowercase_string = s.to_lowercase();
let hash = rapidhash_v3_seeded(lowercase_string.as_bytes(), &rapid_secrets);
Zeroize::zeroize(&mut lowercase_string);
hash
}
Sensitivity::CaseSensitive => rapidhash_v3_seeded(s.as_bytes(), &rapid_secrets),
};
Self {
value,
seed,
sensitivity,
}
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub fn get_raw_value(&self) -> u64 {
self.value
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub fn new_from_raw_value(value: u64, seed: u64, sensitivity: Sensitivity) -> Self {
Self {
value,
seed,
sensitivity,
}
}
}
#[cfg(feature = "hashstrings")]
impl PartialEq<&str> for Hashstring {
fn eq(&self, other: &&str) -> bool {
let seed = RapidSecrets::seed_cpp(self.seed);
let other_value = match self.sensitivity {
Sensitivity::CaseInsensitive => {
rapidhash_v3_seeded(other.to_lowercase().as_bytes(), &seed)
}
Sensitivity::CaseSensitive => rapidhash_v3_seeded(other.as_bytes(), &seed),
};
self.value == other_value
}
}
#[cfg(feature = "hashstrings")]
pub struct Hashbytes {
value: u64,
seed: u64,
}
#[cfg(feature = "hashstrings")]
impl Hashbytes {
pub fn new(bytes: &[u8], seed: u64) -> Self {
let rapid_secrets = RapidSecrets::seed_cpp(seed);
let value = rapidhash_v3_seeded(bytes, &rapid_secrets);
Self { value, seed }
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub fn get_raw_value(&self) -> u64 {
self.value
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub fn new_from_raw_value(value: u64, seed: u64) -> Self {
Self { value, seed }
}
}
#[cfg(feature = "hashstrings")]
impl PartialEq<&[u8]> for Hashbytes {
fn eq(&self, other: &&[u8]) -> bool {
let rapid_secrets = RapidSecrets::seed_cpp(self.seed);
let other_value = rapidhash_v3_seeded(other, &rapid_secrets);
self.value == other_value
}
}
#[cfg(test)]
mod tests {
use super::*;
const A_STRING: &str = "A stringš¶";
const A_LOWERCASE_STRING: &str = "a stringš¶";
const A_STRING_BYTES: &[u8] = A_STRING.as_bytes();
const A_LOWERCASE_STRING_BYTES: &[u8] = A_LOWERCASE_STRING.as_bytes();
const SEED: u64 = 0x2357_bd11_1317_1d1f;
#[test]
fn test_hashstrings() {
let case_sensitive_hashstring = Hashstring::new(A_STRING, SEED, Sensitivity::CaseSensitive);
let case_insensitive_hashstring =
Hashstring::new(A_STRING, SEED, Sensitivity::CaseInsensitive);
assert!(case_sensitive_hashstring == A_STRING);
assert!(case_sensitive_hashstring != A_LOWERCASE_STRING);
assert!(case_insensitive_hashstring == A_STRING);
assert!(case_insensitive_hashstring == A_LOWERCASE_STRING);
}
#[test]
fn test_hashbytes() {
let hashbytes = Hashbytes::new(A_STRING_BYTES, SEED);
assert!(hashbytes == A_STRING_BYTES);
assert!(hashbytes != A_LOWERCASE_STRING_BYTES);
}
#[test]
fn test_empty_hashstrings() {
let empty = Hashstring::new("", SEED, Sensitivity::CaseSensitive);
assert!(empty == "");
assert!(empty != " ");
}
#[test]
fn test_empty_hashbytes() {
let empty = Hashbytes::new(&[], SEED);
assert!(empty == &[][..]);
assert!(empty != &[0][..]);
}
#[test]
fn ensure_hashstring_bytes_has_not_changed() {
#[allow(
clippy::unreadable_literal,
reason = "Created using a random seed, has no special meaning outside of this crate."
)]
let value = 856128386601824369u64;
let hashed_string_lower =
Hashstring::new_from_raw_value(value, SEED, Sensitivity::CaseSensitive);
let hashed_string_lower_ci =
Hashstring::new_from_raw_value(value, SEED, Sensitivity::CaseInsensitive);
let hashed_bytes = Hashbytes::new_from_raw_value(value, SEED);
assert!(hashed_string_lower != A_STRING);
assert!(hashed_string_lower == A_LOWERCASE_STRING);
assert!(hashed_string_lower_ci == A_STRING);
assert!(hashed_string_lower_ci == A_LOWERCASE_STRING);
assert!(hashed_bytes != A_STRING_BYTES);
assert!(hashed_bytes == A_LOWERCASE_STRING_BYTES);
}
}