#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
mod config;
mod error;
pub use config::Config;
pub use error::{EureToJsonError, JsonToEureError};
use eure::document::OriginMap;
use eure::document::node::NodeValue;
use eure::document::{EureDocument, NodeId};
use eure::query::{ParseDocument, TextFile, ValidCst};
use eure::report::{ErrorReport, ErrorReports, Origin, OriginHints};
use eure::tree::{Cst, InputSpan};
use eure::value::{ObjectKey, PrimitiveValue};
use eure_document::text::Text;
use eure_schema::interop::VariantRepr;
use num_bigint::BigInt;
use query_flow::{Db, QueryError, query};
use serde_json::Value as JsonValue;
#[query]
pub fn eure_to_json(
db: &impl Db,
text_file: TextFile,
config: Config,
) -> Result<JsonValue, QueryError> {
let parsed = db.query(ParseDocument::new(text_file.clone()))?;
Ok(document_to_value(&parsed.doc, &config)?)
}
#[query]
pub fn eure_to_json_formatted(
db: &impl Db,
text_file: TextFile,
config: Config,
) -> Result<JsonValue, QueryError> {
let parsed = db.query(ParseDocument::new(text_file.clone()))?;
match document_to_value(&parsed.doc, &config) {
Ok(json) => Ok(json),
Err(e) => {
let cst = db.query(ValidCst::new(text_file.clone()))?;
let report = create_json_error_report(&e, text_file, &parsed.origins, &cst);
Err(ErrorReports::from(vec![report]))?
}
}
}
fn create_json_error_report(
error: &EureToJsonError,
file: TextFile,
origins: &OriginMap,
cst: &Cst,
) -> ErrorReport {
let node_id = error.node_id();
let (span, is_fallback) = match origins.get_value_span(node_id, cst) {
Some(span) => (span, false),
None => (InputSpan::EMPTY, true),
};
let mut origin = Origin::with_hints(file, span, OriginHints::default().with_doc(node_id));
if is_fallback {
origin = origin.as_fallback();
}
ErrorReport::error(error.to_string(), origin)
}
#[query]
pub fn json_to_eure(
db: &impl Db,
json_file: TextFile,
config: Config,
) -> Result<EureDocument, QueryError> {
let content = db.asset(json_file.clone())?;
let json: JsonValue = serde_json::from_str(content.get())?;
Ok(value_to_document(&json, &config)?)
}
pub fn document_to_value(
doc: &EureDocument,
config: &Config,
) -> Result<JsonValue, EureToJsonError> {
let root_id = doc.get_root_id();
convert_node(doc, root_id, config)
}
fn convert_node(
doc: &EureDocument,
node_id: NodeId,
config: &Config,
) -> Result<JsonValue, EureToJsonError> {
let node = doc.node(node_id);
let variant_ext: Option<&str> = node
.extensions
.iter()
.find(|(k, _)| k.as_ref() == "variant")
.and_then(|(_, &ext_id)| {
if let NodeValue::Primitive(PrimitiveValue::Text(t)) = &doc.node(ext_id).content {
Some(t.as_str())
} else {
None
}
});
if let Some(tag) = variant_ext {
return convert_variant_node(doc, node_id, tag, config);
}
match &node.content {
NodeValue::Hole(_) => Err(EureToJsonError::HoleNotSupported { node_id }),
NodeValue::PartialMap(_) => Err(EureToJsonError::PartialMapNotSupported { node_id }),
NodeValue::Primitive(prim) => convert_primitive(prim, node_id),
NodeValue::Array(arr) => {
let mut result = Vec::new();
for &child_id in arr.iter() {
result.push(convert_node(doc, child_id, config)?);
}
Ok(JsonValue::Array(result))
}
NodeValue::Tuple(tuple) => {
let mut result = Vec::new();
for &child_id in tuple.iter() {
result.push(convert_node(doc, child_id, config)?);
}
Ok(JsonValue::Array(result))
}
NodeValue::Map(map) => {
let mut result = serde_json::Map::new();
for (key, &child_id) in map.iter() {
let key_string = convert_object_key(key)?;
let value = convert_node(doc, child_id, config)?;
result.insert(key_string, value);
}
Ok(JsonValue::Object(result))
}
}
}
fn convert_primitive(prim: &PrimitiveValue, node_id: NodeId) -> Result<JsonValue, EureToJsonError> {
match prim {
PrimitiveValue::Null => Ok(JsonValue::Null),
PrimitiveValue::Bool(b) => Ok(JsonValue::Bool(*b)),
PrimitiveValue::Integer(bi) => {
let i64_value = bi.to_string().parse::<i64>();
if let Ok(i) = i64_value {
return Ok(JsonValue::Number(i.into()));
}
let u64_value = bi.to_string().parse::<u64>();
if let Ok(u) = u64_value {
return Ok(JsonValue::Number(u.into()));
}
Err(EureToJsonError::BigIntOutOfRange { node_id })
}
PrimitiveValue::F32(f) => {
if let Some(num) = serde_json::Number::from_f64(*f as f64) {
Ok(JsonValue::Number(num))
} else {
Err(EureToJsonError::NonFiniteFloat { node_id })
}
}
PrimitiveValue::F64(f) => {
if let Some(num) = serde_json::Number::from_f64(*f) {
Ok(JsonValue::Number(num))
} else {
Err(EureToJsonError::NonFiniteFloat { node_id })
}
}
PrimitiveValue::Text(text) => Ok(JsonValue::String(text.content.clone())),
}
}
fn convert_variant_node(
doc: &EureDocument,
node_id: NodeId,
tag: &str,
config: &Config,
) -> Result<JsonValue, EureToJsonError> {
let content_json = convert_node_content_only(doc, node_id, config)?;
match &config.variant_repr {
VariantRepr::External => {
let mut map = serde_json::Map::new();
map.insert(tag.to_string(), content_json);
Ok(JsonValue::Object(map))
}
VariantRepr::Internal { tag: tag_field } => {
if let JsonValue::Object(mut content_map) = content_json {
if content_map.contains_key(tag_field) {
return Err(EureToJsonError::VariantTagConflict {
tag: tag_field.clone(),
node_id,
});
}
content_map.insert(tag_field.clone(), JsonValue::String(tag.to_string()));
Ok(JsonValue::Object(content_map))
} else {
let mut map = serde_json::Map::new();
map.insert(tag.to_string(), content_json);
Ok(JsonValue::Object(map))
}
}
VariantRepr::Adjacent {
tag: tag_field,
content: content_key,
} => {
if tag_field == content_key {
return Err(EureToJsonError::VariantAdjacentConflict {
field: tag_field.clone(),
node_id,
});
}
let mut map = serde_json::Map::new();
map.insert(tag_field.clone(), JsonValue::String(tag.to_string()));
map.insert(content_key.clone(), content_json);
Ok(JsonValue::Object(map))
}
VariantRepr::Untagged => {
Ok(content_json)
}
}
}
fn convert_node_content_only(
doc: &EureDocument,
node_id: NodeId,
config: &Config,
) -> Result<JsonValue, EureToJsonError> {
let node = doc.node(node_id);
match &node.content {
NodeValue::Hole(_) => Err(EureToJsonError::HoleNotSupported { node_id }),
NodeValue::PartialMap(_) => Err(EureToJsonError::PartialMapNotSupported { node_id }),
NodeValue::Primitive(prim) => convert_primitive(prim, node_id),
NodeValue::Array(arr) => {
let mut result = Vec::new();
for &child_id in arr.iter() {
result.push(convert_node(doc, child_id, config)?);
}
Ok(JsonValue::Array(result))
}
NodeValue::Tuple(tuple) => {
let mut result = Vec::new();
for &child_id in tuple.iter() {
result.push(convert_node(doc, child_id, config)?);
}
Ok(JsonValue::Array(result))
}
NodeValue::Map(map) => {
let mut result = serde_json::Map::new();
for (key, &child_id) in map.iter() {
let key_string = convert_object_key(key)?;
let value = convert_node(doc, child_id, config)?;
result.insert(key_string, value);
}
Ok(JsonValue::Object(result))
}
}
}
fn convert_object_key(key: &ObjectKey) -> Result<String, EureToJsonError> {
match key {
ObjectKey::Number(n) => Ok(n.to_string()),
ObjectKey::String(s) => Ok(s.clone()),
ObjectKey::Tuple(tuple) => {
let mut parts = Vec::new();
for item in &tuple.0 {
parts.push(convert_object_key(item)?);
}
Ok(format!("({})", parts.join(", ")))
}
}
}
pub fn value_to_document(
value: &JsonValue,
_config: &Config,
) -> Result<EureDocument, JsonToEureError> {
let mut doc = EureDocument::new();
let root_id = doc.get_root_id();
convert_json_to_node(&mut doc, root_id, value);
Ok(doc)
}
fn convert_json_to_node(doc: &mut EureDocument, node_id: NodeId, value: &JsonValue) {
match value {
JsonValue::Null => {
doc.node_mut(node_id).content = NodeValue::Primitive(PrimitiveValue::Null);
}
JsonValue::Bool(b) => {
doc.node_mut(node_id).content = NodeValue::Primitive(PrimitiveValue::Bool(*b));
}
JsonValue::Number(n) => {
if let Some(i) = n.as_i64() {
doc.node_mut(node_id).content =
NodeValue::Primitive(PrimitiveValue::Integer(BigInt::from(i)));
} else if let Some(u) = n.as_u64() {
doc.node_mut(node_id).content =
NodeValue::Primitive(PrimitiveValue::Integer(BigInt::from(u)));
} else if let Some(f) = n.as_f64() {
doc.node_mut(node_id).content = NodeValue::Primitive(PrimitiveValue::F64(f));
}
}
JsonValue::String(s) => {
doc.node_mut(node_id).content =
NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext(s.clone())));
}
JsonValue::Array(arr) => {
doc.node_mut(node_id).content = NodeValue::empty_array();
for item in arr {
let child_id = doc.create_node(NodeValue::hole());
convert_json_to_node(doc, child_id, item);
if let NodeValue::Array(ref mut array) = doc.node_mut(node_id).content {
let _ = array.push(child_id);
}
}
}
JsonValue::Object(obj) => {
doc.node_mut(node_id).content = NodeValue::empty_map();
for (key, val) in obj {
let child_id = doc.create_node(NodeValue::hole());
convert_json_to_node(doc, child_id, val);
if let NodeValue::Map(ref mut map) = doc.node_mut(node_id).content {
map.insert(ObjectKey::String(key.clone()), child_id);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use eure_document::eure;
use eure_schema::interop::VariantRepr;
use serde_json::json;
#[test]
fn test_null() {
let eure = eure!({ = null });
let json = json!(null);
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_bool_true() {
let eure = eure!({ = true });
let json = json!(true);
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_bool_false() {
let eure = eure!({ = false });
let json = json!(false);
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_integer() {
let eure = eure!({ = 42 });
let json = json!(42);
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_negative_integer() {
let eure = eure!({ = -42 });
let json = json!(-42);
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_float() {
let eure = eure!({ = 1.5f64 });
let json = json!(1.5);
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_string() {
let eure = eure!({ = "hello world" });
let json = json!("hello world");
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_array() {
let eure = eure!({
items[] = 1,
items[] = 2,
items[] = 3,
});
let json = json!({"items": [1, 2, 3]});
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_tuple() {
let eure = eure!({
point.#0 = 1.5f64,
point.#1 = 2.5f64,
});
let json = json!({"point": [1.5, 2.5]});
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_empty_map() {
let eure = eure!({});
let json = json!({});
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_map_with_fields() {
let eure = eure!({ key = true });
let json = json!({"key": true});
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_nested_map() {
let eure = eure!({
user.name = "Alice",
user.age = 30,
});
let json = json!({"user": {"name": "Alice", "age": 30}});
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_array_of_maps() {
let eure = eure!({
users[].name = "Alice",
users[].name = "Bob",
});
let json = json!({"users": [{"name": "Alice"}, {"name": "Bob"}]});
assert_eq!(document_to_value(&eure, &Config::default()).unwrap(), json);
}
#[test]
fn test_variant_external() {
let eure = eure!({
= true,
%variant = "Success",
});
let config = Config {
variant_repr: VariantRepr::External,
};
let json = json!({"Success": true});
assert_eq!(document_to_value(&eure, &config).unwrap(), json);
}
#[test]
fn test_variant_untagged() {
let eure = eure!({
= true,
%variant = "Success",
});
let config = Config {
variant_repr: VariantRepr::Untagged,
};
let json = json!(true);
assert_eq!(document_to_value(&eure, &config).unwrap(), json);
}
#[test]
fn test_variant_internal() {
let eure = eure!({
field = 42,
%variant = "Success",
});
let config = Config {
variant_repr: VariantRepr::Internal {
tag: "type".to_string(),
},
};
let json = json!({"type": "Success", "field": 42});
assert_eq!(document_to_value(&eure, &config).unwrap(), json);
}
#[test]
fn test_variant_adjacent() {
let eure = eure!({
= true,
%variant = "Success",
});
let config = Config {
variant_repr: VariantRepr::Adjacent {
tag: "tag".to_string(),
content: "content".to_string(),
},
};
let json = json!({"tag": "Success", "content": true});
assert_eq!(document_to_value(&eure, &config).unwrap(), json);
}
#[test]
fn test_hole_error() {
let eure = eure!({ placeholder = ! });
let result = document_to_value(&eure, &Config::default());
assert!(matches!(
result,
Err(EureToJsonError::HoleNotSupported { .. })
));
}
#[test]
fn test_f64_nan_error() {
let nan_value = f64::NAN;
let eure = eure!({ = nan_value });
let result = document_to_value(&eure, &Config::default());
assert!(matches!(
result,
Err(EureToJsonError::NonFiniteFloat { .. })
));
}
#[test]
fn test_f64_infinity_error() {
let inf_value = f64::INFINITY;
let eure = eure!({ = inf_value });
let result = document_to_value(&eure, &Config::default());
assert!(matches!(
result,
Err(EureToJsonError::NonFiniteFloat { .. })
));
}
#[test]
fn test_json_to_eure_null() {
let json = json!(null);
let expected = eure!({ = null });
assert_eq!(
value_to_document(&json, &Config::default()).unwrap(),
expected
);
}
#[test]
fn test_json_to_eure_bool() {
let json = json!(true);
let expected = eure!({ = true });
assert_eq!(
value_to_document(&json, &Config::default()).unwrap(),
expected
);
}
#[test]
fn test_json_to_eure_integer() {
let json = json!(42);
let expected = eure!({ = 42 });
assert_eq!(
value_to_document(&json, &Config::default()).unwrap(),
expected
);
}
#[test]
fn test_json_to_eure_float() {
let json = json!(1.5);
let expected = eure!({ = 1.5f64 });
assert_eq!(
value_to_document(&json, &Config::default()).unwrap(),
expected
);
}
#[test]
fn test_json_to_eure_string() {
let json = json!("hello");
let expected = eure!({ = "hello" });
assert_eq!(
value_to_document(&json, &Config::default()).unwrap(),
expected
);
}
#[test]
fn test_json_to_eure_array() {
let json = json!([1, 2, 3]);
let doc = value_to_document(&json, &Config::default()).unwrap();
let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
assert_eq!(json, roundtrip);
}
#[test]
fn test_json_to_eure_empty_object() {
let json = json!({});
let expected = eure!({});
assert_eq!(
value_to_document(&json, &Config::default()).unwrap(),
expected
);
}
#[test]
fn test_json_to_eure_object() {
let json = json!({"name": "Alice", "age": 30});
let expected = eure!({
name = "Alice",
age = 30,
});
assert_eq!(
value_to_document(&json, &Config::default()).unwrap(),
expected
);
}
#[test]
fn test_json_to_eure_nested() {
let json = json!({"user": {"name": "Alice"}});
let expected = eure!({ user.name = "Alice" });
assert_eq!(
value_to_document(&json, &Config::default()).unwrap(),
expected
);
}
#[test]
fn test_json_to_eure_array_of_objects() {
let json = json!({"users": [{"name": "Alice"}, {"name": "Bob"}]});
let expected = eure!({
users[].name = "Alice",
users[].name = "Bob",
});
assert_eq!(
value_to_document(&json, &Config::default()).unwrap(),
expected
);
}
#[test]
fn test_roundtrip_primitives() {
for json in [
json!(null),
json!(true),
json!(42),
json!(1.5),
json!("hello"),
] {
let doc = value_to_document(&json, &Config::default()).unwrap();
let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
assert_eq!(json, roundtrip);
}
}
#[test]
fn test_roundtrip_array() {
let json = json!([1, 2, 3, "hello", true, null]);
let doc = value_to_document(&json, &Config::default()).unwrap();
let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
assert_eq!(json, roundtrip);
}
#[test]
fn test_roundtrip_nested() {
let json = json!({"name": "test", "nested": {"a": 1, "b": [true, false]}});
let doc = value_to_document(&json, &Config::default()).unwrap();
let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
assert_eq!(json, roundtrip);
}
#[test]
fn test_roundtrip_deeply_nested() {
let json = json!({"Ok": {"Some": {"method": "add"}}});
let doc = value_to_document(&json, &Config::default()).unwrap();
let roundtrip = document_to_value(&doc, &Config::default()).unwrap();
assert_eq!(json, roundtrip);
}
}