use std::fmt::Display;
pub trait PostgrestValue {
fn render(&self) -> String;
}
impl<T: Display + ?Sized> PostgrestValue for T {
fn render(&self) -> String {
self.to_string()
}
}
pub(crate) fn encode_value(value: &str) -> String {
urlencoding::encode(value).into_owned()
}
pub(crate) fn encode_column(column: &str) -> String {
urlencoding::encode(column).into_owned()
}
pub(crate) fn render_list<I, V>(values: I) -> String
where
I: IntoIterator<Item = V>,
V: PostgrestValue,
{
let parts: Vec<String> = values
.into_iter()
.map(|v| {
let raw = v.render();
if raw.contains([',', '(', ')', '"']) {
format!("\"{}\"", raw.replace('"', "\\\""))
} else {
raw
}
})
.collect();
format!("({})", parts.join(","))
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn encode_value_plain_ascii_passthrough() {
assert_eq!(encode_value("hello"), "hello");
assert_eq!(encode_value("active"), "active");
}
#[test]
fn encode_value_special_chars() {
assert_eq!(encode_value("hello world"), "hello%20world");
assert_eq!(encode_value("a&b"), "a%26b");
assert_eq!(encode_value("foo=bar"), "foo%3Dbar");
assert_eq!(encode_value("a+b"), "a%2Bb");
assert_eq!(encode_value("100%"), "100%25");
}
#[test]
fn encode_value_unicode() {
let encoded = encode_value("café");
assert!(encoded.contains('%'), "expected percent-encoding: {encoded}");
}
#[test]
fn encode_value_empty() {
assert_eq!(encode_value(""), "");
}
#[test]
fn encode_column_simple() {
assert_eq!(encode_column("user_id"), "user_id");
assert_eq!(encode_column("firstName"), "firstName");
}
#[test]
fn encode_column_dot_notation_preserved() {
let col = "profile.name";
let encoded = encode_column(col);
assert_eq!(encoded, "profile.name");
}
#[test]
fn render_list_basic() {
let result = render_list(["a", "b", "c"]);
assert_eq!(result, "(a,b,c)");
}
#[test]
fn render_list_empty() {
let result = render_list(Vec::<&str>::new());
assert_eq!(result, "()");
}
#[test]
fn render_list_single() {
let result = render_list(["only"]);
assert_eq!(result, "(only)");
}
#[test]
fn render_list_values_with_commas_are_quoted() {
let result = render_list(["a,b", "c"]);
assert_eq!(result, r#"("a,b",c)"#);
}
#[test]
fn render_list_values_with_parens_are_quoted() {
let result = render_list(["(nested)"]);
assert_eq!(result, r#"("(nested)")"#);
}
#[test]
fn render_list_values_with_embedded_quotes_escaped() {
let result = render_list([r#"say "hi""#]);
assert_eq!(result, r#"("say \"hi\"")"#);
}
#[test]
fn render_list_integers() {
let result = render_list([1i32, 2, 3, 4]);
assert_eq!(result, "(1,2,3,4)");
}
#[test]
fn postgrest_value_for_bool() {
assert_eq!(true.render(), "true");
assert_eq!(false.render(), "false");
}
#[test]
fn postgrest_value_for_numeric() {
assert_eq!(42i32.render(), "42");
assert_eq!(2.5f64.render(), "2.5");
assert_eq!((-7i64).render(), "-7");
}
#[test]
fn postgrest_value_for_string_ref() {
let s = String::from("hello");
assert_eq!(s.render(), "hello");
assert_eq!("hello".render(), "hello");
}
}