fraiseql_core/federation/
sql_utils.rs1use serde_json::Value;
6
7use crate::error::{FraiseQLError, Result};
8
9pub fn value_to_sql_literal(value: &Value) -> Result<String> {
27 match value {
28 Value::String(s) => {
29 let escaped = escape_sql_string(s);
30 Ok(format!("'{}'", escaped))
31 },
32 Value::Number(n) => Ok(n.to_string()),
33 Value::Bool(b) => Ok(if *b { "true" } else { "false" }.to_string()),
34 Value::Null => Ok("NULL".to_string()),
35 _ => Err(FraiseQLError::Validation {
36 message: format!("Cannot convert {} to SQL literal", value.type_str()),
37 path: None,
38 }),
39 }
40}
41
42pub fn value_to_string(value: &Value) -> Result<String> {
53 match value {
54 Value::String(s) => Ok(s.clone()),
55 Value::Number(n) => Ok(n.to_string()),
56 Value::Bool(b) => Ok(b.to_string()),
57 Value::Null => Ok("null".to_string()),
58 _ => Err(FraiseQLError::Validation {
59 message: format!("Cannot convert {} to string for WHERE clause", value.type_str()),
60 path: None,
61 }),
62 }
63}
64
65pub fn escape_sql_string(value: &str) -> String {
77 value.replace("'", "''")
78}
79
80pub trait JsonTypeStr {
82 fn type_str(&self) -> &'static str;
83}
84
85impl JsonTypeStr for Value {
86 fn type_str(&self) -> &'static str {
87 match self {
88 Value::Null => "null",
89 Value::Bool(_) => "bool",
90 Value::Number(_) => "number",
91 Value::String(_) => "string",
92 Value::Array(_) => "array",
93 Value::Object(_) => "object",
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use serde_json::json;
101
102 use super::*;
103
104 #[test]
105 fn test_value_to_sql_literal_string() {
106 let result = value_to_sql_literal(&Value::String("John".to_string())).unwrap();
107 assert_eq!(result, "'John'");
108 }
109
110 #[test]
111 fn test_value_to_sql_literal_string_with_quotes() {
112 let result = value_to_sql_literal(&Value::String("O'Brien".to_string())).unwrap();
113 assert_eq!(result, "'O''Brien'");
114 }
115
116 #[test]
117 fn test_value_to_sql_literal_number() {
118 let result = value_to_sql_literal(&json!(123)).unwrap();
119 assert_eq!(result, "123");
120
121 let result = value_to_sql_literal(&json!(99.99)).unwrap();
122 assert_eq!(result, "99.99");
123 }
124
125 #[test]
126 fn test_value_to_sql_literal_bool() {
127 let result = value_to_sql_literal(&Value::Bool(true)).unwrap();
128 assert_eq!(result, "true");
129
130 let result = value_to_sql_literal(&Value::Bool(false)).unwrap();
131 assert_eq!(result, "false");
132 }
133
134 #[test]
135 fn test_value_to_sql_literal_null() {
136 let result = value_to_sql_literal(&Value::Null).unwrap();
137 assert_eq!(result, "NULL");
138 }
139
140 #[test]
141 fn test_value_to_sql_literal_array_error() {
142 let result = value_to_sql_literal(&Value::Array(vec![]));
143 assert!(result.is_err());
144 }
145
146 #[test]
147 fn test_value_to_string() {
148 assert_eq!(value_to_string(&Value::String("test".to_string())).unwrap(), "test");
149 assert_eq!(value_to_string(&Value::Number(789.into())).unwrap(), "789");
150 assert_eq!(value_to_string(&Value::Bool(true)).unwrap(), "true");
151 assert_eq!(value_to_string(&Value::Null).unwrap(), "null");
152 }
153
154 #[test]
155 fn test_escape_sql_string() {
156 assert_eq!(escape_sql_string("O'Brien"), "O''Brien");
157 assert_eq!(escape_sql_string("test"), "test");
158 assert_eq!(escape_sql_string("test''; DROP--"), "test''''; DROP--");
159 }
160}