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