use crate::migrate::OnlineSafetyClassification;
pub trait FieldCodec: Send + Sync + 'static {
const ID: &'static str;
type Decoded;
type Encoded;
type Error: std::error::Error + Send + Sync + 'static;
fn encode(value: &Self::Decoded) -> Result<Self::Encoded, Self::Error>;
fn decode(stored: &Self::Encoded) -> Result<Self::Decoded, Self::Error>;
fn classify_transition<Other: FieldCodec>() -> OnlineSafetyClassification;
}
pub(crate) static REGISTRY: phf::Set<&'static str> = phf::phf_set! {};
#[doc(hidden)]
pub fn is_registered(id: &str) -> bool {
REGISTRY.contains(id)
}
#[cfg(test)]
mod tests {
use super::{FieldCodec, is_registered};
use crate::migrate::OnlineSafetyClassification;
use std::fmt;
#[derive(Debug)]
struct Utf8RoundtripError;
impl fmt::Display for Utf8RoundtripError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("utf-8 round-trip failed")
}
}
impl std::error::Error for Utf8RoundtripError {}
struct Utf8Roundtrip;
impl FieldCodec for Utf8Roundtrip {
const ID: &'static str = "_djogi_test_utf8_roundtrip";
type Decoded = String;
type Encoded = Vec<u8>;
type Error = Utf8RoundtripError;
fn encode(value: &Self::Decoded) -> Result<Self::Encoded, Self::Error> {
Ok(value.as_bytes().to_vec())
}
fn decode(stored: &Self::Encoded) -> Result<Self::Decoded, Self::Error> {
std::str::from_utf8(stored)
.map(|s| s.to_owned())
.map_err(|_| Utf8RoundtripError)
}
fn classify_transition<Other: FieldCodec>() -> OnlineSafetyClassification {
if Self::ID == Other::ID {
OnlineSafetyClassification::OnlineSafe
} else {
OnlineSafetyClassification::OfflineOnly
}
}
}
struct OtherTestCodec;
impl FieldCodec for OtherTestCodec {
const ID: &'static str = "_djogi_test_other";
type Decoded = String;
type Encoded = Vec<u8>;
type Error = Utf8RoundtripError;
fn encode(value: &Self::Decoded) -> Result<Self::Encoded, Self::Error> {
Ok(value.as_bytes().to_vec())
}
fn decode(stored: &Self::Encoded) -> Result<Self::Decoded, Self::Error> {
std::str::from_utf8(stored)
.map(|s| s.to_owned())
.map_err(|_| Utf8RoundtripError)
}
fn classify_transition<Other: FieldCodec>() -> OnlineSafetyClassification {
if Self::ID == Other::ID {
OnlineSafetyClassification::OnlineSafe
} else {
OnlineSafetyClassification::ExpandContract
}
}
}
#[test]
fn encode_decode_round_trips() {
let original = String::from("djogi field codec round-trip");
let encoded = Utf8Roundtrip::encode(&original).expect("encode");
let decoded = Utf8Roundtrip::decode(&encoded).expect("decode");
assert_eq!(decoded, original);
}
#[test]
fn registry_does_not_contain_unshipped_codec_id() {
assert!(!is_registered("aes256_gcm_v1"));
}
#[test]
fn registry_does_not_contain_test_codec_id() {
assert!(!is_registered(Utf8Roundtrip::ID));
assert!(!is_registered(OtherTestCodec::ID));
}
#[test]
fn classify_transition_to_self_is_online_safe() {
let classification = Utf8Roundtrip::classify_transition::<Utf8Roundtrip>();
assert_eq!(classification, OnlineSafetyClassification::OnlineSafe);
}
#[test]
fn classify_transition_across_codecs_is_callable() {
let to_other = Utf8Roundtrip::classify_transition::<OtherTestCodec>();
assert_eq!(to_other, OnlineSafetyClassification::OfflineOnly);
let from_other = OtherTestCodec::classify_transition::<Utf8Roundtrip>();
assert_eq!(from_other, OnlineSafetyClassification::ExpandContract);
}
}