use std::error::Error;
use std::fmt;
use anyhow::bail;
use mz_lowertest::MzReflect;
use mz_ore::cast::CastFrom;
use mz_proto::{RustType, TryFromProtoError};
use proptest::arbitrary::Arbitrary;
use proptest::strategy::{BoxedStrategy, Strategy};
use serde::{Deserialize, Serialize};
include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.varchar.rs"));
pub const MAX_MAX_LENGTH: u32 = 10_485_760;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct VarChar<S: AsRef<str>>(pub S);
#[derive(
Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, MzReflect,
)]
pub struct VarCharMaxLength(pub(crate) u32);
impl VarCharMaxLength {
pub fn into_u32(self) -> u32 {
self.0
}
}
impl TryFrom<i64> for VarCharMaxLength {
type Error = InvalidVarCharMaxLengthError;
fn try_from(max_length: i64) -> Result<Self, Self::Error> {
match u32::try_from(max_length) {
Ok(max_length) if max_length > 0 && max_length < MAX_MAX_LENGTH => {
Ok(VarCharMaxLength(max_length))
}
_ => Err(InvalidVarCharMaxLengthError),
}
}
}
impl RustType<ProtoVarCharMaxLength> for VarCharMaxLength {
fn into_proto(&self) -> ProtoVarCharMaxLength {
ProtoVarCharMaxLength { value: self.0 }
}
fn from_proto(proto: ProtoVarCharMaxLength) -> Result<Self, TryFromProtoError> {
Ok(VarCharMaxLength(proto.value))
}
}
impl Arbitrary for VarCharMaxLength {
type Parameters = ();
type Strategy = BoxedStrategy<VarCharMaxLength>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
proptest::arbitrary::any::<u32>()
.prop_map(|len| VarCharMaxLength(len % 300))
.boxed()
}
}
#[derive(Debug, Clone)]
pub struct InvalidVarCharMaxLengthError;
impl fmt::Display for InvalidVarCharMaxLengthError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"length for type character varying must be between 1 and {}",
MAX_MAX_LENGTH
)
}
}
impl Error for InvalidVarCharMaxLengthError {}
pub fn format_str(
s: &str,
length: Option<VarCharMaxLength>,
fail_on_len: bool,
) -> Result<&str, anyhow::Error> {
Ok(match length {
Some(l) => {
let l = usize::cast_from(l.into_u32());
match s.char_indices().nth(l) {
None => s,
Some((idx, _)) => {
if !fail_on_len || s[idx..].chars().all(|c| c.is_ascii_whitespace()) {
&s[..idx]
} else {
bail!("{} exceeds maximum length of {}", s, l)
}
}
}
}
None => s,
})
}
#[cfg(test)]
mod tests {
use mz_ore::assert_ok;
use mz_proto::protobuf_roundtrip;
use proptest::prelude::*;
use super::*;
proptest! {
#[mz_ore::test]
fn var_char_max_length_protobuf_roundtrip(expect in any::<VarCharMaxLength>()) {
let actual = protobuf_roundtrip::<_, ProtoVarCharMaxLength>(&expect);
assert_ok!(actual);
assert_eq!(actual.unwrap(), expect);
}
}
}