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> {
79 Ok(VarCharMaxLength(proto.value))
80 }
81}
82
83#[cfg(any(test, feature = "proptest"))]
84impl Arbitrary for VarCharMaxLength {
85 type Parameters = ();
86 type Strategy = BoxedStrategy<VarCharMaxLength>;
87
88 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
89 proptest::arbitrary::any::<u32>()
90 .prop_map(|len| VarCharMaxLength(len % 300))
94 .boxed()
95 }
96}
97
98#[derive(Debug, Clone)]
101pub struct InvalidVarCharMaxLengthError;
102
103impl fmt::Display for InvalidVarCharMaxLengthError {
104 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105 write!(
106 f,
107 "length for type character varying must be between 1 and {}",
108 MAX_MAX_LENGTH
109 )
110 }
111}
112
113impl Error for InvalidVarCharMaxLengthError {}
114
115pub fn format_str(
116 s: &str,
117 length: Option<VarCharMaxLength>,
118 fail_on_len: bool,
119) -> Result<&str, anyhow::Error> {
120 Ok(match length {
121 Some(l) => {
125 let l = usize::cast_from(l.into_u32());
126 match s.char_indices().nth(l) {
127 None => s,
128 Some((idx, _)) => {
129 if !fail_on_len || s[idx..].chars().all(|c| c.is_ascii_whitespace()) {
130 &s[..idx]
131 } else {
132 bail!("{} exceeds maximum length of {}", s, l)
133 }
134 }
135 }
136 }
137 None => s,
138 })
139}
140
141#[cfg(test)]
142mod tests {
143 use mz_ore::assert_ok;
144 use mz_proto::protobuf_roundtrip;
145 use proptest::prelude::*;
146
147 use super::*;
148
149 proptest! {
150 #[mz_ore::test]
151 fn var_char_max_length_protobuf_roundtrip(expect in any::<VarCharMaxLength>()) {
152 let actual = protobuf_roundtrip::<_, ProtoVarCharMaxLength>(&expect);
153 assert_ok!(actual);
154 assert_eq!(actual.unwrap(), expect);
155 }
156 }
157}