use core::fmt;
use schemars::JsonSchema;
use core::ops::Deref;
use base64::engine::{Engine, GeneralPurpose};
use serde::{de::{self, DeserializeOwned}, ser, Deserialize, Deserializer, Serialize};
use crate::AuthError;
#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)]
#[cfg_attr(feature = "substrate", derive(
saa_schema::scale::Encode,
saa_schema::scale::Decode
))]
#[cfg_attr(feature = "solana", derive(
saa_schema::borsh::BorshSerialize,
saa_schema::borsh::BorshDeserialize
))]
#[cfg_attr(all(feature = "std", feature="substrate"), derive(
saa_schema::scale_info::TypeInfo)
)]
pub struct Binary(#[schemars(with = "String")] pub Vec<u8>);
impl Binary {
const B64_ENGINE: GeneralPurpose = GeneralPurpose::new(
&base64::alphabet::STANDARD,
base64::engine::GeneralPurposeConfig::new()
.with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
);
pub fn from_base64(encoded: &str) -> Result<Self, AuthError> {
Self::B64_ENGINE
.decode(encoded.as_bytes())
.map(Binary::from)
.map_err(|_| AuthError::generic("invalid base64"))
}
pub fn to_base64(&self) -> String {
Self::B64_ENGINE.encode(self.0.as_slice())
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
pub fn to_array<const LENGTH: usize>(&self) -> Result<[u8; LENGTH], AuthError> {
if self.len() != LENGTH {
return Err(AuthError::InvalidLength(LENGTH as u16, self.len() as u16));
}
let mut out: [u8; LENGTH] = [0; LENGTH];
out.copy_from_slice(&self.0);
Ok(out)
}
}
impl fmt::Display for Binary {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_base64())
}
}
impl fmt::Debug for Binary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Binary(")?;
for byte in self.0.iter() {
write!(f, "{byte:02x}")?;
}
write!(f, ")")?;
Ok(())
}
}
impl Deref for Binary {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl AsRef<[u8]> for Binary {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl From<&[u8]> for Binary {
fn from(binary: &[u8]) -> Self {
Self(binary.to_vec())
}
}
impl<const LENGTH: usize> From<&[u8; LENGTH]> for Binary {
fn from(source: &[u8; LENGTH]) -> Self {
Self(source.to_vec())
}
}
impl<const LENGTH: usize> From<[u8; LENGTH]> for Binary {
fn from(source: [u8; LENGTH]) -> Self {
Self(source.into())
}
}
impl From<Vec<u8>> for Binary {
fn from(vec: Vec<u8>) -> Self {
Self(vec)
}
}
impl From<Binary> for Vec<u8> {
fn from(original: Binary) -> Vec<u8> {
original.0
}
}
impl PartialEq<Vec<u8>> for Binary {
fn eq(&self, rhs: &Vec<u8>) -> bool {
self.0 == *rhs
}
}
impl PartialEq<Binary> for Vec<u8> {
fn eq(&self, rhs: &Binary) -> bool {
*self == rhs.0
}
}
impl PartialEq<&[u8]> for Binary {
fn eq(&self, rhs: &&[u8]) -> bool {
self.as_slice() == *rhs
}
}
impl PartialEq<Binary> for &[u8] {
fn eq(&self, rhs: &Binary) -> bool {
*self == rhs.as_slice()
}
}
impl<const LENGTH: usize> PartialEq<&[u8; LENGTH]> for Binary {
fn eq(&self, rhs: &&[u8; LENGTH]) -> bool {
self.as_slice() == rhs.as_slice()
}
}
impl<const LENGTH: usize> PartialEq<Binary> for &[u8; LENGTH] {
fn eq(&self, rhs: &Binary) -> bool {
self.as_slice() == rhs.as_slice()
}
}
impl<const LENGTH: usize> PartialEq<[u8; LENGTH]> for Binary {
fn eq(&self, rhs: &[u8; LENGTH]) -> bool {
self.as_slice() == rhs.as_slice()
}
}
impl<const LENGTH: usize> PartialEq<Binary> for [u8; LENGTH] {
fn eq(&self, rhs: &Binary) -> bool {
self.as_slice() == rhs.as_slice()
}
}
impl Serialize for Binary {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_base64())
} else {
panic!("Binary is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
}
}
}
impl<'de> Deserialize<'de> for Binary {
fn deserialize<D>(deserializer: D) -> Result<Binary, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
deserializer.deserialize_str(Base64Visitor)
} else {
panic!("Binary is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
}
}
}
struct Base64Visitor;
impl<'de> de::Visitor<'de> for Base64Visitor {
type Value = Binary;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("valid base64 encoded string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match Binary::from_base64(v) {
Ok(binary) => Ok(binary),
Err(_) => Err(E::custom(format!("invalid base64: {v}"))),
}
}
}
#[cfg(feature = "cosmwasm")]
impl From<Binary> for cosmwasm_std::Binary {
fn from(binary: Binary) -> Self {
cosmwasm_std::Binary(binary.0)
}
}
#[cfg(feature = "cosmwasm")]
impl Into<Binary> for cosmwasm_std::Binary {
fn into(self) -> Binary {
Binary(self.0)
}
}
pub fn to_json_binary<T>(data: &T) -> Result<Binary, AuthError>
where
T: Serialize + ?Sized,
{
serde_json_wasm::to_vec(data).map_err(|e| AuthError::generic(e.to_string())).map(Binary)
}
pub fn from_json<T: DeserializeOwned>(value: impl AsRef<[u8]>) -> Result<T, AuthError> {
serde_json_wasm::from_slice(value.as_ref())
.map_err(|e| AuthError::generic(e.to_string()))
}