1use std::error::Error;
11use std::fmt;
12
13use anyhow::bail;
14use mz_ore::cast::CastFrom;
15use mz_proto::{RustType, TryFromProtoError};
16#[cfg(any(test, feature = "proptest"))]
17use proptest::arbitrary::Arbitrary;
18#[cfg(any(test, feature = "proptest"))]
19use proptest::strategy::{BoxedStrategy, Strategy};
20use serde::{Deserialize, Serialize};
21
22include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.varchar.rs"));
23
24pub const MAX_MAX_LENGTH: u32 = 10_485_760;
26
27#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
32pub struct VarChar<S: AsRef<str>>(pub S);
33
34#[derive(
40 Debug,
41 Clone,
42 Copy,
43 Eq,
44 PartialEq,
45 Ord,
46 PartialOrd,
47 Hash,
48 Serialize,
49 Deserialize
50)]
51pub struct VarCharMaxLength(pub(crate) u32);
52
53impl VarCharMaxLength {
54 pub fn into_u32(self) -> u32 {
56 self.0
57 }
58}
59
60impl TryFrom<i64> for VarCharMaxLength {
61 type Error = InvalidVarCharMaxLengthError;
62
63 fn try_from(max_length: i64) -> Result<Self, Self::Error> {
64 match u32::try_from(max_length) {
65 Ok(max_length) if max_length > 0 && max_length < MAX_MAX_LENGTH => {
66 Ok(VarCharMaxLength(max_length))
67 }
68 _ => Err(InvalidVarCharMaxLengthError),
69 }
70 }
71}
72
73impl RustType<ProtoVarCharMaxLength> for VarCharMaxLength {
74 fn into_proto(&self) -> ProtoVarCharMaxLength {
75 ProtoVarCharMaxLength { value: self.0 }
76 }
77
78 fn from_proto(proto: ProtoVarCharMaxLength) -> Result<Self, TryFromProtoError> {
81 VarCharMaxLength::try_from(i64::from(proto.value)).map_err(|e| {
82 TryFromProtoError::InvalidFieldError(format!(
83 "ProtoVarCharMaxLength::value {}: {e}",
84 proto.value
85 ))
86 })
87 }
88}
89
90#[cfg(any(test, feature = "proptest"))]
91impl Arbitrary for VarCharMaxLength {
92 type Parameters = ();
93 type Strategy = BoxedStrategy<VarCharMaxLength>;
94
95 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
96 (1..300u32).prop_map(VarCharMaxLength).boxed()
102 }
103}
104
105#[derive(Debug, Clone)]
108pub struct InvalidVarCharMaxLengthError;
109
110impl fmt::Display for InvalidVarCharMaxLengthError {
111 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112 write!(
113 f,
114 "length for type character varying must be between 1 and {}",
115 MAX_MAX_LENGTH
116 )
117 }
118}
119
120impl Error for InvalidVarCharMaxLengthError {}
121
122pub fn format_str(
123 s: &str,
124 length: Option<VarCharMaxLength>,
125 fail_on_len: bool,
126) -> Result<&str, anyhow::Error> {
127 Ok(match length {
128 Some(l) => {
132 let l = usize::cast_from(l.into_u32());
133 match s.char_indices().nth(l) {
134 None => s,
135 Some((idx, _)) => {
136 if !fail_on_len || s[idx..].chars().all(|c| c.is_ascii_whitespace()) {
137 &s[..idx]
138 } else {
139 bail!("{} exceeds maximum length of {}", s, l)
140 }
141 }
142 }
143 }
144 None => s,
145 })
146}
147
148#[cfg(test)]
149mod tests {
150 use mz_ore::assert_ok;
151 use mz_proto::protobuf_roundtrip;
152 use proptest::prelude::*;
153
154 use super::*;
155
156 proptest! {
157 #[mz_ore::test]
158 fn var_char_max_length_protobuf_roundtrip(expect in any::<VarCharMaxLength>()) {
159 let actual = protobuf_roundtrip::<_, ProtoVarCharMaxLength>(&expect);
160 assert_ok!(actual);
161 assert_eq!(actual.unwrap(), expect);
162 }
163 }
164}