use crate::{EsmFile, error::EsmError};
use std::path::Path;
pub fn to_json(esm_file: &EsmFile) -> Result<String, EsmError> {
let mut value = serde_json::to_value(esm_file).map_err(EsmError::JsonParse)?;
raise_faq_esm_floor(&mut value);
serde_json::to_string_pretty(&value).map_err(EsmError::JsonParse)
}
fn raise_faq_esm_floor(value: &mut serde_json::Value) {
fn has_faq(v: &serde_json::Value) -> bool {
match v {
serde_json::Value::Object(map) => {
map.get("op").and_then(|o| o.as_str()) == Some("faq") || map.values().any(has_faq)
}
serde_json::Value::Array(items) => items.iter().any(has_faq),
_ => false,
}
}
let below = value
.get("esm")
.and_then(|v| v.as_str())
.and_then(crate::diagnostic::parse_semver)
.is_some_and(|(major, minor, _)| (major, minor) < (1, 1));
if below
&& has_faq(value)
&& let Some(obj) = value.as_object_mut()
{
obj.insert(
"esm".to_string(),
serde_json::Value::String("1.1.0".to_string()),
);
}
}
pub fn to_json_compact(esm_file: &EsmFile) -> Result<String, EsmError> {
let mut value = serde_json::to_value(esm_file).map_err(EsmError::JsonParse)?;
raise_faq_esm_floor(&mut value);
serde_json::to_string(&value).map_err(EsmError::JsonParse)
}
pub fn write_path<P: AsRef<Path>>(esm_file: &EsmFile, path: P) -> Result<(), EsmError> {
let path = path.as_ref();
let json = to_json(esm_file)?;
std::fs::write(path, json).map_err(|e| EsmError::FileWrite {
path: path.display().to_string(),
source: e,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::var;
use crate::types::{Equation, Metadata, ModelVariable, VariableType};
use crate::{Expr, Model};
use indexmap::IndexMap;
#[test]
fn test_save_minimal_file() {
let esm_file = EsmFile {
esm: "0.1.0".to_string(),
metadata: Metadata {
name: Some("test_model".to_string()),
..Default::default()
},
..Default::default()
};
let result = to_json(&esm_file);
assert!(result.is_ok());
let json = result.unwrap();
assert!(json.contains("\"esm\": \"0.1.0\""));
assert!(json.contains("\"name\": \"test_model\""));
}
#[test]
fn test_save_with_model() {
let mut models = IndexMap::new();
let mut variables = IndexMap::new();
variables.insert(
"x".to_string(),
ModelVariable {
default: Some(0.0.into()),
..var(VariableType::Unknown, Some("m"))
},
);
models.insert(
"test".to_string(),
Model {
name: Some("Test Model".to_string()),
variables,
equations: vec![Equation {
comment: None,
lhs: Expr::Variable("d(x)/dt".to_string()),
rhs: Expr::Number(1.0),
}],
..Default::default()
},
);
let esm_file = EsmFile {
esm: "0.1.0".to_string(),
metadata: Metadata {
name: Some("test_model".to_string()),
..Default::default()
},
models: Some(models),
..Default::default()
};
let result = to_json(&esm_file);
assert!(result.is_ok());
let json = result.unwrap();
assert!(json.contains("\"models\""));
assert!(json.contains("\"test\""));
assert!(json.contains("\"variables\""));
assert!(json.contains("\"equations\""));
}
#[test]
fn test_save_compact() {
let esm_file = EsmFile {
esm: "0.1.0".to_string(),
metadata: Metadata {
name: Some("test_model".to_string()),
..Default::default()
},
..Default::default()
};
let result = to_json_compact(&esm_file);
assert!(result.is_ok());
let json = result.unwrap();
assert!(!json.contains(" "));
assert!(json.contains("\"esm\":\"0.1.0\""));
}
}