use std::collections::BTreeMap;
use ::serde::{Serialize, de::DeserializeOwned};
use base64::{Engine as _, engine::general_purpose::STANDARD};
pub type Int64 = String;
pub type Uint64 = String;
pub type Timestamp = String;
pub type Duration = String;
pub type OptionalValue<T> = Option<Option<T>>;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Bytes(bytes::Bytes);
impl Bytes {
pub fn new(value: impl Into<bytes::Bytes>) -> Self {
Self(value.into())
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.0
}
#[must_use]
pub fn into_shared(self) -> bytes::Bytes {
self.0
}
#[must_use]
pub fn into_vec(self) -> Vec<u8> {
self.0.to_vec()
}
}
impl From<Vec<u8>> for Bytes {
fn from(value: Vec<u8>) -> Self {
Self(value.into())
}
}
impl From<&[u8]> for Bytes {
fn from(value: &[u8]) -> Self {
Self(bytes::Bytes::copy_from_slice(value))
}
}
impl From<bytes::Bytes> for Bytes {
fn from(value: bytes::Bytes) -> Self {
Self(value)
}
}
impl From<Bytes> for bytes::Bytes {
fn from(value: Bytes) -> Self {
value.0
}
}
impl From<Bytes> for Vec<u8> {
fn from(value: Bytes) -> Self {
value.into_vec()
}
}
impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl std::ops::Deref for Bytes {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl Serialize for Bytes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
serializer.serialize_str(&STANDARD.encode(self.as_slice()))
}
}
impl<'de> ::serde::Deserialize<'de> for Bytes {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
let encoded = <String as ::serde::Deserialize>::deserialize(deserializer)?;
STANDARD
.decode(encoded)
.map(Self::from)
.map_err(|_| ::serde::de::Error::custom("bytes must be canonical padded base64"))
}
}
#[derive(Clone, Debug, PartialEq, Serialize, ::serde::Deserialize)]
pub struct UnknownDomainError {
pub code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub payload: Option<serde_json::Value>,
#[serde(default, flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
pub fn encode_portable_json<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
let value = serde_json::to_value(value)?;
validate_portable_json_value(&value).map_err(portable_json_error)?;
serde_json::to_string(&value)
}
pub fn decode_portable_json<T: DeserializeOwned>(wire: &str) -> Result<T, serde_json::Error> {
let value: serde_json::Value = serde_json::from_str(wire)?;
validate_portable_json_value(&value).map_err(portable_json_error)?;
serde_json::from_value(value)
}
pub fn validate_portable_json_value(value: &serde_json::Value) -> Result<(), String> {
match value {
serde_json::Value::Number(number) => {
let safe = number.as_i64().is_some_and(|value| {
(-9_007_199_254_740_991..=9_007_199_254_740_991).contains(&value)
}) || number
.as_u64()
.is_some_and(|value| value <= 9_007_199_254_740_991)
|| (number.is_f64()
&& number.as_f64().is_some_and(|value| {
value.is_finite()
&& (value.abs() <= 9_007_199_254_740_991.0 || value.fract() != 0.0)
}));
if !safe {
return Err("wire JSON contains an unsafe number".to_owned());
}
}
serde_json::Value::Array(values) => {
for value in values {
validate_portable_json_value(value)?;
}
}
serde_json::Value::Object(values) => {
for value in values.values() {
validate_portable_json_value(value)?;
}
}
serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::String(_) => {}
}
Ok(())
}
fn portable_json_error(detail: String) -> serde_json::Error {
serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidData, detail))
}
pub mod serde {
pub fn deserialize_required<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: ::serde::Deserializer<'de>,
T: ::serde::Deserialize<'de>,
{
<T as ::serde::Deserialize>::deserialize(deserializer)
}
#[allow(clippy::option_option)]
pub fn deserialize_optional_value<'de, D, T>(
deserializer: D,
) -> Result<Option<Option<T>>, D::Error>
where
D: ::serde::Deserializer<'de>,
T: ::serde::Deserialize<'de>,
{
Ok(Some(<Option<T> as ::serde::Deserialize>::deserialize(
deserializer,
)?))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bytes_preserve_canonical_padded_base64_wire_behavior() {
for length in 0..=258 {
let value = Bytes::from(
(0..length)
.map(|index| u8::try_from(index % 256).unwrap())
.collect::<Vec<_>>(),
);
let wire = serde_json::to_string(&value).unwrap();
assert_eq!(serde_json::from_str::<Bytes>(&wire).unwrap(), value);
}
for wire in [r#""not base64""#, r#""AQI""#, r#""AQJ=""#] {
assert!(serde_json::from_str::<Bytes>(wire).is_err());
}
}
#[test]
fn shared_bytes_round_trip_without_copying() {
let source = bytes::Bytes::from_static(b"shared");
let contract = Bytes::from(source.clone());
assert_eq!(contract.as_slice().as_ptr(), source.as_ptr());
let restored = contract.into_shared();
assert_eq!(restored.as_ptr(), source.as_ptr());
}
#[test]
fn portable_json_rejects_unsafe_integer_values() {
let error = decode_portable_json::<serde_json::Value>("9007199254740992").unwrap_err();
assert!(error.to_string().contains("unsafe number"));
}
}