mod variable_extractor;
use pgrx::prelude::*;
use serde_json::Value as JsonValue;
pgrx::pg_module_magic!();
#[pg_extern]
pub fn check_valid_syntax(liquid_code: &str) -> bool {
let parser = match liquid::ParserBuilder::with_stdlib().build() {
Ok(parser) => parser,
Err(_) => return false,
};
match parser.parse(liquid_code) {
Ok(_) => true,
Err(_) => false,
}
}
#[pg_extern]
pub fn render(liquid_code: &str, data: pgrx::JsonB) -> Result<String, Box<dyn std::error::Error + Send + Sync + 'static>> {
let parser = liquid::ParserBuilder::with_stdlib().build()
.map_err(|e| format!("Failed to create liquid parser: {}", e))?;
let template = parser.parse(liquid_code)
.map_err(|e| format!("Failed to parse liquid template: {}", e))?;
let json_string = data.0.to_string();
let json_value: JsonValue = serde_json::from_str(&json_string)
.map_err(|e| format!("Failed to parse JSON data: {}", e))?;
let liquid_data: liquid::Object = serde_json::from_value(json_value)
.map_err(|e| format!("Failed to convert JSON to liquid Object: {}", e))?;
let output = template.render(&liquid_data)
.map_err(|e| format!("Failed to render template: {}", e))?;
Ok(output)
}
#[pg_extern]
fn get_variables(liquid_code: &str) -> Vec<String> {
variable_extractor::extract(liquid_code)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_valid_syntax_positive() {
assert_eq!(true, check_valid_syntax("Hello {{ name }}!"));
assert_eq!(true, check_valid_syntax("{% if user %}Welcome{% endif %}"));
assert_eq!(true, check_valid_syntax("{{ product.price }}")); assert_eq!(true, check_valid_syntax("Plain text without liquid"));
}
#[test]
fn test_check_valid_syntax_negative() {
assert_eq!(false, check_valid_syntax("Hello {{ name }!")); assert_eq!(false, check_valid_syntax("{% if user %}Welcome")); assert_eq!(false, check_valid_syntax("{{ | filter }}")); assert_eq!(false, check_valid_syntax("{% unknown_tag %}")); }
#[test]
fn test_render_positive() {
let data = pgrx::JsonB(serde_json::json!({"name": "Alice", "age": 30}));
let result = render("Hello {{ name }}!", data).unwrap();
assert_eq!("Hello Alice!", result);
let data = pgrx::JsonB(serde_json::json!({"price": 19.99}));
let result = render("Price: ${{ price }}", data).unwrap();
assert_eq!("Price: $19.99", result);
let data = pgrx::JsonB(serde_json::json!({"user": {"name": "Bob", "credits": 100}}));
let result = render("Welcome {{ user.name }}! Credits: {{ user.credits }}", data).unwrap();
assert_eq!("Welcome Bob! Credits: 100", result);
}
#[test]
fn test_render_negative() {
let data = pgrx::JsonB(serde_json::json!({"name": "Alice"}));
let result = render("Hello {{ name }!", data);
assert!(result.is_err());
let data = pgrx::JsonB(serde_json::json!({}));
let result = render("Hello {{ missing_var }}!", data);
assert!(result.is_err());
let data = pgrx::JsonB(serde_json::json!(null));
let result = render("{{ value }}", data);
assert!(result.is_err());
}
#[test]
fn test_get_variables_functionality() {
let vars = get_variables("Hello {{ user.name }}!");
assert_eq!(vars, vec!["user"]);
let template = r#"
<h1>{{ page.title }}</h1>
<p>Welcome {{ user.name }}!</p>
{% for item in products %}
<h3>{{ item.name }}</h3>
{% endfor %}
{% assign total = cart.total %}
"#;
let vars = get_variables(template);
assert_eq!(vars, vec!["cart", "item", "page", "products", "total", "user"]);
let vars = get_variables("Just plain text.");
assert!(vars.is_empty());
}
}
#[cfg(test)]
pub mod pg_test {
pub fn setup(_options: Vec<&str>) {
}
pub fn postgresql_conf_options() -> Vec<&'static str> {
vec![]
}
}