use crate::registry::{get_struct_config, get_tagged_union};
use crate::types::{
EnumRepresentation, FieldType, ForeignTypeRegistry, StructConfig, TaggedUnion, VariantData,
};
use serde_json::Value;
pub fn to_surreal_string(
field_type: &FieldType,
value: &Value,
registry: &ForeignTypeRegistry,
) -> String {
match field_type {
FieldType::String | FieldType::Char => {
let s = value.as_str().unwrap_or_default();
format!("'{}'", escape_single_quotes(s))
}
FieldType::Bool => {
if value.as_bool().unwrap_or(false) {
"true".to_string()
} else {
"false".to_string()
}
}
FieldType::Other(name) => {
if let Some(ftc) = registry.lookup(name) {
match ftc.surql_value_format.as_str() {
"datetime" => {
if let Some(s) = value.as_str() {
format!("d'{}'", escape_single_quotes(s))
} else {
format!("d'{}'", chrono::Utc::now().to_rfc3339())
}
}
"duration_from_nanos" => {
if let Some(nanos) = value.as_i64() {
format!("duration::from_nanos({})", nanos)
} else if let Some(nanos) = value.as_u64() {
format!("duration::from_nanos({})", nanos)
} else if let Some(arr) = value.as_array() {
let seconds = arr.first().and_then(|v| v.as_i64()).unwrap_or(0);
let nanos = arr.get(1).and_then(|v| v.as_i64()).unwrap_or(0);
let total_nanos = seconds * 1_000_000_000 + nanos;
format!("duration::from_nanos({})", total_nanos)
} else {
"duration::from_nanos(0)".to_string()
}
}
"quoted_string" => {
if let Some(s) = value.as_str() {
format!("'{}'", escape_single_quotes(s))
} else {
"'UTC'".to_string()
}
}
"decimal_number" => {
let raw = if value.is_string() {
value.as_str().unwrap_or("0.0").to_string()
} else if value.is_number() {
value.to_string()
} else {
"0.0".to_string()
};
format!("{}dec", raw)
}
"record_id" => {
let id_string = value.as_str().unwrap_or_default();
id_string.replace('`', "")
}
"uuid_literal" => {
if let Some(s) = value.as_str() {
format!("u'{}'", escape_single_quotes(s))
} else {
"rand::uuid::v7()".to_string()
}
}
"bytes_literal" => {
if let Some(s) = value.as_str() {
format!("b64\"{}\"", s)
} else {
"b64\"\"".to_string()
}
}
"geometry_literal" => {
to_surreal_string_inferred(value)
}
_ => to_surreal_string_inferred(value),
}
} else if let Some(tagged_union) = get_tagged_union(name) {
tagged_union_to_surreal_string(&tagged_union, value, registry)
} else if let Some(struct_config) = get_struct_config(name) {
struct_config_to_surreal_string(&struct_config, value, registry)
} else {
to_surreal_string_inferred(value)
}
}
FieldType::F32
| FieldType::F64
| FieldType::I8
| FieldType::I16
| FieldType::I32
| FieldType::I64
| FieldType::I128
| FieldType::Isize
| FieldType::U8
| FieldType::U16
| FieldType::U32
| FieldType::U64
| FieldType::U128
| FieldType::Usize => {
if value.is_number() {
value.to_string()
} else {
"0".to_string()
}
}
FieldType::Unit => "null".to_string(),
FieldType::Vec(inner_type) => {
if let Some(array) = value.as_array() {
let items: Vec<String> = array
.iter()
.map(|item_value| to_surreal_string(inner_type, item_value, registry))
.collect();
format!("[{}]", items.join(", "))
} else {
"[]".to_string()
}
}
FieldType::Option(inner_type) => {
if value.is_null() {
"null".to_string()
} else {
to_surreal_string(inner_type, value, registry)
}
}
FieldType::Tuple(field_types) => {
if let Some(arr) = value.as_array() {
let mut parts = Vec::new();
for (sub_ftype, sub_val) in field_types.iter().zip(arr.iter()) {
let s = to_surreal_string(sub_ftype, sub_val, registry);
parts.push(s);
}
format!("[{}]", parts.join(", "))
} else {
"".to_string()
}
}
FieldType::Struct(fields) => {
if let Some(obj) = value.as_object() {
let mut pairs = Vec::new();
for (sub_field_name, sub_field_type) in fields {
if let Some(sub_val) = obj.get(sub_field_name) {
let s = to_surreal_string(sub_field_type, sub_val, registry);
pairs.push(format!("{}: {}", sub_field_name, s));
}
}
format!("{{ {} }}", pairs.join(", "))
} else {
"{}".to_string()
}
}
FieldType::HashMap(key_type, value_type) => {
if let Some(obj) = value.as_object() {
let mut pairs = Vec::new();
for (k, v) in obj {
let key_str = match &**key_type {
FieldType::String | FieldType::Char | FieldType::Other(_) => {
format!("'{}'", escape_single_quotes(k))
}
_ => k.clone(),
};
let val_str = to_surreal_string(value_type, v, registry);
pairs.push(format!("{}: {}", key_str, val_str));
}
format!("{{ {} }}", pairs.join(", "))
} else {
"{}".to_string()
}
}
FieldType::BTreeMap(key_type, value_type) => {
if let Some(obj) = value.as_object() {
let mut pairs = Vec::new();
for (k, v) in obj {
let key_str = match &**key_type {
FieldType::String | FieldType::Char | FieldType::Other(_) => {
format!("'{}'", escape_single_quotes(k))
}
_ => k.clone(),
};
let val_str = to_surreal_string(value_type, v, registry);
pairs.push(format!("{}: {}", key_str, val_str));
}
format!("{{ {} }}", pairs.join(", "))
} else {
"{}".to_string()
}
}
FieldType::RecordLink(inner_ftype) => {
if value.is_string() {
let link_string = value
.as_str()
.expect("Record link value should not be None");
format!("type::record('{}')", escape_single_quotes(link_string))
} else if let Some(obj) = value.as_object() {
if let Some(id_value) = obj.get("Id") {
if let Some(id_str) = id_value.as_str() {
format!("type::record('{}')", escape_single_quotes(id_str))
} else {
"null".to_string()
}
} else if let Some(id_value) = obj.get("id") {
if let Some(id_str) = id_value.as_str() {
format!("type::record('{}')", escape_single_quotes(id_str))
} else {
"null".to_string()
}
} else if let Some(obj_value) = obj.get("Object") {
to_surreal_string(inner_ftype, obj_value, registry)
} else {
to_surreal_string(inner_ftype, value, registry)
}
} else {
"null".to_string()
}
}
}
}
fn tagged_union_to_surreal_string(
tu: &TaggedUnion,
value: &Value,
registry: &ForeignTypeRegistry,
) -> String {
let tu = tu.effective();
let (variant_name, variant_obj) = match (&tu.representation, value) {
(EnumRepresentation::ExternallyTagged, Value::String(s)) => (s.clone(), None),
(EnumRepresentation::ExternallyTagged, Value::Object(obj)) => {
if let Some((k, v)) = obj.iter().next() {
(k.clone(), Some(v.clone()))
} else {
return to_surreal_string_inferred(value);
}
}
(EnumRepresentation::InternallyTagged { tag }, Value::Object(obj)) => {
let name = obj
.get(tag.as_str())
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();
let mut without_tag = obj.clone();
without_tag.remove(tag.as_str());
(name, Some(Value::Object(without_tag)))
}
(EnumRepresentation::AdjacentlyTagged { tag, content }, Value::Object(obj)) => {
let name = obj
.get(tag.as_str())
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();
(name, obj.get(content.as_str()).cloned())
}
(EnumRepresentation::Untagged, Value::Object(obj)) => {
let mut best: Option<(String, usize)> = None;
for candidate in &tu.variants {
if let Some(VariantData::InlineStruct(sc)) = candidate.data.as_ref() {
let sc = sc.effective();
let names: std::collections::HashSet<&str> =
sc.fields.iter().map(|f| f.field_name.as_str()).collect();
if obj.keys().all(|k| names.contains(k.as_str()))
&& best.as_ref().is_none_or(|(_, n)| obj.len() > *n)
{
best = Some((candidate.name.clone(), obj.len()));
}
}
}
match best {
Some((name, _)) => (name, Some(value.clone())),
None => return to_surreal_string_inferred(value),
}
}
(EnumRepresentation::Untagged, _) => {
return to_surreal_string_inferred(value);
}
_ => return to_surreal_string_inferred(value),
};
let Some(variant) = tu.variants.iter().find(|v| v.name == variant_name) else {
return to_surreal_string_inferred(value);
};
let mut pairs: Vec<String> = Vec::new();
match &tu.representation {
EnumRepresentation::InternallyTagged { tag } => {
pairs.push(format!(
"{}: '{}'",
tag,
escape_single_quotes(&variant_name)
));
}
EnumRepresentation::AdjacentlyTagged { tag, .. } => {
pairs.push(format!(
"{}: '{}'",
tag,
escape_single_quotes(&variant_name)
));
}
_ => {}
}
if let Some(VariantData::InlineStruct(struct_config)) = variant.data.as_ref()
&& let Some(payload_obj) = variant_obj.as_ref().and_then(|v| v.as_object())
{
for field in &struct_config.effective().fields {
if let Some(sub_val) = payload_obj.get(&field.field_name) {
let s = to_surreal_string(&field.field_type, sub_val, registry);
pairs.push(format!("{}: {}", field.field_name, s));
}
}
}
if let Some(VariantData::DataStructureRef(ft)) = variant.data.as_ref()
&& let Some(payload) = variant_obj.as_ref()
{
let payload_str = to_surreal_string(ft, payload, registry);
return match &tu.representation {
EnumRepresentation::ExternallyTagged => format!(
"{{ '{}': {} }}",
escape_single_quotes(&variant_name),
payload_str
),
EnumRepresentation::AdjacentlyTagged { tag, content } => format!(
"{{ {}: '{}', {}: {} }}",
tag,
escape_single_quotes(&variant_name),
content,
payload_str
),
EnumRepresentation::InternallyTagged { tag } => format!(
"{{ {}: '{}', value: {} }}",
tag,
escape_single_quotes(&variant_name),
payload_str
),
EnumRepresentation::Untagged => payload_str,
};
}
if let EnumRepresentation::AdjacentlyTagged { content, .. } = &tu.representation {
let body_pairs: Vec<String> = pairs
.iter()
.filter(|p| {
!p.starts_with(&format!("{}:", content))
&& !p.starts_with(&format!("{}: ", content))
})
.cloned()
.collect();
let tag_pair = body_pairs
.iter()
.find(|p| p.contains(": '"))
.cloned()
.unwrap_or_default();
let inner_pairs: Vec<String> = body_pairs.into_iter().filter(|p| p != &tag_pair).collect();
return format!(
"{{ {}, {}: {{ {} }} }}",
tag_pair,
content,
inner_pairs.join(", ")
);
}
if matches!(tu.representation, EnumRepresentation::ExternallyTagged) && variant_obj.is_some() {
return format!(
"{{ '{}': {{ {} }} }}",
escape_single_quotes(&variant_name),
pairs.join(", ")
);
}
if matches!(tu.representation, EnumRepresentation::ExternallyTagged) && variant_obj.is_none() {
return format!("'{}'", escape_single_quotes(&variant_name));
}
format!("{{ {} }}", pairs.join(", "))
}
fn struct_config_to_surreal_string(
sc: &StructConfig,
value: &Value,
registry: &ForeignTypeRegistry,
) -> String {
let sc = sc.effective();
let Some(obj) = value.as_object() else {
return to_surreal_string_inferred(value);
};
let mut pairs: Vec<String> = Vec::new();
for field in &sc.fields {
if let Some(sub_val) = obj.get(&field.field_name) {
let s = to_surreal_string(&field.field_type, sub_val, registry);
pairs.push(format!("{}: {}", field.field_name, s));
}
}
format!("{{ {} }}", pairs.join(", "))
}
fn to_surreal_string_inferred(value: &Value) -> String {
match value {
Value::Null => "null".to_string(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => {
if is_iso8601_datetime(s) {
format!("d'{}'", escape_single_quotes(s))
} else {
format!("'{}'", escape_single_quotes(s))
}
}
Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(to_surreal_string_inferred).collect();
format!("[{}]", items.join(", "))
}
Value::Object(obj) => {
let pairs: Vec<String> = obj
.iter()
.map(|(k, v)| format!("{}: {}", k, to_surreal_string_inferred(v)))
.collect();
format!("{{ {} }}", pairs.join(", "))
}
}
}
fn is_iso8601_datetime(s: &str) -> bool {
if s.len() < 20 {
return false;
}
let b = s.as_bytes();
b[4] == b'-' && b[7] == b'-' && b[10] == b'T' && b[13] == b':' && b[16] == b':'
}
fn escape_single_quotes(s: &str) -> String {
s.replace('\'', "\\'")
}