use std::borrow::Cow;
use std::collections::BTreeMap;
use std::error::Error;
use std::fmt;
use std::num::FpCategory;
use std::str::FromStr;
use std::sync::LazyLock;
use chrono::offset::{Offset, TimeZone};
use chrono::{DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
use dec::OrderedDecimal;
use mz_lowertest::MzReflect;
use mz_ore::cast::ReinterpretCast;
use mz_ore::error::ErrorExt;
use mz_ore::fmt::FormatBuffer;
use mz_ore::lex::LexBuf;
use mz_ore::str::StrExt;
use mz_pgtz::timezone::{Timezone, TimezoneSpec};
use mz_proto::{ProtoType, RustType, TryFromProtoError};
use num_traits::Float as NumFloat;
use proptest_derive::Arbitrary;
use regex::bytes::Regex;
use ryu::Float as RyuFloat;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::adt::array::ArrayDimension;
use crate::adt::date::Date;
use crate::adt::datetime::{self, DateTimeField, ParsedDateTime};
use crate::adt::interval::Interval;
use crate::adt::jsonb::{Jsonb, JsonbRef};
use crate::adt::mz_acl_item::{AclItem, MzAclItem};
use crate::adt::numeric::{self, Numeric, NUMERIC_DATUM_MAX_PRECISION};
use crate::adt::pg_legacy_name::NAME_MAX_BYTES;
use crate::adt::range::{Range, RangeBound, RangeInner};
use crate::adt::timestamp::CheckedTimestamp;
include!(concat!(env!("OUT_DIR"), "/mz_repr.strconv.rs"));
macro_rules! bail {
($($arg:tt)*) => { return Err(format!($($arg)*)) };
}
#[derive(Debug)]
pub enum Nestable {
Yes,
MayNeedEscaping,
}
pub fn parse_bool(s: &str) -> Result<bool, ParseError> {
match s.trim().to_lowercase().as_str() {
"t" | "tr" | "tru" | "true" | "y" | "ye" | "yes" | "on" | "1" => Ok(true),
"f" | "fa" | "fal" | "fals" | "false" | "n" | "no" | "of" | "off" | "0" => Ok(false),
_ => Err(ParseError::invalid_input_syntax("boolean", s)),
}
}
pub fn format_bool_static(b: bool) -> &'static str {
match b {
true => "t",
false => "f",
}
}
pub fn format_bool<F>(buf: &mut F, b: bool) -> Nestable
where
F: FormatBuffer,
{
buf.write_str(format_bool_static(b));
Nestable::Yes
}
pub fn parse_int16(s: &str) -> Result<i16, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("smallint", s).with_details(e))
}
pub fn format_int16<F>(buf: &mut F, i: i16) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", i);
Nestable::Yes
}
pub fn parse_int32(s: &str) -> Result<i32, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("integer", s).with_details(e))
}
pub fn format_int32<F>(buf: &mut F, i: i32) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", i);
Nestable::Yes
}
pub fn parse_int64(s: &str) -> Result<i64, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("bigint", s).with_details(e))
}
pub fn format_int64<F>(buf: &mut F, i: i64) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", i);
Nestable::Yes
}
pub fn parse_uint16(s: &str) -> Result<u16, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("uint2", s).with_details(e))
}
pub fn format_uint16<F>(buf: &mut F, u: u16) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", u);
Nestable::Yes
}
pub fn parse_uint32(s: &str) -> Result<u32, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("uint4", s).with_details(e))
}
pub fn format_uint32<F>(buf: &mut F, u: u32) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", u);
Nestable::Yes
}
pub fn parse_uint64(s: &str) -> Result<u64, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("uint8", s).with_details(e))
}
pub fn format_uint64<F>(buf: &mut F, u: u64) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", u);
Nestable::Yes
}
pub fn parse_mz_timestamp(s: &str) -> Result<crate::Timestamp, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("mz_timestamp", s).with_details(e))
}
pub fn format_mz_timestamp<F>(buf: &mut F, u: crate::Timestamp) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", u);
Nestable::Yes
}
pub fn parse_oid(s: &str) -> Result<u32, ParseError> {
let oid: i32 = s
.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("oid", s).with_details(e))?;
Ok(u32::reinterpret_cast(oid))
}
fn parse_float<Fl>(type_name: &'static str, s: &str) -> Result<Fl, ParseError>
where
Fl: NumFloat + FromStr,
{
static ZERO_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#"(?i-u)^[-+]?(0+(\.0*)?|\.0+)(e|$)"#).unwrap());
static INF_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new("(?i-u)^[-+]?inf").unwrap());
let buf = s.trim();
let f: Fl = buf
.parse()
.map_err(|_| ParseError::invalid_input_syntax(type_name, s))?;
match f.classify() {
FpCategory::Infinite if !INF_RE.is_match(buf.as_bytes()) => {
Err(ParseError::out_of_range(type_name, s))
}
FpCategory::Zero if !ZERO_RE.is_match(buf.as_bytes()) => {
Err(ParseError::out_of_range(type_name, s))
}
_ => Ok(f),
}
}
fn format_float<F, Fl>(buf: &mut F, f: Fl) -> Nestable
where
F: FormatBuffer,
Fl: NumFloat + RyuFloat,
{
match f.classify() {
FpCategory::Infinite if f.is_sign_negative() => buf.write_str("-Infinity"),
FpCategory::Infinite => buf.write_str("Infinity"),
FpCategory::Nan => buf.write_str("NaN"),
FpCategory::Zero if f.is_sign_negative() => buf.write_str("-0"),
_ => {
debug_assert!(f.is_finite());
let mut ryu_buf = ryu::Buffer::new();
let mut s = ryu_buf.format_finite(f);
if let Some(trimmed) = s.strip_suffix(".0") {
s = trimmed;
}
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
buf.write_char(ch);
if ch == 'e' && chars.peek() != Some(&'-') {
buf.write_char('+');
}
}
}
}
Nestable::Yes
}
pub fn parse_float32(s: &str) -> Result<f32, ParseError> {
parse_float("real", s)
}
pub fn format_float32<F>(buf: &mut F, f: f32) -> Nestable
where
F: FormatBuffer,
{
format_float(buf, f)
}
pub fn parse_float64(s: &str) -> Result<f64, ParseError> {
parse_float("double precision", s)
}
pub fn format_float64<F>(buf: &mut F, f: f64) -> Nestable
where
F: FormatBuffer,
{
format_float(buf, f)
}
fn parse_timestamp_string(s: &str) -> Result<(NaiveDate, NaiveTime, Timezone), String> {
if s.is_empty() {
return Err("timestamp string is empty".into());
}
if s == "epoch" {
return Ok((
NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(),
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
Default::default(),
));
}
let (ts_string, tz_string) = datetime::split_timestamp_string(s);
let pdt = ParsedDateTime::build_parsed_datetime_timestamp(ts_string)?;
let d: NaiveDate = pdt.compute_date()?;
let t: NaiveTime = pdt.compute_time()?;
let offset = if tz_string.is_empty() {
Default::default()
} else {
Timezone::parse(tz_string, TimezoneSpec::Iso)?
};
Ok((d, t, offset))
}
pub fn parse_date(s: &str) -> Result<Date, ParseError> {
match parse_timestamp_string(s) {
Ok((date, _, _)) => Date::try_from(date).map_err(|_| ParseError::out_of_range("date", s)),
Err(e) => Err(ParseError::invalid_input_syntax("date", s).with_details(e)),
}
}
pub fn format_date<F>(buf: &mut F, d: Date) -> Nestable
where
F: FormatBuffer,
{
let d: NaiveDate = d.into();
let (year_ad, year) = d.year_ce();
write!(buf, "{:04}-{}", year, d.format("%m-%d"));
if !year_ad {
write!(buf, " BC");
}
Nestable::Yes
}
pub fn parse_time(s: &str) -> Result<NaiveTime, ParseError> {
ParsedDateTime::build_parsed_datetime_time(s)
.and_then(|pdt| pdt.compute_time())
.map_err(|e| ParseError::invalid_input_syntax("time", s).with_details(e))
}
pub fn format_time<F>(buf: &mut F, t: NaiveTime) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", t.format("%H:%M:%S"));
format_nanos_to_micros(buf, t.nanosecond());
Nestable::Yes
}
pub fn parse_timestamp(s: &str) -> Result<CheckedTimestamp<NaiveDateTime>, ParseError> {
match parse_timestamp_string(s) {
Ok((date, time, _)) => CheckedTimestamp::from_timestamplike(date.and_time(time))
.map_err(|_| ParseError::out_of_range("timestamp", s)),
Err(e) => Err(ParseError::invalid_input_syntax("timestamp", s).with_details(e)),
}
}
pub fn format_timestamp<F>(buf: &mut F, ts: &NaiveDateTime) -> Nestable
where
F: FormatBuffer,
{
let (year_ad, year) = ts.year_ce();
write!(buf, "{:04}-{}", year, ts.format("%m-%d %H:%M:%S"));
format_nanos_to_micros(buf, ts.timestamp_subsec_nanos());
if !year_ad {
write!(buf, " BC");
}
Nestable::MayNeedEscaping
}
pub fn parse_timestamptz(s: &str) -> Result<CheckedTimestamp<DateTime<Utc>>, ParseError> {
parse_timestamp_string(s)
.and_then(|(date, time, timezone)| {
use Timezone::*;
let mut dt = date.and_time(time);
let offset = match timezone {
FixedOffset(offset) => offset,
Tz(tz) => match tz.offset_from_local_datetime(&dt).latest() {
Some(offset) => offset.fix(),
None => {
dt += Duration::try_hours(1).unwrap();
tz.offset_from_local_datetime(&dt)
.latest()
.ok_or_else(|| "invalid timezone conversion".to_owned())?
.fix()
}
},
};
Ok(DateTime::from_naive_utc_and_offset(dt - offset, Utc))
})
.map_err(|e| {
ParseError::invalid_input_syntax("timestamp with time zone", s).with_details(e)
})
.and_then(|ts| {
CheckedTimestamp::from_timestamplike(ts)
.map_err(|_| ParseError::out_of_range("timestamp with time zone", s))
})
}
pub fn format_timestamptz<F>(buf: &mut F, ts: &DateTime<Utc>) -> Nestable
where
F: FormatBuffer,
{
let (year_ad, year) = ts.year_ce();
write!(buf, "{:04}-{}", year, ts.format("%m-%d %H:%M:%S"));
format_nanos_to_micros(buf, ts.timestamp_subsec_nanos());
write!(buf, "+00");
if !year_ad {
write!(buf, " BC");
}
Nestable::MayNeedEscaping
}
pub fn parse_interval(s: &str) -> Result<Interval, ParseError> {
parse_interval_w_disambiguator(s, None, DateTimeField::Second)
}
pub fn parse_interval_w_disambiguator(
s: &str,
leading_time_precision: Option<DateTimeField>,
d: DateTimeField,
) -> Result<Interval, ParseError> {
ParsedDateTime::build_parsed_datetime_interval(s, leading_time_precision, d)
.and_then(|pdt| pdt.compute_interval())
.map_err(|e| ParseError::invalid_input_syntax("interval", s).with_details(e))
}
pub fn format_interval<F>(buf: &mut F, iv: Interval) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", iv);
Nestable::MayNeedEscaping
}
pub fn parse_numeric(s: &str) -> Result<OrderedDecimal<Numeric>, ParseError> {
let mut cx = numeric::cx_datum();
let mut n = match cx.parse(s.trim()) {
Ok(n) => n,
Err(..) => {
return Err(ParseError::invalid_input_syntax("numeric", s));
}
};
let cx_status = cx.status();
if (n.is_infinite() && !cx_status.overflow())
|| (n.is_nan() && n.is_negative())
|| n.is_signaling_nan()
{
return Err(ParseError::invalid_input_syntax("numeric", s));
}
let out_of_range = numeric::munge_numeric(&mut n).is_err();
if cx_status.overflow() || cx_status.subnormal() || out_of_range {
Err(ParseError::out_of_range("numeric", s).with_details(format!(
"exceeds maximum precision {}",
NUMERIC_DATUM_MAX_PRECISION
)))
} else {
Ok(OrderedDecimal(n))
}
}
pub fn format_numeric<F>(buf: &mut F, n: &OrderedDecimal<Numeric>) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", n.0.to_standard_notation_string());
Nestable::Yes
}
pub fn format_string<F>(buf: &mut F, s: &str) -> Nestable
where
F: FormatBuffer,
{
buf.write_str(s);
Nestable::MayNeedEscaping
}
pub fn parse_pg_legacy_name(s: &str) -> String {
let mut out = String::new();
let mut len = 0;
for c in s.chars() {
len += c.len_utf8();
if len > NAME_MAX_BYTES {
break;
}
out.push(c);
}
out
}
pub fn parse_bytes(s: &str) -> Result<Vec<u8>, ParseError> {
if let Some(remainder) = s.strip_prefix(r"\x") {
parse_bytes_hex(remainder).map_err(|e| {
ParseError::invalid_input_syntax("bytea", s).with_details(e.to_string_with_causes())
})
} else {
parse_bytes_traditional(s)
}
}
pub fn parse_bytes_hex(s: &str) -> Result<Vec<u8>, ParseHexError> {
let decode_nibble = |b| match b {
b'a'..=b'f' => Ok(b - b'a' + 10),
b'A'..=b'F' => Ok(b - b'A' + 10),
b'0'..=b'9' => Ok(b - b'0'),
_ => Err(ParseHexError::InvalidHexDigit(char::from(b))),
};
let mut buf = vec![];
let mut nibbles = s.as_bytes().iter().copied();
while let Some(n) = nibbles.next() {
if let b' ' | b'\n' | b'\t' | b'\r' = n {
continue;
}
let n = decode_nibble(n)?;
let n2 = match nibbles.next() {
None => return Err(ParseHexError::OddLength),
Some(n2) => decode_nibble(n2)?,
};
buf.push((n << 4) | n2);
}
Ok(buf)
}
pub fn parse_bytes_traditional(s: &str) -> Result<Vec<u8>, ParseError> {
let mut out = Vec::new();
let mut bytes = s.as_bytes().iter().fuse();
while let Some(&b) = bytes.next() {
if b != b'\\' {
out.push(b);
continue;
}
match bytes.next() {
None => {
return Err(ParseError::invalid_input_syntax("bytea", s)
.with_details("ends with escape character"))
}
Some(b'\\') => out.push(b'\\'),
b => match (b, bytes.next(), bytes.next()) {
(Some(d2 @ b'0'..=b'3'), Some(d1 @ b'0'..=b'7'), Some(d0 @ b'0'..=b'7')) => {
out.push(((d2 - b'0') << 6) + ((d1 - b'0') << 3) + (d0 - b'0'));
}
_ => {
return Err(ParseError::invalid_input_syntax("bytea", s)
.with_details("invalid escape sequence"))
}
},
}
}
Ok(out)
}
pub fn format_bytes<F>(buf: &mut F, bytes: &[u8]) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "\\x{}", hex::encode(bytes));
Nestable::MayNeedEscaping
}
pub fn parse_jsonb(s: &str) -> Result<Jsonb, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("jsonb", s).with_details(e))
}
pub fn format_jsonb<F>(buf: &mut F, jsonb: JsonbRef) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", jsonb);
Nestable::MayNeedEscaping
}
pub fn format_jsonb_pretty<F>(buf: &mut F, jsonb: JsonbRef)
where
F: FormatBuffer,
{
write!(buf, "{:#}", jsonb)
}
pub fn parse_uuid(s: &str) -> Result<Uuid, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("uuid", s).with_details(e))
}
pub fn format_uuid<F>(buf: &mut F, uuid: Uuid) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{}", uuid);
Nestable::Yes
}
fn format_nanos_to_micros<F>(buf: &mut F, nanos: u32)
where
F: FormatBuffer,
{
if nanos >= 500 {
let mut micros = nanos / 1000;
let rem = nanos % 1000;
if rem >= 500 {
micros += 1;
}
let mut width = 6;
while micros % 10 == 0 {
width -= 1;
micros /= 10;
}
write!(buf, ".{:0width$}", micros, width = width);
}
}
#[derive(Debug, thiserror::Error)]
enum ArrayParsingError {
#[error("Array value must start with \"{{\"")]
OpeningBraceMissing,
#[error("Specifying array lower bounds is not supported")]
DimsUnsupported,
#[error("{0}")]
Generic(String),
#[error("Unexpected \"{0}\" character.")]
UnexpectedChar(char),
#[error("Multidimensional arrays must have sub-arrays with matching dimensions.")]
NonRectilinearDims,
#[error("Unexpected array element.")]
UnexpectedElement,
#[error("Junk after closing right brace.")]
Junk,
#[error("Unexpected end of input.")]
EarlyTerm,
}
impl From<String> for ArrayParsingError {
fn from(value: String) -> Self {
ArrayParsingError::Generic(value)
}
}
pub fn parse_array<'a, T, E>(
s: &'a str,
make_null: impl FnMut() -> T,
gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
) -> Result<(Vec<T>, Vec<ArrayDimension>), ParseError>
where
E: ToString,
{
parse_array_inner(s, make_null, gen_elem)
.map_err(|details| ParseError::invalid_input_syntax("array", s).with_details(details))
}
fn parse_array_inner<'a, T, E>(
s: &'a str,
mut make_null: impl FnMut() -> T,
mut gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
) -> Result<(Vec<T>, Vec<ArrayDimension>), ArrayParsingError>
where
E: ToString,
{
use ArrayParsingError::*;
#[derive(Clone, Debug, Default)]
struct Dimension {
length: Option<usize>,
staged_element: bool,
committed_element_count: usize,
}
#[derive(Clone, Debug, Default)]
struct ArrayBuilder<'a> {
current_command_char: char,
dimensions: Vec<Dimension>,
current_dim: usize,
sealed: bool,
elements: Vec<Option<Cow<'a, str>>>,
}
impl<'a> ArrayBuilder<'a> {
fn build(
s: &'a str,
) -> Result<(Vec<Option<Cow<'a, str>>>, Vec<ArrayDimension>), ArrayParsingError> {
let buf = &mut LexBuf::new(s);
if buf.consume('[') {
Err(DimsUnsupported)?;
}
buf.take_while(|ch| ch.is_ascii_whitespace());
if !buf.consume('{') {
Err(OpeningBraceMissing)?;
}
let mut dimensions = 1;
loop {
buf.take_while(|ch| ch.is_ascii_whitespace());
if buf.consume('{') {
dimensions += 1;
} else {
break;
}
}
let mut builder = ArrayBuilder {
current_command_char: '{',
dimensions: vec![Dimension::default(); dimensions],
current_dim: dimensions - 1,
sealed: false,
elements: vec![],
};
let is_special_char = |c| matches!(c, '{' | '}' | ',' | '\\' | '"');
let is_end_of_literal = |c| matches!(c, ',' | '}');
loop {
buf.take_while(|ch| ch.is_ascii_whitespace());
match buf.next() {
None if builder.sealed => {
break;
}
None => Err(EarlyTerm)?,
Some(_) if builder.sealed => Err(Junk)?,
Some(c) => builder.current_command_char = c,
}
match builder.current_command_char {
'{' => builder.enter_dim()?,
'}' => builder.exit_dim()?,
',' => builder.commit_element(true)?,
c => {
buf.prev();
let s = match c {
'"' => Some(lex_quoted_element(buf)?),
_ => lex_unquoted_element(buf, is_special_char, is_end_of_literal)?,
};
builder.insert_element(s)?;
}
}
}
if builder.elements.is_empty() {
return Ok((vec![], vec![]));
}
let dims = builder
.dimensions
.into_iter()
.map(|dim| ArrayDimension {
length: dim
.length
.expect("every dimension must have its length discovered"),
lower_bound: 1,
})
.collect();
Ok((builder.elements, dims))
}
fn enter_dim(&mut self) -> Result<(), ArrayParsingError> {
let d = &mut self.dimensions[self.current_dim];
if d.staged_element {
return Err(UnexpectedChar(self.current_command_char));
}
self.current_dim += 1;
if self.current_dim >= self.dimensions.len() {
return Err(NonRectilinearDims);
}
Ok(())
}
fn insert_element(&mut self, s: Option<Cow<'a, str>>) -> Result<(), ArrayParsingError> {
if self.current_dim != self.dimensions.len() - 1 {
return Err(UnexpectedElement);
}
self.stage_element()?;
self.elements.push(s);
Ok(())
}
fn stage_element(&mut self) -> Result<(), ArrayParsingError> {
let d = &mut self.dimensions[self.current_dim];
if d.staged_element {
return Err(UnexpectedElement);
}
d.staged_element = true;
Ok(())
}
fn commit_element(&mut self, require_staged: bool) -> Result<(), ArrayParsingError> {
let d = &mut self.dimensions[self.current_dim];
if !d.staged_element {
return if require_staged || d.committed_element_count > 0 {
Err(UnexpectedChar(self.current_command_char))
} else {
Ok(())
};
}
d.staged_element = false;
d.committed_element_count += 1;
Ok(())
}
fn exit_dim(&mut self) -> Result<(), ArrayParsingError> {
self.commit_element(false)?;
let d = &mut self.dimensions[self.current_dim];
match d.length {
None => d.length = Some(d.committed_element_count),
Some(l) => {
if l != d.committed_element_count {
return Err(NonRectilinearDims);
}
}
}
d.committed_element_count = 0;
if self.current_dim == 0 {
self.sealed = true;
} else {
self.current_dim -= 1;
self.stage_element()?;
}
Ok(())
}
}
let (raw_elems, dims) = ArrayBuilder::build(s)?;
let mut elems = Vec::with_capacity(raw_elems.len());
let mut gen = |elem| gen_elem(elem).map_err(|e| e.to_string());
for elem in raw_elems.into_iter() {
elems.push(match elem {
Some(elem) => gen(elem)?,
None => make_null(),
});
}
Ok((elems, dims))
}
pub fn parse_list<'a, T, E>(
s: &'a str,
is_element_type_list: bool,
make_null: impl FnMut() -> T,
gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
) -> Result<Vec<T>, ParseError>
where
E: ToString,
{
parse_list_inner(s, is_element_type_list, make_null, gen_elem)
.map_err(|details| ParseError::invalid_input_syntax("list", s).with_details(details))
}
fn parse_list_inner<'a, T, E>(
s: &'a str,
is_element_type_list: bool,
mut make_null: impl FnMut() -> T,
mut gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
) -> Result<Vec<T>, String>
where
E: ToString,
{
let mut elems = vec![];
let buf = &mut LexBuf::new(s);
if !buf.consume('{') {
bail!(
"expected '{{', found {}",
match buf.next() {
Some(c) => format!("{}", c),
None => "empty string".to_string(),
}
)
}
let mut gen = |elem| gen_elem(elem).map_err(|e| e.to_string());
let is_special_char = |c| matches!(c, '{' | '}' | ',' | '\\' | '"');
let is_end_of_literal = |c| matches!(c, ',' | '}');
loop {
buf.take_while(|ch| ch.is_ascii_whitespace());
match buf.next() {
Some('}') => {
break;
}
_ if elems.len() == 0 => {
buf.prev();
}
Some(',') => {}
Some(c) => bail!("expected ',' or '}}', got '{}'", c),
None => bail!("unexpected end of input"),
}
buf.take_while(|ch| ch.is_ascii_whitespace());
let elem = match buf.peek() {
Some('"') => gen(lex_quoted_element(buf)?)?,
Some('{') => {
if !is_element_type_list {
bail!(
"unescaped '{{' at beginning of element; perhaps you \
want a nested list, e.g. '{{a}}'::text list list"
)
}
gen(lex_embedded_element(buf)?)?
}
Some(_) => match lex_unquoted_element(buf, is_special_char, is_end_of_literal)? {
Some(elem) => gen(elem)?,
None => make_null(),
},
None => bail!("unexpected end of input"),
};
elems.push(elem);
}
buf.take_while(|ch| ch.is_ascii_whitespace());
if let Some(c) = buf.next() {
bail!(
"malformed array literal; contains '{}' after terminal '}}'",
c
)
}
Ok(elems)
}
pub fn parse_legacy_vector<'a, T, E>(
s: &'a str,
gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
) -> Result<Vec<T>, ParseError>
where
E: ToString,
{
parse_legacy_vector_inner(s, gen_elem)
.map_err(|details| ParseError::invalid_input_syntax("int2vector", s).with_details(details))
}
pub fn parse_legacy_vector_inner<'a, T, E>(
s: &'a str,
mut gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
) -> Result<Vec<T>, String>
where
E: ToString,
{
let mut elems = vec![];
let buf = &mut LexBuf::new(s);
let mut gen = |elem| gen_elem(elem).map_err(|e| e.to_string());
loop {
buf.take_while(|ch| ch.is_ascii_whitespace());
match buf.peek() {
Some(_) => {
let elem = buf.take_while(|ch| !ch.is_ascii_whitespace());
elems.push(gen(elem.into())?);
}
None => break,
}
}
Ok(elems)
}
fn lex_quoted_element<'a>(buf: &mut LexBuf<'a>) -> Result<Cow<'a, str>, String> {
assert!(buf.consume('"'));
let s = buf.take_while(|ch| !matches!(ch, '"' | '\\'));
if let Some('"') = buf.peek() {
buf.next();
return Ok(s.into());
}
let mut s = s.to_string();
loop {
match buf.next() {
Some('\\') => match buf.next() {
Some(c) => s.push(c),
None => bail!("unterminated quoted string"),
},
Some('"') => break,
Some(c) => s.push(c),
None => bail!("unterminated quoted string"),
}
}
Ok(s.into())
}
fn lex_embedded_element<'a>(buf: &mut LexBuf<'a>) -> Result<Cow<'a, str>, String> {
let pos = buf.pos();
assert!(matches!(buf.next(), Some('{')));
let mut depth = 1;
let mut in_escape = false;
while depth > 0 {
match buf.next() {
Some('\\') => {
buf.next(); }
Some('"') => in_escape = !in_escape, Some('{') if !in_escape => depth += 1,
Some('}') if !in_escape => depth -= 1,
Some(_) => (),
None => bail!("unterminated embedded element"),
}
}
let s = &buf.inner()[pos..buf.pos()];
Ok(Cow::Borrowed(s))
}
fn lex_unquoted_element<'a>(
buf: &mut LexBuf<'a>,
is_special_char: impl Fn(char) -> bool,
is_end_of_literal: impl Fn(char) -> bool,
) -> Result<Option<Cow<'a, str>>, String> {
assert!(!buf.peek().unwrap().is_ascii_whitespace());
let s = buf.take_while(|ch| !is_special_char(ch) && !ch.is_ascii_whitespace());
match buf.peek() {
Some(',') | Some('}') if !s.is_empty() => {
return Ok(if s.to_uppercase() == "NULL" {
None
} else {
Some(s.into())
});
}
_ => {}
}
let mut escaped_char = false;
let mut s = s.to_string();
let mut trimmed_len = s.len();
loop {
match buf.next() {
Some('\\') => match buf.next() {
Some(c) => {
escaped_char = true;
s.push(c);
trimmed_len = s.len();
}
None => return Err("unterminated element".into()),
},
Some(c) if is_end_of_literal(c) => {
if s.is_empty() {
bail!("malformed literal; missing element")
}
buf.prev();
break;
}
Some(c) if is_special_char(c) => {
bail!("malformed literal; must escape special character '{}'", c)
}
Some(c) => {
s.push(c);
if !c.is_ascii_whitespace() {
trimmed_len = s.len();
}
}
None => bail!("unterminated element"),
}
}
s.truncate(trimmed_len);
Ok(if s.to_uppercase() == "NULL" && !escaped_char {
None
} else {
Some(Cow::Owned(s))
})
}
pub fn parse_map<'a, V, E>(
s: &'a str,
is_value_type_map: bool,
gen_elem: impl FnMut(Option<Cow<'a, str>>) -> Result<V, E>,
) -> Result<BTreeMap<String, V>, ParseError>
where
E: ToString,
{
parse_map_inner(s, is_value_type_map, gen_elem)
.map_err(|details| ParseError::invalid_input_syntax("map", s).with_details(details))
}
fn parse_map_inner<'a, V, E>(
s: &'a str,
is_value_type_map: bool,
mut gen_elem: impl FnMut(Option<Cow<'a, str>>) -> Result<V, E>,
) -> Result<BTreeMap<String, V>, String>
where
E: ToString,
{
let mut map = BTreeMap::new();
let buf = &mut LexBuf::new(s);
if !buf.consume('{') {
bail!(
"expected '{{', found {}",
match buf.next() {
Some(c) => format!("{}", c),
None => "empty string".to_string(),
}
)
}
let gen_key = |key: Option<Cow<'a, str>>| -> Result<String, String> {
match key {
Some(Cow::Owned(s)) => Ok(s),
Some(Cow::Borrowed(s)) => Ok(s.to_owned()),
None => Err("expected key".to_owned()),
}
};
let mut gen_value = |elem| gen_elem(elem).map_err(|e| e.to_string());
let is_special_char = |c| matches!(c, '{' | '}' | ',' | '"' | '=' | '>' | '\\');
let is_end_of_literal = |c| matches!(c, ',' | '}' | '=');
loop {
buf.take_while(|ch| ch.is_ascii_whitespace());
match buf.next() {
Some('}') => break,
_ if map.len() == 0 => {
buf.prev();
}
Some(',') => {}
Some(c) => bail!("expected ',' or end of input, got '{}'", c),
None => bail!("unexpected end of input"),
}
buf.take_while(|ch| ch.is_ascii_whitespace());
let key = match buf.peek() {
Some('"') => Some(lex_quoted_element(buf)?),
Some(_) => lex_unquoted_element(buf, is_special_char, is_end_of_literal)?,
None => bail!("unexpected end of input"),
};
let key = gen_key(key)?;
buf.take_while(|ch| ch.is_ascii_whitespace());
if !buf.consume('=') || !buf.consume('>') {
bail!("expected =>")
}
buf.take_while(|ch| ch.is_ascii_whitespace());
let value = match buf.peek() {
Some('"') => Some(lex_quoted_element(buf)?),
Some('{') => {
if !is_value_type_map {
bail!(
"unescaped '{{' at beginning of value; perhaps you \
want a nested map, e.g. '{{a=>{{a=>1}}}}'::map[text=>map[text=>int]]"
)
}
Some(lex_embedded_element(buf)?)
}
Some(_) => lex_unquoted_element(buf, is_special_char, is_end_of_literal)?,
None => bail!("unexpected end of input"),
};
let value = gen_value(value)?;
map.insert(key, value);
}
Ok(map)
}
pub fn format_map<F, T, E>(
buf: &mut F,
elems: impl IntoIterator<Item = (impl AsRef<str>, T)>,
mut format_elem: impl FnMut(MapValueWriter<F>, T) -> Result<Nestable, E>,
) -> Result<Nestable, E>
where
F: FormatBuffer,
{
buf.write_char('{');
let mut elems = elems.into_iter().peekable();
while let Some((key, value)) = elems.next() {
let key_start = buf.len();
buf.write_str(key.as_ref());
escape_elem::<_, MapElementEscaper>(buf, key_start);
buf.write_str("=>");
let value_start = buf.len();
if let Nestable::MayNeedEscaping = format_elem(MapValueWriter(buf), value)? {
escape_elem::<_, MapElementEscaper>(buf, value_start);
}
if elems.peek().is_some() {
buf.write_char(',');
}
}
buf.write_char('}');
Ok(Nestable::Yes)
}
pub fn parse_range<'a, V, E>(
s: &'a str,
gen_elem: impl FnMut(Cow<'a, str>) -> Result<V, E>,
) -> Result<Range<V>, ParseError>
where
E: ToString,
{
Ok(Range {
inner: parse_range_inner(s, gen_elem).map_err(|details| {
ParseError::invalid_input_syntax("range", s).with_details(details)
})?,
})
}
fn parse_range_inner<'a, V, E>(
s: &'a str,
mut gen_elem: impl FnMut(Cow<'a, str>) -> Result<V, E>,
) -> Result<Option<RangeInner<V>>, String>
where
E: ToString,
{
let buf = &mut LexBuf::new(s);
buf.take_while(|ch| ch.is_ascii_whitespace());
if buf.consume_str("empty") {
buf.take_while(|ch| ch.is_ascii_whitespace());
if buf.next().is_none() {
return Ok(None);
} else {
bail!("Junk after \"empty\" key word.")
}
}
let lower_inclusive = match buf.next() {
Some('[') => true,
Some('(') => false,
_ => bail!("Missing left parenthesis or bracket."),
};
let lower_bound = match buf.peek() {
Some(',') => None,
Some(_) => {
let v = buf.take_while(|c| !matches!(c, ','));
let v = gen_elem(Cow::from(v)).map_err(|e| e.to_string())?;
Some(v)
}
None => bail!("Unexpected end of input."),
};
buf.take_while(|ch| ch.is_ascii_whitespace());
if buf.next() != Some(',') {
bail!("Missing comma after lower bound.")
}
let upper_bound = match buf.peek() {
Some(']' | ')') => None,
Some(_) => {
let v = buf.take_while(|c| !matches!(c, ')' | ']'));
let v = gen_elem(Cow::from(v)).map_err(|e| e.to_string())?;
Some(v)
}
None => bail!("Unexpected end of input."),
};
let upper_inclusive = match buf.next() {
Some(']') => true,
Some(')') => false,
_ => bail!("Missing left parenthesis or bracket."),
};
buf.take_while(|ch| ch.is_ascii_whitespace());
if buf.next().is_some() {
bail!("Junk after right parenthesis or bracket.")
}
let range = Some(RangeInner {
lower: RangeBound {
inclusive: lower_inclusive,
bound: lower_bound,
},
upper: RangeBound {
inclusive: upper_inclusive,
bound: upper_bound,
},
});
Ok(range)
}
pub fn format_range<F, V, E>(
buf: &mut F,
r: &Range<V>,
mut format_elem: impl FnMut(RangeElementWriter<F>, Option<&V>) -> Result<Nestable, E>,
) -> Result<Nestable, E>
where
F: FormatBuffer,
{
let range = match &r.inner {
None => {
buf.write_str("empty");
return Ok(Nestable::MayNeedEscaping);
}
Some(i) => i,
};
if range.lower.inclusive {
buf.write_char('[');
} else {
buf.write_char('(');
}
let start = buf.len();
if let Nestable::MayNeedEscaping =
format_elem(RangeElementWriter(buf), range.lower.bound.as_ref())?
{
escape_elem::<_, ListElementEscaper>(buf, start);
}
buf.write_char(',');
let start = buf.len();
if let Nestable::MayNeedEscaping =
format_elem(RangeElementWriter(buf), range.upper.bound.as_ref())?
{
escape_elem::<_, ListElementEscaper>(buf, start);
}
if range.upper.inclusive {
buf.write_char(']');
} else {
buf.write_char(')');
}
Ok(Nestable::MayNeedEscaping)
}
#[derive(Debug)]
pub struct RangeElementWriter<'a, F>(&'a mut F);
impl<'a, F> RangeElementWriter<'a, F>
where
F: FormatBuffer,
{
pub fn write_null(self) -> Nestable {
Nestable::Yes
}
pub fn nonnull_buffer(self) -> &'a mut F {
self.0
}
}
pub fn format_array<F, T, E>(
buf: &mut F,
dims: &[ArrayDimension],
elems: impl IntoIterator<Item = T>,
mut format_elem: impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
) -> Result<Nestable, E>
where
F: FormatBuffer,
{
if dims.iter().any(|dim| dim.lower_bound != 1) {
for d in dims.iter() {
let (lower, upper) = d.dimension_bounds();
write!(buf, "[{}:{}]", lower, upper);
}
buf.write_char('=');
}
format_array_inner(buf, dims, &mut elems.into_iter(), &mut format_elem)?;
Ok(Nestable::Yes)
}
pub fn format_array_inner<F, T, E>(
buf: &mut F,
dims: &[ArrayDimension],
elems: &mut impl Iterator<Item = T>,
format_elem: &mut impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
) -> Result<(), E>
where
F: FormatBuffer,
{
if dims.is_empty() {
buf.write_str("{}");
return Ok(());
}
buf.write_char('{');
for j in 0..dims[0].length {
if j > 0 {
buf.write_char(',');
}
if dims.len() == 1 {
let start = buf.len();
let elem = elems.next().unwrap();
if let Nestable::MayNeedEscaping = format_elem(ListElementWriter(buf), elem)? {
escape_elem::<_, ListElementEscaper>(buf, start);
}
} else {
format_array_inner(buf, &dims[1..], elems, format_elem)?;
}
}
buf.write_char('}');
Ok(())
}
pub fn format_legacy_vector<F, T, E>(
buf: &mut F,
elems: impl IntoIterator<Item = T>,
format_elem: impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
) -> Result<Nestable, E>
where
F: FormatBuffer,
{
format_elems(buf, elems, format_elem, ' ')?;
Ok(Nestable::MayNeedEscaping)
}
pub fn format_list<F, T, E>(
buf: &mut F,
elems: impl IntoIterator<Item = T>,
format_elem: impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
) -> Result<Nestable, E>
where
F: FormatBuffer,
{
buf.write_char('{');
format_elems(buf, elems, format_elem, ',')?;
buf.write_char('}');
Ok(Nestable::Yes)
}
pub fn format_elems<F, T, E>(
buf: &mut F,
elems: impl IntoIterator<Item = T>,
mut format_elem: impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
sep: char,
) -> Result<(), E>
where
F: FormatBuffer,
{
let mut elems = elems.into_iter().peekable();
while let Some(elem) = elems.next() {
let start = buf.len();
if let Nestable::MayNeedEscaping = format_elem(ListElementWriter(buf), elem)? {
escape_elem::<_, ListElementEscaper>(buf, start);
}
if elems.peek().is_some() {
buf.write_char(sep)
}
}
Ok(())
}
pub fn format_mz_acl_item<F>(buf: &mut F, mz_acl_item: MzAclItem) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{mz_acl_item}");
Nestable::Yes
}
pub fn parse_mz_acl_item(s: &str) -> Result<MzAclItem, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("mz_aclitem", s).with_details(e))
}
pub fn format_acl_item<F>(buf: &mut F, acl_item: AclItem) -> Nestable
where
F: FormatBuffer,
{
write!(buf, "{acl_item}");
Nestable::Yes
}
pub fn parse_acl_item(s: &str) -> Result<AclItem, ParseError> {
s.trim()
.parse()
.map_err(|e| ParseError::invalid_input_syntax("aclitem", s).with_details(e))
}
pub trait ElementEscaper {
fn needs_escaping(elem: &[u8]) -> bool;
fn escape_char(c: u8) -> u8;
}
struct ListElementEscaper;
impl ElementEscaper for ListElementEscaper {
fn needs_escaping(elem: &[u8]) -> bool {
elem.is_empty()
|| elem == b"NULL"
|| elem
.iter()
.any(|c| matches!(c, b'{' | b'}' | b',' | b'"' | b'\\') || c.is_ascii_whitespace())
}
fn escape_char(_: u8) -> u8 {
b'\\'
}
}
struct MapElementEscaper;
impl ElementEscaper for MapElementEscaper {
fn needs_escaping(elem: &[u8]) -> bool {
elem.is_empty()
|| elem == b"NULL"
|| elem.iter().any(|c| {
matches!(c, b'{' | b'}' | b',' | b'"' | b'=' | b'>' | b'\\')
|| c.is_ascii_whitespace()
})
}
fn escape_char(_: u8) -> u8 {
b'\\'
}
}
struct RecordElementEscaper;
impl ElementEscaper for RecordElementEscaper {
fn needs_escaping(elem: &[u8]) -> bool {
elem.is_empty()
|| elem
.iter()
.any(|c| matches!(c, b'(' | b')' | b',' | b'"' | b'\\') || c.is_ascii_whitespace())
}
fn escape_char(c: u8) -> u8 {
if c == b'"' {
b'"'
} else {
b'\\'
}
}
}
fn escape_elem<F, E>(buf: &mut F, start: usize)
where
F: FormatBuffer,
E: ElementEscaper,
{
let elem = &buf.as_ref()[start..];
if !E::needs_escaping(elem) {
return;
}
let extras = 2 + elem.iter().filter(|b| matches!(b, b'"' | b'\\')).count();
let orig_end = buf.len();
let new_end = buf.len() + extras;
for _ in 0..extras {
buf.write_char('\0');
}
let elem = unsafe { buf.as_bytes_mut() };
let mut wi = new_end - 1;
elem[wi] = b'"';
wi -= 1;
for ri in (start..orig_end).rev() {
elem[wi] = elem[ri];
wi -= 1;
if let b'\\' | b'"' = elem[ri] {
elem[wi] = E::escape_char(elem[ri]);
wi -= 1;
}
}
elem[wi] = b'"';
assert!(wi == start);
}
#[derive(Debug)]
pub struct ListElementWriter<'a, F>(&'a mut F);
impl<'a, F> ListElementWriter<'a, F>
where
F: FormatBuffer,
{
pub fn write_null(self) -> Nestable {
self.0.write_str("NULL");
Nestable::Yes
}
pub fn nonnull_buffer(self) -> &'a mut F {
self.0
}
}
#[derive(Debug)]
pub struct MapValueWriter<'a, F>(&'a mut F);
impl<'a, F> MapValueWriter<'a, F>
where
F: FormatBuffer,
{
pub fn write_null(self) -> Nestable {
self.0.write_str("NULL");
Nestable::Yes
}
pub fn nonnull_buffer(self) -> &'a mut F {
self.0
}
}
pub fn format_record<F, T, E>(
buf: &mut F,
elems: impl IntoIterator<Item = T>,
mut format_elem: impl FnMut(RecordElementWriter<F>, T) -> Result<Nestable, E>,
) -> Result<Nestable, E>
where
F: FormatBuffer,
{
buf.write_char('(');
let mut elems = elems.into_iter().peekable();
while let Some(elem) = elems.next() {
let start = buf.len();
if let Nestable::MayNeedEscaping = format_elem(RecordElementWriter(buf), elem)? {
escape_elem::<_, RecordElementEscaper>(buf, start);
}
if elems.peek().is_some() {
buf.write_char(',')
}
}
buf.write_char(')');
Ok(Nestable::MayNeedEscaping)
}
#[derive(Debug)]
pub struct RecordElementWriter<'a, F>(&'a mut F);
impl<'a, F> RecordElementWriter<'a, F>
where
F: FormatBuffer,
{
pub fn write_null(self) -> Nestable {
Nestable::Yes
}
pub fn nonnull_buffer(self) -> &'a mut F {
self.0
}
}
#[derive(
Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
)]
pub struct ParseError {
pub kind: ParseErrorKind,
pub type_name: Box<str>,
pub input: Box<str>,
pub details: Option<Box<str>>,
}
#[derive(
Arbitrary,
Ord,
PartialOrd,
Clone,
Copy,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
Hash,
MzReflect,
)]
pub enum ParseErrorKind {
OutOfRange,
InvalidInputSyntax,
}
impl ParseError {
fn new<S>(kind: ParseErrorKind, type_name: &'static str, input: S) -> ParseError
where
S: Into<Box<str>>,
{
ParseError {
kind,
type_name: type_name.into(),
input: input.into(),
details: None,
}
}
fn out_of_range<S>(type_name: &'static str, input: S) -> ParseError
where
S: Into<Box<str>>,
{
ParseError::new(ParseErrorKind::OutOfRange, type_name, input)
}
fn invalid_input_syntax<S>(type_name: &'static str, input: S) -> ParseError
where
S: Into<Box<str>>,
{
ParseError::new(ParseErrorKind::InvalidInputSyntax, type_name, input)
}
fn with_details<D>(mut self, details: D) -> ParseError
where
D: fmt::Display,
{
self.details = Some(details.to_string().into());
self
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
ParseErrorKind::OutOfRange => {
write!(
f,
"{} is out of range for type {}",
self.input.quoted(),
self.type_name
)?;
if let Some(details) = &self.details {
write!(f, ": {}", details)?;
}
Ok(())
}
ParseErrorKind::InvalidInputSyntax => {
write!(f, "invalid input syntax for type {}: ", self.type_name)?;
if let Some(details) = &self.details {
write!(f, "{}: ", details)?;
}
write!(f, "{}", self.input.quoted())
}
}
}
}
impl Error for ParseError {}
impl RustType<ProtoParseError> for ParseError {
fn into_proto(&self) -> ProtoParseError {
use proto_parse_error::*;
use Kind::*;
let kind = match self.kind {
ParseErrorKind::OutOfRange => OutOfRange(()),
ParseErrorKind::InvalidInputSyntax => InvalidInputSyntax(()),
};
ProtoParseError {
kind: Some(kind),
type_name: self.type_name.into_proto(),
input: self.input.into_proto(),
details: self.details.into_proto(),
}
}
fn from_proto(proto: ProtoParseError) -> Result<Self, TryFromProtoError> {
use proto_parse_error::Kind::*;
if let Some(kind) = proto.kind {
Ok(ParseError {
kind: match kind {
OutOfRange(()) => ParseErrorKind::OutOfRange,
InvalidInputSyntax(()) => ParseErrorKind::InvalidInputSyntax,
},
type_name: proto.type_name.into(),
input: proto.input.into(),
details: proto.details.into_rust()?,
})
} else {
Err(TryFromProtoError::missing_field("ProtoParseError::kind"))
}
}
}
#[derive(
Arbitrary,
Ord,
PartialOrd,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
Hash,
MzReflect,
)]
pub enum ParseHexError {
InvalidHexDigit(char),
OddLength,
}
impl Error for ParseHexError {}
impl fmt::Display for ParseHexError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseHexError::InvalidHexDigit(c) => {
write!(f, "invalid hexadecimal digit: \"{}\"", c.escape_default())
}
ParseHexError::OddLength => {
f.write_str("invalid hexadecimal data: odd number of digits")
}
}
}
}
impl RustType<ProtoParseHexError> for ParseHexError {
fn into_proto(&self) -> ProtoParseHexError {
use proto_parse_hex_error::*;
use Kind::*;
let kind = match self {
ParseHexError::InvalidHexDigit(v) => InvalidHexDigit(v.into_proto()),
ParseHexError::OddLength => OddLength(()),
};
ProtoParseHexError { kind: Some(kind) }
}
fn from_proto(error: ProtoParseHexError) -> Result<Self, TryFromProtoError> {
use proto_parse_hex_error::Kind::*;
match error.kind {
Some(kind) => match kind {
InvalidHexDigit(v) => Ok(ParseHexError::InvalidHexDigit(char::from_proto(v)?)),
OddLength(()) => Ok(ParseHexError::OddLength),
},
None => Err(TryFromProtoError::missing_field(
"`ProtoParseHexError::kind`",
)),
}
}
}
#[cfg(test)]
mod tests {
use mz_ore::assert_ok;
use mz_proto::protobuf_roundtrip;
use proptest::prelude::*;
use super::*;
proptest! {
#[mz_ore::test]
#[cfg_attr(miri, ignore)] fn parse_error_protobuf_roundtrip(expect in any::<ParseError>()) {
let actual = protobuf_roundtrip::<_, ProtoParseError>(&expect);
assert_ok!(actual);
assert_eq!(actual.unwrap(), expect);
}
}
proptest! {
#[mz_ore::test]
#[cfg_attr(miri, ignore)] fn parse_hex_error_protobuf_roundtrip(expect in any::<ParseHexError>()) {
let actual = protobuf_roundtrip::<_, ProtoParseHexError>(&expect);
assert_ok!(actual);
assert_eq!(actual.unwrap(), expect);
}
}
#[mz_ore::test]
fn test_format_nanos_to_micros() {
let cases: Vec<(u32, &str)> = vec![
(0, ""),
(1, ""),
(499, ""),
(500, ".000001"),
(500_000, ".0005"),
(5_000_000, ".005"),
(1_999_999_999, ".2"),
];
for (nanos, expect) in cases {
let mut buf = String::new();
format_nanos_to_micros(&mut buf, nanos);
assert_eq!(&buf, expect);
}
}
#[mz_ore::test]
fn test_parse_pg_legacy_name() {
let s = "hello world";
assert_eq!(s, parse_pg_legacy_name(s));
let s = "x".repeat(63);
assert_eq!(s, parse_pg_legacy_name(&s));
let s = "x".repeat(64);
assert_eq!("x".repeat(63), parse_pg_legacy_name(&s));
let s = format!("{}{}", "x".repeat(61), "א");
assert_eq!(s, parse_pg_legacy_name(&s));
let s = format!("{}{}", "x".repeat(62), "א");
assert_eq!("x".repeat(62), parse_pg_legacy_name(&s));
}
}