use nodedb_types::error::NodeDbError;
use tokio_postgres::types::Type;
#[derive(Debug, Clone, thiserror::Error, PartialEq)]
pub(super) enum DecodeReason {
#[error("binary form needs {expected} bytes, found {found}")]
Width { expected: usize, found: usize },
#[error("binary bool byte must be 0 or 1")]
BoolByte,
#[error("text is not UTF-8")]
NotUtf8,
#[error("text is not a valid {expected}")]
Invalid { expected: &'static str },
#[error("{what} is out of range")]
OutOfRange { what: &'static str },
#[error("the client has no decoder for this type")]
Unsupported,
#[error("array literal syntax: {0}")]
ArraySyntax(&'static str),
#[error("array element {index} ({text:?}) of type {element_type}: {source}")]
Element {
index: usize,
text: String,
element_type: String,
source: Box<DecodeReason>,
},
}
pub(super) fn cell_error(
column: &str,
ty: &Type,
raw: &[u8],
reason: &DecodeReason,
) -> NodeDbError {
NodeDbError::serialization(
"pgwire",
format!(
"column \"{column}\" of type {}: cannot decode {}: {reason}",
ty.name(),
cell_text(raw)
),
)
}
fn cell_text(raw: &[u8]) -> String {
match std::str::from_utf8(raw) {
Ok(text) if !text.chars().any(char::is_control) => format!("{text:?}"),
_ => {
let mut hex = String::with_capacity(2 + raw.len() * 2);
hex.push_str("\\x");
for byte in raw {
hex.push_str(&format!("{byte:02x}"));
}
hex
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_column_type_text_and_reason() {
let reason = DecodeReason::Invalid {
expected: "numeric",
};
let message = cell_error("price", &Type::NUMERIC, b"abc", &reason).to_string();
assert!(message.contains("\"price\""), "{message}");
assert!(message.contains("type numeric"), "{message}");
assert!(message.contains("\"abc\""), "{message}");
assert!(message.contains("not a valid numeric"), "{message}");
}
#[test]
fn shows_binary_bytes_as_hex() {
let reason = DecodeReason::Width {
expected: 4,
found: 3,
};
let message = cell_error("n", &Type::INT4, &[0, 1, 0xff], &reason).to_string();
assert!(message.contains("\\x0001ff"), "{message}");
assert!(message.contains("needs 4 bytes, found 3"), "{message}");
}
}