senax-common 0.4.8

Senax common library
Documentation
use anyhow::Result;
use base64::alphabet;
use base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig};
use base64::{Engine as _, engine::general_purpose};
use once_cell::sync::OnceCell;
use schemars::JsonSchema;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use serde::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::{self, Display};
use std::str::FromStr;
use std::sync::RwLock;

pub static FILES: OnceCell<RwLock<HashMap<String, Cow<[u8]>>>> = OnceCell::new();

#[derive(Serialize, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
pub struct Blob(#[schemars(schema_with = "crate::types::blob::schema")] pub Vec<u8>);

pub(crate) fn schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema {
    let schema = SchemaObject {
        instance_type: Some(InstanceType::String.into()),
        ..Default::default()
    };
    Schema::Object(schema)
}

impl<'de> serde::Deserialize<'de> for Blob {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use serde::de::Visitor;

        struct IdVisitor;

        impl Visitor<'_> for IdVisitor {
            type Value = Blob;

            #[inline]
            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str("an file path or base64")
            }

            #[inline]
            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                let files = FILES.get().map(|files| files.read().unwrap());
                if let Some(file) =
                    files.and_then(|files| files.get(v).map(|v| v.as_ref().to_owned()))
                {
                    return Ok(Blob(file));
                }
                if let Ok(decode) = DECODE.decode(v) {
                    return Ok(Blob(decode));
                }
                Err(serde::de::Error::custom(format_args!(
                    "file not found: {}",
                    v
                )))
            }
        }
        deserializer.deserialize_bytes(IdVisitor)
    }
}

impl From<Blob> for Vec<u8> {
    fn from(v: Blob) -> Self {
        v.0
    }
}

impl From<Vec<u8>> for Blob {
    fn from(v: Vec<u8>) -> Self {
        Blob(v)
    }
}

impl From<&[u8]> for Blob {
    fn from(v: &[u8]) -> Self {
        Blob(v.to_vec())
    }
}

impl FromStr for Blob {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Blob(DECODE.decode(s)?))
    }
}
impl Display for Blob {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&general_purpose::STANDARD.encode(&self.0))
    }
}

pub trait BlobToApi {
    fn to_str(&self) -> String;
}
impl BlobToApi for std::sync::Arc<Vec<u8>> {
    fn to_str(&self) -> String {
        general_purpose::STANDARD_NO_PAD.encode(self.as_ref())
    }
}
pub trait ApiToBlob {
    fn to_vec(&self) -> Vec<u8>;
}
impl ApiToBlob for String {
    fn to_vec(&self) -> Vec<u8> {
        DECODE.decode(self).unwrap_or_default()
    }
}
pub const DECODE: GeneralPurpose = GeneralPurpose::new(
    &alphabet::STANDARD,
    GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        assert_eq!(
            "SGVsbG8sIOOCu+OCq+OCpA==".to_string().to_vec(),
            "Hello, セカイ".as_bytes()
        );
        assert_eq!(
            "SGVsbG8sIOOCu+OCq+OCpA".to_string().to_vec(),
            "Hello, セカイ".as_bytes()
        );
    }
}