1use base64::Engine;
6use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
7use serde_json;
8use std::collections::HashMap;
9use surreal_sync_core::{Type, TypedValue, Value};
10
11fn parse_iso8601_duration(s: &str) -> Option<std::time::Duration> {
17 let trimmed = s.trim();
18 if let Some(secs_str) = trimmed.strip_prefix("PT").and_then(|s| s.strip_suffix('S')) {
20 if let Some(dot_pos) = secs_str.find('.') {
21 let secs: u64 = secs_str[..dot_pos].parse().ok()?;
23 let nanos_str = &secs_str[dot_pos + 1..];
24 let nanos: u32 = nanos_str.parse().ok()?;
25 Some(std::time::Duration::new(secs, nanos))
26 } else {
27 let secs: u64 = secs_str.parse().ok()?;
28 Some(std::time::Duration::from_secs(secs))
29 }
30 } else {
31 None
32 }
33}
34
35fn parse_datetime_string(s: &str) -> Option<DateTime<Utc>> {
42 if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
44 return Some(dt.with_timezone(&Utc));
45 }
46
47 if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
50 return Some(Utc.from_utc_datetime(&naive));
51 }
52
53 if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
56 return Some(Utc.from_utc_datetime(&naive));
57 }
58
59 if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
61 return Some(Utc.from_utc_datetime(&naive));
62 }
63
64 if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f") {
66 return Some(Utc.from_utc_datetime(&naive));
67 }
68
69 if let Ok(dt) = DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%#z") {
71 return Some(dt.with_timezone(&Utc));
72 }
73
74 None
75}
76
77#[derive(Debug, Clone)]
79pub struct JsonValueWithSchema {
80 pub value: serde_json::Value,
82 pub sync_type: Type,
84}
85
86impl JsonValueWithSchema {
87 pub fn new(value: serde_json::Value, sync_type: Type) -> Self {
89 Self { value, sync_type }
90 }
91
92 pub fn to_typed_value(&self) -> TypedValue {
94 TypedValue::from(self.clone())
95 }
96}
97
98impl From<JsonValueWithSchema> for TypedValue {
99 fn from(jv: JsonValueWithSchema) -> Self {
100 match (&jv.sync_type, &jv.value) {
101 (sync_type, serde_json::Value::Null) => TypedValue::null(sync_type.clone()),
103
104 (Type::Bool, serde_json::Value::Bool(b)) => TypedValue::bool(*b),
106 (Type::Bool, serde_json::Value::Number(n)) => {
108 if let Some(i) = n.as_i64() {
109 TypedValue::bool(i != 0)
110 } else {
111 TypedValue::null(Type::Bool)
112 }
113 }
114
115 (Type::Int8 { width }, serde_json::Value::Number(n)) => {
117 if let Some(i) = n.as_i64() {
118 TypedValue::int8(i as i8, *width)
119 } else {
120 TypedValue::null(Type::Int8 { width: *width })
121 }
122 }
123 (Type::Int16, serde_json::Value::Number(n)) => {
124 if let Some(i) = n.as_i64() {
125 TypedValue::int16(i as i16)
126 } else {
127 TypedValue::null(Type::Int16)
128 }
129 }
130 (Type::Int32, serde_json::Value::Number(n)) => {
131 if let Some(i) = n.as_i64() {
132 TypedValue::int32(i as i32)
133 } else {
134 TypedValue::null(Type::Int32)
135 }
136 }
137 (Type::Int64, serde_json::Value::Number(n)) => {
138 if let Some(i) = n.as_i64() {
139 TypedValue::int64(i)
140 } else {
141 TypedValue::null(Type::Int64)
142 }
143 }
144
145 (Type::Float32, serde_json::Value::Number(n)) => {
147 if let Some(f) = n.as_f64() {
148 TypedValue::float32(f as f32)
149 } else {
150 TypedValue::null(Type::Float32)
151 }
152 }
153 (Type::Float64, serde_json::Value::Number(n)) => {
154 if let Some(f) = n.as_f64() {
155 TypedValue::float64(f)
156 } else {
157 TypedValue::null(Type::Float64)
158 }
159 }
160
161 (Type::Decimal { precision, scale }, serde_json::Value::String(s)) => {
163 TypedValue::decimal(s, *precision, *scale)
164 }
165 (Type::Decimal { precision, scale }, serde_json::Value::Number(n)) => {
166 TypedValue::decimal(n.to_string(), *precision, *scale)
167 }
168
169 (Type::Char { length }, serde_json::Value::String(s)) => {
171 TypedValue::char_type(s, *length)
172 }
173 (Type::VarChar { length }, serde_json::Value::String(s)) => {
174 TypedValue::varchar(s, *length)
175 }
176 (Type::Text, serde_json::Value::String(s)) => TypedValue::text(s),
177
178 (Type::Blob, serde_json::Value::String(s)) => {
180 match base64::engine::general_purpose::STANDARD.decode(s) {
181 Ok(bytes) => TypedValue::blob(bytes),
182 Err(_) => TypedValue::null(Type::Blob),
183 }
184 }
185 (Type::Bytes, serde_json::Value::String(s)) => {
186 match base64::engine::general_purpose::STANDARD.decode(s) {
187 Ok(bytes) => TypedValue::bytes(bytes),
188 Err(_) => TypedValue::null(Type::Bytes),
189 }
190 }
191
192 (Type::Uuid, serde_json::Value::String(s)) => {
194 if let Ok(uuid) = uuid::Uuid::parse_str(s) {
195 TypedValue::uuid(uuid)
196 } else {
197 TypedValue::null(Type::Uuid)
198 }
199 }
200
201 (Type::LocalDateTime, serde_json::Value::String(s)) => {
203 if Value::is_mysql_zero_temporal_literal(s) {
204 TypedValue::zero_temporal(Type::LocalDateTime, Some(s.clone()))
205 } else if let Some(dt) = parse_datetime_string(s) {
206 TypedValue::datetime(dt)
207 } else {
208 TypedValue::null(Type::LocalDateTime)
209 }
210 }
211 (Type::LocalDateTimeNano, serde_json::Value::String(s)) => {
212 if Value::is_mysql_zero_temporal_literal(s) {
213 TypedValue::zero_temporal(Type::LocalDateTimeNano, Some(s.clone()))
214 } else if let Some(dt) = parse_datetime_string(s) {
215 TypedValue::datetime_nano(dt)
216 } else {
217 TypedValue::null(Type::LocalDateTimeNano)
218 }
219 }
220 (Type::ZonedDateTime, serde_json::Value::String(s)) => {
221 if Value::is_mysql_zero_temporal_literal(s) {
222 TypedValue::zero_temporal(Type::ZonedDateTime, Some(s.clone()))
223 } else if let Some(dt) = parse_datetime_string(s) {
224 TypedValue::timestamptz(dt)
225 } else {
226 TypedValue::null(Type::ZonedDateTime)
227 }
228 }
229
230 (Type::Date, serde_json::Value::String(s)) => {
232 if Value::is_mysql_zero_temporal_literal(s) {
233 TypedValue::zero_temporal(Type::Date, Some(s.clone()))
234 } else if let Ok(dt) = chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") {
235 let datetime = dt.and_hms_opt(0, 0, 0).unwrap();
236 let utc_dt = DateTime::<Utc>::from_naive_utc_and_offset(datetime, Utc);
237 TypedValue::date(utc_dt)
238 } else {
239 TypedValue::null(Type::Date)
240 }
241 }
242
243 (Type::Time, serde_json::Value::String(s)) => {
245 if let Ok(time) = chrono::NaiveTime::parse_from_str(s, "%H:%M:%S") {
246 let datetime = chrono::NaiveDate::from_ymd_opt(1970, 1, 1)
247 .unwrap()
248 .and_time(time);
249 let utc_dt = DateTime::<Utc>::from_naive_utc_and_offset(datetime, Utc);
250 TypedValue::time(utc_dt)
251 } else {
252 TypedValue::null(Type::Time)
253 }
254 }
255
256 (Type::Json, serde_json::Value::Object(obj)) => {
258 let value = json_object_to_universal(obj);
259 TypedValue::json(value)
260 }
261 (Type::Json, serde_json::Value::Array(arr)) => {
262 let value = json_array_to_universal(arr);
263 TypedValue::json(value)
264 }
265 (Type::Jsonb, serde_json::Value::Object(obj)) => {
266 let value = json_object_to_universal(obj);
267 TypedValue::jsonb(value)
268 }
269 (Type::Jsonb, serde_json::Value::Array(arr)) => {
270 let value = json_array_to_universal(arr);
271 TypedValue::jsonb(value)
272 }
273
274 (Type::Array { element_type }, serde_json::Value::Array(arr)) => {
276 let values: Vec<Value> = arr
277 .iter()
278 .map(|v| {
279 let jv = JsonValueWithSchema::new(v.clone(), (**element_type).clone());
280 TypedValue::from(jv).value
281 })
282 .collect();
283 TypedValue::array(values, (**element_type).clone())
284 }
285
286 (Type::Set { values: set_values }, serde_json::Value::Array(arr)) => {
288 let elements: Vec<String> = arr
289 .iter()
290 .filter_map(|v| {
291 if let serde_json::Value::String(s) = v {
292 Some(s.clone())
293 } else {
294 None
295 }
296 })
297 .collect();
298 TypedValue::set(elements, set_values.clone())
299 }
300
301 (
303 Type::Enum {
304 values: enum_values,
305 },
306 serde_json::Value::String(s),
307 ) => TypedValue::enum_type(s.clone(), enum_values.clone()),
308
309 (Type::Geometry { geometry_type }, serde_json::Value::Object(obj)) => {
311 TypedValue::geometry_geojson(
312 serde_json::Value::Object(obj.clone()),
313 geometry_type.clone(),
314 )
315 }
316
317 (Type::Duration, serde_json::Value::String(s)) => {
319 if let Some(duration) = parse_iso8601_duration(s) {
320 TypedValue::duration(duration)
321 } else {
322 TypedValue::null(Type::Duration)
323 }
324 }
325
326 (sync_type, _) => TypedValue::null(sync_type.clone()),
328 }
329 }
330}
331
332#[allow(dead_code)]
334fn json_object_to_universal(obj: &serde_json::Map<String, serde_json::Value>) -> serde_json::Value {
335 serde_json::Value::Object(obj.clone())
336}
337
338#[allow(dead_code)]
340fn json_array_to_universal(arr: &[serde_json::Value]) -> serde_json::Value {
341 serde_json::Value::Array(arr.to_vec())
342}
343
344#[allow(dead_code)]
346fn json_object_to_geojson_hashmap(
347 obj: &serde_json::Map<String, serde_json::Value>,
348) -> HashMap<String, Value> {
349 let mut map = HashMap::new();
350 for (key, value) in obj {
351 map.insert(key.clone(), json_value_to_universal(value));
352 }
353 map
354}
355
356pub fn json_value_to_universal(value: &serde_json::Value) -> Value {
360 match value {
361 serde_json::Value::Null => Value::Null,
362 serde_json::Value::Bool(b) => Value::Bool(*b),
363 serde_json::Value::Number(n) => {
364 if let Some(i) = n.as_i64() {
365 Value::Int64(i)
366 } else if let Some(f) = n.as_f64() {
367 Value::Float64(f)
368 } else {
369 Value::Text(n.to_string())
370 }
371 }
372 serde_json::Value::String(s) => Value::Text(s.clone()),
373 serde_json::Value::Array(arr) => Value::Array {
374 elements: arr.iter().map(json_value_to_universal).collect(),
375 element_type: Box::new(Type::Text),
376 },
377 serde_json::Value::Object(obj) => {
378 let mut map = HashMap::new();
379 for (key, val) in obj {
380 map.insert(key.clone(), json_value_to_universal(val));
381 }
382 Value::Json(Box::new(serde_json::Value::Object(obj.clone())))
383 }
384 }
385}
386
387#[allow(dead_code)]
389fn json_value_to_generated(value: &serde_json::Value) -> Value {
390 match value {
391 serde_json::Value::Null => Value::Null,
392 serde_json::Value::Bool(b) => Value::Bool(*b),
393 serde_json::Value::Number(n) => {
394 if let Some(i) = n.as_i64() {
395 Value::Int64(i)
396 } else if let Some(f) = n.as_f64() {
397 Value::Float64(f)
398 } else {
399 Value::Null
400 }
401 }
402 serde_json::Value::String(s) => Value::Text(s.clone()),
403 serde_json::Value::Array(arr) => Value::Array {
404 elements: arr.iter().map(json_value_to_generated).collect(),
405 element_type: Box::new(surreal_sync_core::Type::Text),
406 },
407 serde_json::Value::Object(_obj) => Value::Json(Box::new(value.clone())),
408 }
409}
410
411#[derive(Debug, Clone, Default)]
428pub struct JsonConversionConfig {
429 pub boolean_paths: Vec<String>,
432 pub set_paths: Vec<String>,
434}
435
436impl JsonConversionConfig {
437 pub fn new() -> Self {
439 Self::default()
440 }
441
442 pub fn with_boolean_path(mut self, path: &str) -> Self {
444 self.boolean_paths.push(path.to_string());
445 self
446 }
447
448 pub fn with_boolean_paths(mut self, paths: &[&str]) -> Self {
450 self.boolean_paths
451 .extend(paths.iter().map(|s| s.to_string()));
452 self
453 }
454
455 pub fn with_set_path(mut self, path: &str) -> Self {
457 self.set_paths.push(path.to_string());
458 self
459 }
460
461 pub fn with_set_paths(mut self, paths: &[&str]) -> Self {
463 self.set_paths.extend(paths.iter().map(|s| s.to_string()));
464 self
465 }
466}
467
468pub fn json_to_typed_value_with_config(
491 value: serde_json::Value,
492 current_path: &str,
493 config: &JsonConversionConfig,
494) -> TypedValue {
495 let gv = json_to_generated_value_with_config(value, current_path, config);
496 let json_value = if let Value::Json(json_val) = gv {
498 *json_val
499 } else {
500 universal_value_to_json(&gv)
502 };
503 TypedValue::json(json_value)
504}
505
506pub fn json_to_generated_value_with_config(
510 value: serde_json::Value,
511 current_path: &str,
512 config: &JsonConversionConfig,
513) -> Value {
514 match value {
515 serde_json::Value::Null => Value::Null,
516 serde_json::Value::Bool(b) => Value::Bool(b),
517 serde_json::Value::Number(n) => {
518 let is_boolean_path = config.boolean_paths.iter().any(|p| p == current_path);
520
521 if let Some(i) = n.as_i64() {
522 if is_boolean_path && (i == 0 || i == 1) {
523 Value::Bool(i == 1)
525 } else {
526 Value::Int64(i)
527 }
528 } else if let Some(f) = n.as_f64() {
529 Value::Float64(f)
530 } else {
531 Value::Text(n.to_string())
532 }
533 }
534 serde_json::Value::String(s) => {
535 let is_set_path = config.set_paths.iter().any(|p| p == current_path);
537
538 if is_set_path {
539 if s.is_empty() {
541 Value::Array {
542 elements: Vec::new(),
543 element_type: Box::new(surreal_sync_core::Type::Text),
544 }
545 } else {
546 let values: Vec<Value> =
547 s.split(',').map(|v| Value::Text(v.to_string())).collect();
548 Value::Array {
549 elements: values,
550 element_type: Box::new(surreal_sync_core::Type::Text),
551 }
552 }
553 } else {
554 Value::Text(s)
555 }
556 }
557 serde_json::Value::Array(arr) => {
558 let values: Vec<Value> = arr
559 .into_iter()
560 .enumerate()
561 .map(|(idx, item)| {
562 let item_path = format!("{current_path}[{idx}]");
563 json_to_generated_value_with_config(item, &item_path, config)
564 })
565 .collect();
566 Value::Array {
567 elements: values,
568 element_type: Box::new(surreal_sync_core::Type::Text),
569 }
570 }
571 serde_json::Value::Object(obj) => {
572 let map: HashMap<String, Value> = obj
573 .into_iter()
574 .map(|(key, val)| {
575 let nested_path = if current_path.is_empty() {
577 key.clone()
578 } else {
579 format!("{current_path}.{key}")
580 };
581 let converted = json_to_generated_value_with_config(val, &nested_path, config);
583 (key, converted)
584 })
585 .collect();
586 let json_obj: serde_json::Map<String, serde_json::Value> = map
588 .iter()
589 .map(|(k, v)| (k.clone(), universal_value_to_json(v)))
590 .collect();
591 Value::Json(Box::new(serde_json::Value::Object(json_obj)))
592 }
593 }
594}
595
596fn universal_value_to_json(value: &Value) -> serde_json::Value {
598 match value {
599 Value::Null => serde_json::Value::Null,
600 Value::Bool(b) => serde_json::Value::Bool(*b),
601 Value::Int64(i) => serde_json::json!(*i),
602 Value::Float64(f) => serde_json::json!(*f),
603 Value::Text(s) => serde_json::Value::String(s.clone()),
604 Value::Array { elements, .. } => {
605 serde_json::Value::Array(elements.iter().map(universal_value_to_json).collect())
606 }
607 Value::Json(json_val) => (**json_val).clone(),
608 _ => serde_json::Value::Null,
609 }
610}
611
612pub fn extract_field(
614 obj: &serde_json::Map<String, serde_json::Value>,
615 field: &str,
616 sync_type: &Type,
617) -> TypedValue {
618 match obj.get(field) {
619 Some(value) => JsonValueWithSchema::new(value.clone(), sync_type.clone()).to_typed_value(),
620 None => TypedValue::null(sync_type.clone()),
621 }
622}
623
624pub fn json_object_to_typed_values(
626 obj: &serde_json::Map<String, serde_json::Value>,
627 schema: &[(String, Type)],
628) -> HashMap<String, TypedValue> {
629 let mut result = HashMap::new();
630 for (field_name, sync_type) in schema {
631 let tv = extract_field(obj, field_name, sync_type);
632 result.insert(field_name.clone(), tv);
633 }
634 result
635}
636
637pub fn parse_jsonl_line(
639 line: &str,
640 schema: &[(String, Type)],
641) -> Result<HashMap<String, TypedValue>, serde_json::Error> {
642 let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(line)?;
643 Ok(json_object_to_typed_values(&obj, schema))
644}
645
646pub fn json_to_universal_with_table_schema(
656 value: serde_json::Value,
657 field_name: &str,
658 schema: &surreal_sync_core::TableDefinition,
659) -> anyhow::Result<Value> {
660 let column_type = schema.get_column_type(field_name);
662
663 match column_type {
664 Some(sync_type) => {
665 let jv = JsonValueWithSchema::new(value, sync_type.clone());
666 let tv = TypedValue::from(jv);
667 Ok(tv.value)
668 }
669 None => {
670 Ok(json_value_to_universal(&value))
672 }
673 }
674}
675
676pub fn convert_id_with_database_schema(
681 id_str: &str,
682 table_name: &str,
683 id_column: &str,
684 schema: &surreal_sync_core::DatabaseSchema,
685) -> anyhow::Result<Value> {
686 let table_schema = schema
688 .get_table(table_name)
689 .ok_or_else(|| anyhow::anyhow!("Table '{table_name}' not found in schema"))?;
690
691 let id_type = table_schema.get_column_type(id_column).ok_or_else(|| {
693 anyhow::anyhow!("Column '{id_column}' not found in table '{table_name}' schema")
694 })?;
695
696 convert_id_to_value(id_str, table_name, id_type)
698}
699
700pub fn convert_id_to_value(
702 id_str: &str,
703 table_name: &str,
704 id_type: &Type,
705) -> anyhow::Result<Value> {
706 match id_type {
707 Type::Int8 { .. } | Type::Int16 | Type::Int32 | Type::Int64 => {
708 let id_int: i64 = id_str.parse().map_err(|e| {
709 anyhow::anyhow!(
710 "Failed to parse ID '{id_str}' as integer for table '{table_name}': {e}"
711 )
712 })?;
713 Ok(Value::Int64(id_int))
714 }
715 Type::Uuid => {
716 let uuid = uuid::Uuid::parse_str(id_str).map_err(|e| {
717 anyhow::anyhow!(
718 "Failed to parse ID '{id_str}' as UUID for table '{table_name}': {e}"
719 )
720 })?;
721 Ok(Value::Uuid(uuid))
722 }
723 Type::Text | Type::VarChar { .. } | Type::Char { .. } => {
724 Ok(Value::Text(id_str.to_string()))
725 }
726 other => {
727 anyhow::bail!(
728 "Unsupported ID type {other:?} for table '{table_name}'. Supported types: Int8-64, Uuid, Text, VarChar, Char."
729 );
730 }
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737 use chrono::{Datelike, TimeZone, Timelike, Utc};
738 use serde_json::json;
739 use surreal_sync_core::GeometryType;
740
741 #[test]
742 fn test_null_conversion() {
743 let jv = JsonValueWithSchema::new(serde_json::Value::Null, Type::Text);
744 let tv = TypedValue::from(jv);
745 assert!(matches!(tv.value, Value::Null));
746 }
747
748 #[test]
749 fn test_bool_conversion() {
750 let jv = JsonValueWithSchema::new(json!(true), Type::Bool);
751 let tv = TypedValue::from(jv);
752 assert!(matches!(tv.value, Value::Bool(true)));
753 }
754
755 #[test]
756 fn test_bool_from_number_zero() {
757 let jv = JsonValueWithSchema::new(json!(0), Type::Bool);
759 let tv = TypedValue::from(jv);
760 assert!(matches!(tv.value, Value::Bool(false)));
761 }
762
763 #[test]
764 fn test_bool_from_number_one() {
765 let jv = JsonValueWithSchema::new(json!(1), Type::Bool);
767 let tv = TypedValue::from(jv);
768 assert!(matches!(tv.value, Value::Bool(true)));
769 }
770
771 #[test]
772 fn test_bool_from_nonzero_number() {
773 let jv = JsonValueWithSchema::new(json!(42), Type::Bool);
775 let tv = TypedValue::from(jv);
776 assert!(matches!(tv.value, Value::Bool(true)));
777 }
778
779 #[test]
780 fn test_int_conversion() {
781 let jv = JsonValueWithSchema::new(json!(42), Type::Int32);
782 let tv = TypedValue::from(jv);
783 assert!(matches!(tv.value, Value::Int32(42)));
784 }
785
786 #[test]
787 fn test_bigint_conversion() {
788 let jv = JsonValueWithSchema::new(json!(9876543210i64), Type::Int64);
789 let tv = TypedValue::from(jv);
790 assert!(matches!(tv.value, Value::Int64(9876543210)));
791 }
792
793 #[test]
794 fn test_float_conversion() {
795 let jv = JsonValueWithSchema::new(json!(1.23456), Type::Float64);
796 let tv = TypedValue::from(jv);
797 if let Value::Float64(f) = tv.value {
798 assert!((f - 1.23456).abs() < 0.00001);
799 } else {
800 panic!("Expected Float64");
801 }
802 }
803
804 #[test]
805 fn test_decimal_from_string() {
806 let jv = JsonValueWithSchema::new(
807 json!("123.456"),
808 Type::Decimal {
809 precision: 10,
810 scale: 3,
811 },
812 );
813 let tv = TypedValue::from(jv);
814 if let Value::Decimal {
815 value,
816 precision,
817 scale,
818 } = tv.value
819 {
820 assert_eq!(value, "123.456");
821 assert_eq!(precision, 10);
822 assert_eq!(scale, 3);
823 } else {
824 panic!("Expected Decimal");
825 }
826 }
827
828 #[test]
829 fn test_string_conversion() {
830 let jv = JsonValueWithSchema::new(json!("hello world"), Type::Text);
831 let tv = TypedValue::from(jv);
832 assert!(matches!(tv.value, Value::Text(ref s) if s == "hello world"));
833 }
834
835 #[test]
836 fn test_varchar_conversion() {
837 let jv = JsonValueWithSchema::new(json!("test"), Type::VarChar { length: 100 });
838 let tv = TypedValue::from(jv);
839 assert!(matches!(tv.sync_type, Type::VarChar { length: 100 }));
840 if let Value::VarChar { value, length } = tv.value {
841 assert_eq!(value, "test");
842 assert_eq!(length, 100);
843 } else {
844 panic!("Expected VarChar, got {:?}", tv.value);
845 }
846 }
847
848 #[test]
849 fn test_bytes_from_base64() {
850 let encoded = base64::engine::general_purpose::STANDARD.encode(vec![0x01, 0x02, 0x03]);
851 let jv = JsonValueWithSchema::new(json!(encoded), Type::Bytes);
852 let tv = TypedValue::from(jv);
853 assert!(matches!(tv.value, Value::Bytes(ref b) if *b == vec![0x01, 0x02, 0x03]));
854 }
855
856 #[test]
857 fn test_uuid_conversion() {
858 let jv =
859 JsonValueWithSchema::new(json!("550e8400-e29b-41d4-a716-446655440000"), Type::Uuid);
860 let tv = TypedValue::from(jv);
861 if let Value::Uuid(u) = tv.value {
862 assert_eq!(u.to_string(), "550e8400-e29b-41d4-a716-446655440000");
863 } else {
864 panic!("Expected Uuid");
865 }
866 }
867
868 #[test]
869 fn test_datetime_conversion() {
870 let dt = Utc.with_ymd_and_hms(2024, 6, 15, 10, 30, 0).unwrap();
871 let jv = JsonValueWithSchema::new(json!(dt.to_rfc3339()), Type::LocalDateTime);
872 let tv = TypedValue::from(jv);
873 if let Value::LocalDateTime(result_dt) = tv.value {
874 assert_eq!(result_dt.year(), 2024);
875 assert_eq!(result_dt.month(), 6);
876 assert_eq!(result_dt.day(), 15);
877 } else {
878 panic!("Expected DateTime");
879 }
880 }
881
882 #[test]
886 fn test_datetime_postgresql_to_jsonb_format() {
887 let json_str = "2024-11-13T20:15:33";
889 let jv = JsonValueWithSchema::new(json!(json_str), Type::LocalDateTime);
890 let tv = TypedValue::from(jv);
891
892 match &tv.value {
894 Value::LocalDateTime(dt) => {
895 assert_eq!(dt.year(), 2024);
896 assert_eq!(dt.month(), 11);
897 assert_eq!(dt.day(), 13);
898 assert_eq!(dt.hour(), 20);
899 assert_eq!(dt.minute(), 15);
900 assert_eq!(dt.second(), 33);
901 }
902 Value::Null => {
903 panic!(
904 "PostgreSQL timestamp format '{json_str}' was not parsed! parse_datetime_string failed."
905 );
906 }
907 other => {
908 panic!("Expected LocalDateTime, got {other:?}");
909 }
910 }
911 }
912
913 #[test]
915 fn test_parse_datetime_string_postgresql_format() {
916 let result = parse_datetime_string("2024-11-13T20:15:33");
918 assert!(
919 result.is_some(),
920 "parse_datetime_string must handle PostgreSQL to_jsonb format '2024-11-13T20:15:33'"
921 );
922 }
923
924 #[test]
925 fn test_date_from_string() {
926 let jv = JsonValueWithSchema::new(json!("2024-06-15"), Type::Date);
927 let tv = TypedValue::from(jv);
928 if let Value::Date(dt) = tv.value {
929 assert_eq!(dt.format("%Y-%m-%d").to_string(), "2024-06-15");
930 } else {
931 panic!("Expected Date, got {:?}", tv.value);
932 }
933 }
934
935 #[test]
936 fn test_zero_date_literal_emits_zero_temporal() {
937 let jv = JsonValueWithSchema::new(json!("0000-00-00"), Type::Date);
938 let tv = TypedValue::from(jv);
939 assert!(matches!(
940 tv.value,
941 Value::ZeroTemporal {
942 intended_type: Type::Date,
943 ..
944 }
945 ));
946 }
947
948 #[test]
949 fn test_zero_datetime_literal_emits_zero_temporal() {
950 let jv = JsonValueWithSchema::new(json!("0000-00-00 00:00:00"), Type::LocalDateTime);
951 let tv = TypedValue::from(jv);
952 assert!(matches!(
953 tv.value,
954 Value::ZeroTemporal {
955 intended_type: Type::LocalDateTime,
956 ..
957 }
958 ));
959 }
960
961 #[test]
962 fn test_time_from_string() {
963 let jv = JsonValueWithSchema::new(json!("14:30:45"), Type::Time);
964 let tv = TypedValue::from(jv);
965 if let Value::Time(dt) = tv.value {
966 assert_eq!(dt.format("%H:%M:%S").to_string(), "14:30:45");
967 } else {
968 panic!("Expected Time, got {:?}", tv.value);
969 }
970 }
971
972 #[test]
973 fn test_json_object_conversion() {
974 let jv = JsonValueWithSchema::new(json!({"name": "test", "count": 42}), Type::Json);
975 let tv = TypedValue::from(jv);
976 if let Value::Json(json_val) = tv.value {
977 if let serde_json::Value::Object(map) = json_val.as_ref() {
978 assert!(
979 matches!(map.get("name"), Some(serde_json::Value::String(s)) if s == "test")
980 );
981 assert!(
982 matches!(map.get("count"), Some(serde_json::Value::Number(n)) if n.as_i64() == Some(42))
983 );
984 } else {
985 panic!("Expected Object");
986 }
987 } else {
988 panic!("Expected Json");
989 }
990 }
991
992 #[test]
993 fn test_json_array_conversion() {
994 let jv = JsonValueWithSchema::new(json!([1, 2, 3]), Type::Json);
996 let tv = TypedValue::from(jv);
997 if let Value::Json(json_val) = tv.value {
998 if let serde_json::Value::Array(arr) = json_val.as_ref() {
999 assert_eq!(arr.len(), 3);
1000 assert!(
1001 matches!(arr[0], serde_json::Value::Number(ref n) if n.as_i64() == Some(1))
1002 );
1003 assert!(
1004 matches!(arr[1], serde_json::Value::Number(ref n) if n.as_i64() == Some(2))
1005 );
1006 assert!(
1007 matches!(arr[2], serde_json::Value::Number(ref n) if n.as_i64() == Some(3))
1008 );
1009 } else {
1010 panic!("Expected Array inside Json");
1011 }
1012 } else {
1013 panic!("Expected Json, got {:?}", tv.value);
1014 }
1015 }
1016
1017 #[test]
1018 fn test_json_array_of_strings_conversion() {
1019 let jv = JsonValueWithSchema::new(json!(["tag1", "tag2", "tag3"]), Type::Json);
1021 let tv = TypedValue::from(jv);
1022 if let Value::Json(json_val) = tv.value {
1023 if let serde_json::Value::Array(arr) = json_val.as_ref() {
1024 assert_eq!(arr.len(), 3);
1025 assert!(matches!(arr[0], serde_json::Value::String(ref s) if s == "tag1"));
1026 assert!(matches!(arr[1], serde_json::Value::String(ref s) if s == "tag2"));
1027 assert!(matches!(arr[2], serde_json::Value::String(ref s) if s == "tag3"));
1028 } else {
1029 panic!("Expected Array inside Json");
1030 }
1031 } else {
1032 panic!("Expected Json, got {:?}", tv.value);
1033 }
1034 }
1035
1036 #[test]
1037 fn test_jsonb_array_conversion() {
1038 let jv = JsonValueWithSchema::new(json!([1, 2, 3]), Type::Jsonb);
1040 let tv = TypedValue::from(jv);
1041 if let Value::Jsonb(json_val) = tv.value {
1042 if let serde_json::Value::Array(arr) = json_val.as_ref() {
1043 assert_eq!(arr.len(), 3);
1044 assert!(
1045 matches!(arr[0], serde_json::Value::Number(ref n) if n.as_i64() == Some(1))
1046 );
1047 } else {
1048 panic!("Expected Array inside Jsonb");
1049 }
1050 } else {
1051 panic!("Expected Jsonb, got {:?}", tv.value);
1052 }
1053 }
1054
1055 #[test]
1056 fn test_array_int_conversion() {
1057 let jv = JsonValueWithSchema::new(
1058 json!([1, 2, 3]),
1059 Type::Array {
1060 element_type: Box::new(Type::Int32),
1061 },
1062 );
1063 let tv = TypedValue::from(jv);
1064 if let Value::Array { elements, .. } = tv.value {
1065 assert_eq!(elements.len(), 3);
1066 assert!(matches!(elements[0], Value::Int32(1)));
1067 } else {
1068 panic!("Expected Array");
1069 }
1070 }
1071
1072 #[test]
1073 fn test_set_conversion() {
1074 let jv = JsonValueWithSchema::new(
1075 json!(["a", "b"]),
1076 Type::Set {
1077 values: vec!["a".to_string(), "b".to_string(), "c".to_string()],
1078 },
1079 );
1080 let tv = TypedValue::from(jv);
1081 if let Value::Set { elements, .. } = tv.value {
1082 assert_eq!(elements.len(), 2);
1083 assert!(elements.contains(&"a".to_string()));
1084 assert!(elements.contains(&"b".to_string()));
1085 } else {
1086 panic!("Expected Set, got {:?}", tv.value);
1087 }
1088 }
1089
1090 #[test]
1091 fn test_enum_conversion() {
1092 let jv = JsonValueWithSchema::new(
1093 json!("active"),
1094 Type::Enum {
1095 values: vec!["active".to_string(), "inactive".to_string()],
1096 },
1097 );
1098 let tv = TypedValue::from(jv);
1099 if let Value::Enum { value, .. } = tv.value {
1100 assert_eq!(value, "active");
1101 } else {
1102 panic!("Expected Enum, got {:?}", tv.value);
1103 }
1104 }
1105
1106 #[test]
1107 fn test_geometry_conversion() {
1108 let jv = JsonValueWithSchema::new(
1109 json!({"type": "Point", "coordinates": [-73.97, 40.77]}),
1110 Type::Geometry {
1111 geometry_type: GeometryType::Point,
1112 },
1113 );
1114 let tv = TypedValue::from(jv);
1115 if let Value::Geometry { data, .. } = tv.value {
1116 use surreal_sync_core::values::GeometryData;
1117 let GeometryData(ref geo_json) = data;
1118 if let serde_json::Value::Object(map) = geo_json {
1119 assert!(
1120 matches!(map.get("type"), Some(serde_json::Value::String(s)) if s == "Point")
1121 );
1122 } else {
1123 panic!("Expected Object inside GeometryData");
1124 }
1125 } else {
1126 panic!("Expected Geometry, got {:?}", tv.value);
1127 }
1128 }
1129
1130 #[test]
1131 fn test_extract_field() {
1132 let obj = json!({"name": "Alice", "age": 30})
1133 .as_object()
1134 .unwrap()
1135 .clone();
1136
1137 let name = extract_field(&obj, "name", &Type::Text);
1138 assert!(matches!(name.value, Value::Text(ref s) if s == "Alice"));
1139
1140 let age = extract_field(&obj, "age", &Type::Int32);
1141 assert!(matches!(age.value, Value::Int32(30)));
1142
1143 let missing = extract_field(&obj, "missing", &Type::Text);
1144 assert!(matches!(missing.value, Value::Null));
1145 }
1146
1147 #[test]
1148 fn test_parse_jsonl_line() {
1149 let line = r#"{"name": "Bob", "active": true, "score": 95.5}"#;
1150 let schema = vec![
1151 ("name".to_string(), Type::Text),
1152 ("active".to_string(), Type::Bool),
1153 ("score".to_string(), Type::Float64),
1154 ];
1155
1156 let values = parse_jsonl_line(line, &schema).unwrap();
1157 assert!(matches!(
1158 values.get("name").unwrap().value,
1159 Value::Text(ref s) if s == "Bob"
1160 ));
1161 assert!(matches!(
1162 values.get("active").unwrap().value,
1163 Value::Bool(true)
1164 ));
1165 }
1166
1167 #[test]
1168 fn test_duration_conversion() {
1169 let jv = JsonValueWithSchema::new(json!("PT181S"), Type::Duration);
1171 let tv = TypedValue::from(jv);
1172 if let Value::Duration(d) = tv.value {
1173 assert_eq!(d.as_secs(), 181);
1174 assert_eq!(d.subsec_nanos(), 0);
1175 } else {
1176 panic!("Expected Duration, got {:?}", tv.value);
1177 }
1178 }
1179
1180 #[test]
1181 fn test_duration_with_nanos_conversion() {
1182 let jv = JsonValueWithSchema::new(json!("PT60.123456789S"), Type::Duration);
1184 let tv = TypedValue::from(jv);
1185 if let Value::Duration(d) = tv.value {
1186 assert_eq!(d.as_secs(), 60);
1187 assert_eq!(d.subsec_nanos(), 123456789);
1188 } else {
1189 panic!("Expected Duration, got {:?}", tv.value);
1190 }
1191 }
1192
1193 #[test]
1194 fn test_duration_invalid_format() {
1195 let jv = JsonValueWithSchema::new(json!("not a duration"), Type::Duration);
1197 let tv = TypedValue::from(jv);
1198 assert!(matches!(tv.value, Value::Null));
1199 }
1200
1201 #[test]
1202 fn test_json_to_universal_with_table_schema_array() {
1203 use surreal_sync_core::{ColumnDefinition, TableDefinition};
1206
1207 let pk = ColumnDefinition::new("id", Type::Text);
1209 let columns = vec![ColumnDefinition::new(
1210 "tags",
1211 Type::Array {
1212 element_type: Box::new(Type::Text),
1213 },
1214 )];
1215 let table_schema = TableDefinition::new("test_table", pk, columns);
1216
1217 let json_array = json!(["tag1", "tag2", "tag3"]);
1219 let result =
1220 json_to_universal_with_table_schema(json_array, "tags", &table_schema).unwrap();
1221
1222 match result {
1223 Value::Array { elements, .. } => {
1224 assert_eq!(elements.len(), 3);
1225 assert!(matches!(&elements[0], Value::Text(s) if s == "tag1"));
1226 assert!(matches!(&elements[1], Value::Text(s) if s == "tag2"));
1227 assert!(matches!(&elements[2], Value::Text(s) if s == "tag3"));
1228 }
1229 other => panic!("Expected Array, got {other:?}"),
1230 }
1231 }
1232
1233 #[test]
1234 fn test_json_to_universal_with_table_schema_null_array() {
1235 use surreal_sync_core::{ColumnDefinition, TableDefinition};
1237
1238 let pk = ColumnDefinition::new("id", Type::Text);
1239 let columns = vec![ColumnDefinition::new(
1240 "tags",
1241 Type::Array {
1242 element_type: Box::new(Type::Text),
1243 },
1244 )];
1245 let table_schema = TableDefinition::new("test_table", pk, columns);
1246
1247 let json_null = json!(null);
1248 let result = json_to_universal_with_table_schema(json_null, "tags", &table_schema).unwrap();
1249
1250 assert!(
1251 matches!(result, Value::Null),
1252 "Expected Null, got {result:?}"
1253 );
1254 }
1255
1256 #[test]
1257 fn test_json_to_universal_with_table_schema_unknown_field() {
1258 use surreal_sync_core::{ColumnDefinition, TableDefinition};
1260
1261 let pk = ColumnDefinition::new("id", Type::Text);
1262 let columns = vec![];
1263 let table_schema = TableDefinition::new("test_table", pk, columns);
1264
1265 let json_array = json!(["a", "b"]);
1267 let result =
1268 json_to_universal_with_table_schema(json_array, "unknown_field", &table_schema)
1269 .unwrap();
1270
1271 match result {
1272 Value::Array { elements, .. } => {
1273 assert_eq!(elements.len(), 2);
1274 }
1275 other => panic!("Expected Array from generic conversion, got {other:?}"),
1276 }
1277 }
1278}