Skip to main content

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