eure_json/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3mod config;
4mod error;
5
6pub use config::Config;
7pub use error::{EureToJsonError, JsonToEureError};
8use eure::data_model::VariantRepr;
9use eure::document::node::NodeValue;
10use eure::document::{EureDocument, NodeId};
11use eure::query::{ParseDocument, TextFile};
12use eure::value::{ObjectKey, PrimitiveValue};
13use eure_document::text::Text;
14use num_bigint::BigInt;
15use query_flow::{Db, QueryError, query};
16use serde_json::Value as JsonValue;
17
18#[query]
19pub fn eure_to_json(
20    db: &impl Db,
21    text_file: TextFile,
22    config: Config,
23) -> Result<JsonValue, QueryError> {
24    let parsed = db.query(ParseDocument::new(text_file.clone()))?;
25    Ok(document_to_value(&parsed.doc, &config)?)
26}
27
28/// Convert a JSON file to an Eure document.
29///
30/// This query reads the JSON file, parses it, and converts it to an EureDocument.
31#[query]
32pub fn json_to_eure(
33    db: &impl Db,
34    json_file: TextFile,
35    config: Config,
36) -> Result<EureDocument, QueryError> {
37    let content = db.asset(json_file.clone())?.suspend()?;
38    let json: JsonValue = serde_json::from_str(content.get())?;
39    Ok(value_to_document(&json, &config)?)
40}
41
42pub fn document_to_value(
43    doc: &EureDocument,
44    config: &Config,
45) -> Result<JsonValue, EureToJsonError> {
46    let root_id = doc.get_root_id();
47    convert_node(doc, root_id, config)
48}
49
50fn convert_node(
51    doc: &EureDocument,
52    node_id: NodeId,
53    config: &Config,
54) -> Result<JsonValue, EureToJsonError> {
55    let node = doc.node(node_id);
56
57    // Check for $variant extension
58    let variant_ext: Option<&str> = node
59        .extensions
60        .iter()
61        .find(|(k, _)| k.as_ref() == "variant")
62        .and_then(|(_, &ext_id)| {
63            if let NodeValue::Primitive(PrimitiveValue::Text(t)) = &doc.node(ext_id).content {
64                Some(t.as_str())
65            } else {
66                None
67            }
68        });
69
70    // If this node has a $variant extension, handle it as a variant
71    if let Some(tag) = variant_ext {
72        return convert_variant_node(doc, node_id, tag, config);
73    }
74
75    match &node.content {
76        NodeValue::Hole(_) => Err(EureToJsonError::HoleNotSupported),
77        NodeValue::Primitive(prim) => convert_primitive(prim),
78        NodeValue::Array(arr) => {
79            let mut result = Vec::new();
80            for &child_id in &arr.0 {
81                result.push(convert_node(doc, child_id, config)?);
82            }
83            Ok(JsonValue::Array(result))
84        }
85        NodeValue::Tuple(tuple) => {
86            let mut result = Vec::new();
87            for &child_id in &tuple.0 {
88                result.push(convert_node(doc, child_id, config)?);
89            }
90            Ok(JsonValue::Array(result))
91        }
92        NodeValue::Map(map) => {
93            let mut result = serde_json::Map::new();
94            for (key, &child_id) in &map.0 {
95                let key_string = convert_object_key(key)?;
96                let value = convert_node(doc, child_id, config)?;
97                result.insert(key_string, value);
98            }
99            Ok(JsonValue::Object(result))
100        }
101    }
102}
103
104fn convert_primitive(prim: &PrimitiveValue) -> Result<JsonValue, EureToJsonError> {
105    match prim {
106        PrimitiveValue::Null => Ok(JsonValue::Null),
107        PrimitiveValue::Bool(b) => Ok(JsonValue::Bool(*b)),
108        PrimitiveValue::Integer(bi) => {
109            // Try to convert to i64 for JSON
110            let i64_value = bi.to_string().parse::<i64>();
111            if let Ok(i) = i64_value {
112                return Ok(JsonValue::Number(i.into()));
113            }
114
115            // Try to convert to u64
116            let u64_value = bi.to_string().parse::<u64>();
117            if let Ok(u) = u64_value {
118                return Ok(JsonValue::Number(u.into()));
119            }
120
121            Err(EureToJsonError::BigIntOutOfRange)
122        }
123        PrimitiveValue::F32(f) => {
124            if let Some(num) = serde_json::Number::from_f64(*f as f64) {
125                Ok(JsonValue::Number(num))
126            } else {
127                // NaN or infinity - not supported in JSON
128                Err(EureToJsonError::NonFiniteFloat)
129            }
130        }
131        PrimitiveValue::F64(f) => {
132            if let Some(num) = serde_json::Number::from_f64(*f) {
133                Ok(JsonValue::Number(num))
134            } else {
135                // NaN or infinity - not supported in JSON
136                Err(EureToJsonError::NonFiniteFloat)
137            }
138        }
139        PrimitiveValue::Text(text) => Ok(JsonValue::String(text.content.clone())),
140    }
141}
142
143/// Convert a node that has a $variant extension
144fn convert_variant_node(
145    doc: &EureDocument,
146    node_id: NodeId,
147    tag: &str,
148    config: &Config,
149) -> Result<JsonValue, EureToJsonError> {
150    // Convert the content (the node itself minus the $variant extension)
151    let content_json = convert_node_content_only(doc, node_id, config)?;
152
153    match &config.variant_repr {
154        VariantRepr::External => {
155            // {"variant-name": content}
156            let mut map = serde_json::Map::new();
157            map.insert(tag.to_string(), content_json);
158            Ok(JsonValue::Object(map))
159        }
160        VariantRepr::Internal { tag: tag_field } => {
161            // {"type": "variant-name", ...fields...}
162            // Content must be an object to merge fields
163            if let JsonValue::Object(mut content_map) = content_json {
164                // Check if tag field already exists in content
165                if content_map.contains_key(tag_field) {
166                    return Err(EureToJsonError::VariantTagConflict {
167                        tag: tag_field.clone(),
168                    });
169                }
170                content_map.insert(tag_field.clone(), JsonValue::String(tag.to_string()));
171                Ok(JsonValue::Object(content_map))
172            } else {
173                // If content is not an object, use External representation as fallback
174                let mut map = serde_json::Map::new();
175                map.insert(tag.to_string(), content_json);
176                Ok(JsonValue::Object(map))
177            }
178        }
179        VariantRepr::Adjacent {
180            tag: tag_field,
181            content: content_key,
182        } => {
183            // {"type": "variant-name", "content": {...}}
184            // Check if tag and content keys are the same
185            if tag_field == content_key {
186                return Err(EureToJsonError::VariantAdjacentConflict {
187                    field: tag_field.clone(),
188                });
189            }
190            let mut map = serde_json::Map::new();
191            map.insert(tag_field.clone(), JsonValue::String(tag.to_string()));
192            map.insert(content_key.clone(), content_json);
193            Ok(JsonValue::Object(map))
194        }
195        VariantRepr::Untagged => {
196            // Just the content without variant information
197            Ok(content_json)
198        }
199    }
200}
201
202/// Convert a node's content without checking for $variant extension (to avoid infinite recursion)
203fn convert_node_content_only(
204    doc: &EureDocument,
205    node_id: NodeId,
206    config: &Config,
207) -> Result<JsonValue, EureToJsonError> {
208    let node = doc.node(node_id);
209
210    match &node.content {
211        NodeValue::Hole(_) => Err(EureToJsonError::HoleNotSupported),
212        NodeValue::Primitive(prim) => convert_primitive(prim),
213        NodeValue::Array(arr) => {
214            let mut result = Vec::new();
215            for &child_id in &arr.0 {
216                result.push(convert_node(doc, child_id, config)?);
217            }
218            Ok(JsonValue::Array(result))
219        }
220        NodeValue::Tuple(tuple) => {
221            let mut result = Vec::new();
222            for &child_id in &tuple.0 {
223                result.push(convert_node(doc, child_id, config)?);
224            }
225            Ok(JsonValue::Array(result))
226        }
227        NodeValue::Map(map) => {
228            let mut result = serde_json::Map::new();
229            for (key, &child_id) in &map.0 {
230                let key_string = convert_object_key(key)?;
231                let value = convert_node(doc, child_id, config)?;
232                result.insert(key_string, value);
233            }
234            Ok(JsonValue::Object(result))
235        }
236    }
237}
238
239fn convert_object_key(key: &ObjectKey) -> Result<String, EureToJsonError> {
240    match key {
241        ObjectKey::Number(n) => Ok(n.to_string()),
242        ObjectKey::String(s) => Ok(s.clone()),
243        ObjectKey::Tuple(tuple) => {
244            let mut parts = Vec::new();
245            for item in &tuple.0 {
246                parts.push(convert_object_key(item)?);
247            }
248            Ok(format!("({})", parts.join(", ")))
249        }
250    }
251}
252
253// ============================================================================
254// JSON to EureDocument conversion
255// ============================================================================
256
257/// Convert a JSON value to an EureDocument.
258///
259/// This conversion produces plain data structures without any variant detection.
260/// JSON objects become Eure maps, arrays become arrays, and primitives are converted
261/// directly. Variant reconstruction is not possible without schema information.
262///
263/// The `config` parameter is accepted for API consistency but is not used for
264/// JSON to Eure conversion (variant detection requires schema information).
265///
266/// # Example
267///
268/// ```
269/// use eure_json::{value_to_document, Config};
270/// use serde_json::json;
271///
272/// let json = json!({"name": "Alice", "age": 30});
273/// let doc = value_to_document(&json, &Config::default()).unwrap();
274/// ```
275pub fn value_to_document(
276    value: &JsonValue,
277    _config: &Config,
278) -> Result<EureDocument, JsonToEureError> {
279    let mut doc = EureDocument::new();
280    let root_id = doc.get_root_id();
281    convert_json_to_node(&mut doc, root_id, value);
282    Ok(doc)
283}
284
285/// Convert a JSON value and set it as the content of the given node.
286fn convert_json_to_node(doc: &mut EureDocument, node_id: NodeId, value: &JsonValue) {
287    match value {
288        JsonValue::Null => {
289            doc.node_mut(node_id).content = NodeValue::Primitive(PrimitiveValue::Null);
290        }
291        JsonValue::Bool(b) => {
292            doc.node_mut(node_id).content = NodeValue::Primitive(PrimitiveValue::Bool(*b));
293        }
294        JsonValue::Number(n) => {
295            if let Some(i) = n.as_i64() {
296                doc.node_mut(node_id).content =
297                    NodeValue::Primitive(PrimitiveValue::Integer(BigInt::from(i)));
298            } else if let Some(u) = n.as_u64() {
299                doc.node_mut(node_id).content =
300                    NodeValue::Primitive(PrimitiveValue::Integer(BigInt::from(u)));
301            } else if let Some(f) = n.as_f64() {
302                doc.node_mut(node_id).content = NodeValue::Primitive(PrimitiveValue::F64(f));
303            }
304        }
305        JsonValue::String(s) => {
306            doc.node_mut(node_id).content =
307                NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext(s.clone())));
308        }
309        JsonValue::Array(arr) => {
310            doc.node_mut(node_id).content = NodeValue::empty_array();
311            for item in arr {
312                let child_id = doc.create_node(NodeValue::hole());
313                convert_json_to_node(doc, child_id, item);
314                if let NodeValue::Array(ref mut array) = doc.node_mut(node_id).content {
315                    array.0.push(child_id);
316                }
317            }
318        }
319        JsonValue::Object(obj) => {
320            doc.node_mut(node_id).content = NodeValue::empty_map();
321            for (key, val) in obj {
322                let child_id = doc.create_node(NodeValue::hole());
323                convert_json_to_node(doc, child_id, val);
324                if let NodeValue::Map(ref mut map) = doc.node_mut(node_id).content {
325                    map.0.insert(ObjectKey::String(key.clone()), child_id);
326                }
327            }
328        }
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335    use eure::data_model::VariantRepr;
336    use eure_document::eure;
337    use serde_json::json;
338
339    // ========================================================================
340    // Eure to JSON conversion tests
341    // ========================================================================
342
343    #[test]
344    fn test_null() {
345        let eure = eure!({ = null });
346        let json = json!(null);
347        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
348    }
349
350    #[test]
351    fn test_bool_true() {
352        let eure = eure!({ = true });
353        let json = json!(true);
354        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
355    }
356
357    #[test]
358    fn test_bool_false() {
359        let eure = eure!({ = false });
360        let json = json!(false);
361        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
362    }
363
364    #[test]
365    fn test_integer() {
366        let eure = eure!({ = 42 });
367        let json = json!(42);
368        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
369    }
370
371    #[test]
372    fn test_negative_integer() {
373        let eure = eure!({ = -42 });
374        let json = json!(-42);
375        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
376    }
377
378    #[test]
379    fn test_float() {
380        let eure = eure!({ = 1.5f64 });
381        let json = json!(1.5);
382        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
383    }
384
385    #[test]
386    fn test_string() {
387        let eure = eure!({ = "hello world" });
388        let json = json!("hello world");
389        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
390    }
391
392    #[test]
393    fn test_array() {
394        let eure = eure!({
395            items[] = 1,
396            items[] = 2,
397            items[] = 3,
398        });
399        let json = json!({"items": [1, 2, 3]});
400        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
401    }
402
403    #[test]
404    fn test_tuple() {
405        let eure = eure!({
406            point.#0 = 1.5f64,
407            point.#1 = 2.5f64,
408        });
409        let json = json!({"point": [1.5, 2.5]});
410        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
411    }
412
413    #[test]
414    fn test_empty_map() {
415        let eure = eure!({});
416        let json = json!({});
417        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
418    }
419
420    #[test]
421    fn test_map_with_fields() {
422        let eure = eure!({ key = true });
423        let json = json!({"key": true});
424        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
425    }
426
427    #[test]
428    fn test_nested_map() {
429        let eure = eure!({
430            user.name = "Alice",
431            user.age = 30,
432        });
433        let json = json!({"user": {"name": "Alice", "age": 30}});
434        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
435    }
436
437    #[test]
438    fn test_array_of_maps() {
439        let eure = eure!({
440            users[].name = "Alice",
441            users[].name = "Bob",
442        });
443        let json = json!({"users": [{"name": "Alice"}, {"name": "Bob"}]});
444        assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
445    }
446
447    // Variant tests (Eure with $variant -> JSON)
448    #[test]
449    fn test_variant_external() {
450        let eure = eure!({
451            = true,
452            %variant = "Success",
453        });
454        let config = Config {
455            variant_repr: VariantRepr::External,
456        };
457        let json = json!({"Success": true});
458        assert_eq!(document_to_value(&eure, &config).unwrap(), json);
459    }
460
461    #[test]
462    fn test_variant_untagged() {
463        let eure = eure!({
464            = true,
465            %variant = "Success",
466        });
467        let config = Config {
468            variant_repr: VariantRepr::Untagged,
469        };
470        let json = json!(true);
471        assert_eq!(document_to_value(&eure, &config).unwrap(), json);
472    }
473
474    #[test]
475    fn test_variant_internal() {
476        let eure = eure!({
477            field = 42,
478            %variant = "Success",
479        });
480        let config = Config {
481            variant_repr: VariantRepr::Internal {
482                tag: "type".to_string(),
483            },
484        };
485        let json = json!({"type": "Success", "field": 42});
486        assert_eq!(document_to_value(&eure, &config).unwrap(), json);
487    }
488
489    #[test]
490    fn test_variant_adjacent() {
491        let eure = eure!({
492            = true,
493            %variant = "Success",
494        });
495        let config = Config {
496            variant_repr: VariantRepr::Adjacent {
497                tag: "tag".to_string(),
498                content: "content".to_string(),
499            },
500        };
501        let json = json!({"tag": "Success", "content": true});
502        assert_eq!(document_to_value(&eure, &config).unwrap(), json);
503    }
504
505    // Error tests
506    #[test]
507    fn test_hole_error() {
508        let eure = eure!({ placeholder = ! });
509        let result = document_to_value(&eure, &Config::default());
510        assert_eq!(result, Err(EureToJsonError::HoleNotSupported));
511    }
512
513    #[test]
514    fn test_f64_nan_error() {
515        let nan_value = f64::NAN;
516        let eure = eure!({ = nan_value });
517        let result = document_to_value(&eure, &Config::default());
518        assert_eq!(result, Err(EureToJsonError::NonFiniteFloat));
519    }
520
521    #[test]
522    fn test_f64_infinity_error() {
523        let inf_value = f64::INFINITY;
524        let eure = eure!({ = inf_value });
525        let result = document_to_value(&eure, &Config::default());
526        assert_eq!(result, Err(EureToJsonError::NonFiniteFloat));
527    }
528
529    // ========================================================================
530    // JSON to Eure conversion tests
531    // ========================================================================
532
533    #[test]
534    fn test_json_to_eure_null() {
535        let json = json!(null);
536        let expected = eure!({ = null });
537        assert_eq!(
538            value_to_document(&json, &Config::default()).unwrap(),
539            expected
540        );
541    }
542
543    #[test]
544    fn test_json_to_eure_bool() {
545        let json = json!(true);
546        let expected = eure!({ = true });
547        assert_eq!(
548            value_to_document(&json, &Config::default()).unwrap(),
549            expected
550        );
551    }
552
553    #[test]
554    fn test_json_to_eure_integer() {
555        let json = json!(42);
556        let expected = eure!({ = 42 });
557        assert_eq!(
558            value_to_document(&json, &Config::default()).unwrap(),
559            expected
560        );
561    }
562
563    #[test]
564    fn test_json_to_eure_float() {
565        let json = json!(1.5);
566        let expected = eure!({ = 1.5f64 });
567        assert_eq!(
568            value_to_document(&json, &Config::default()).unwrap(),
569            expected
570        );
571    }
572
573    #[test]
574    fn test_json_to_eure_string() {
575        let json = json!("hello");
576        let expected = eure!({ = "hello" });
577        assert_eq!(
578            value_to_document(&json, &Config::default()).unwrap(),
579            expected
580        );
581    }
582
583    #[test]
584    fn test_json_to_eure_array() {
585        // Test array conversion via roundtrip (eure! macro doesn't support root arrays)
586        let json = json!([1, 2, 3]);
587        let doc = value_to_document(&json, &Config::default()).unwrap();
588        let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
589        assert_eq!(json, roundtrip);
590    }
591
592    #[test]
593    fn test_json_to_eure_empty_object() {
594        let json = json!({});
595        let expected = eure!({});
596        assert_eq!(
597            value_to_document(&json, &Config::default()).unwrap(),
598            expected
599        );
600    }
601
602    #[test]
603    fn test_json_to_eure_object() {
604        let json = json!({"name": "Alice", "age": 30});
605        let expected = eure!({
606            name = "Alice",
607            age = 30,
608        });
609        assert_eq!(
610            value_to_document(&json, &Config::default()).unwrap(),
611            expected
612        );
613    }
614
615    #[test]
616    fn test_json_to_eure_nested() {
617        let json = json!({"user": {"name": "Alice"}});
618        let expected = eure!({ user.name = "Alice" });
619        assert_eq!(
620            value_to_document(&json, &Config::default()).unwrap(),
621            expected
622        );
623    }
624
625    #[test]
626    fn test_json_to_eure_array_of_objects() {
627        let json = json!({"users": [{"name": "Alice"}, {"name": "Bob"}]});
628        let expected = eure!({
629            users[].name = "Alice",
630            users[].name = "Bob",
631        });
632        assert_eq!(
633            value_to_document(&json, &Config::default()).unwrap(),
634            expected
635        );
636    }
637
638    // ========================================================================
639    // Roundtrip tests
640    // ========================================================================
641
642    #[test]
643    fn test_roundtrip_primitives() {
644        for json in [
645            json!(null),
646            json!(true),
647            json!(42),
648            json!(1.5),
649            json!("hello"),
650        ] {
651            let doc = value_to_document(&json, &Config::default()).unwrap();
652            let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
653            assert_eq!(json, roundtrip);
654        }
655    }
656
657    #[test]
658    fn test_roundtrip_array() {
659        let json = json!([1, 2, 3, "hello", true, null]);
660        let doc = value_to_document(&json, &Config::default()).unwrap();
661        let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
662        assert_eq!(json, roundtrip);
663    }
664
665    #[test]
666    fn test_roundtrip_nested() {
667        let json = json!({"name": "test", "nested": {"a": 1, "b": [true, false]}});
668        let doc = value_to_document(&json, &Config::default()).unwrap();
669        let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
670        assert_eq!(json, roundtrip);
671    }
672
673    #[test]
674    fn test_roundtrip_deeply_nested() {
675        let json = json!({"Ok": {"Some": {"method": "add"}}});
676        let doc = value_to_document(&json, &Config::default()).unwrap();
677        let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
678        assert_eq!(json, roundtrip);
679    }
680}