Skip to main content

active_core/convert/
mod.rs

1//! Conversion between Active Value and JSON / simple map representations
2
3use crate::ir::Value;
4use serde_json::{json, Value as JsonValue};
5
6pub mod doc;
7pub mod export;
8pub mod gen;
9pub mod import;
10
11pub use doc::{collect_items, doc_to_html, doc_to_man, doc_to_markdown, DocItem};
12
13pub use export::{value_to_toml_string, value_to_yaml_string};
14pub use gen::{
15    schema_to_go, schema_to_json_schema, schema_to_openapi, schema_to_rust, schema_to_typescript,
16};
17pub use import::{
18    json_text_to_active, json_value_to_active, toml_text_to_active, yaml_text_to_active,
19};
20
21pub fn value_to_json(v: &Value) -> JsonValue {
22    match v {
23        Value::Null => JsonValue::Null,
24        Value::Bool(b) => json!(b),
25        Value::Int(i) => json!(i),
26        Value::Float(f) => json!(f),
27        Value::String(s) => json!(s),
28        Value::DurationMs(ms) => json!({ "ms": ms }),
29        Value::Quantity(n, unit) => json!({ unit.suffix(): n }),
30        Value::Array(arr) => JsonValue::Array(arr.iter().map(value_to_json).collect()),
31        Value::Table(map) => {
32            let mut obj = serde_json::Map::new();
33            for (k, val) in map {
34                obj.insert(k.clone(), value_to_json(val));
35            }
36            JsonValue::Object(obj)
37        }
38        Value::Secret(_) => json!("***SECRET***"),
39    }
40}
41
42pub fn value_to_pretty_json(v: &Value) -> String {
43    serde_json::to_string_pretty(&value_to_json(v)).unwrap_or_else(|_| "{}".into())
44}
45
46/// Active brace-style emission (`key = value` lines plus `[table]`
47/// headers): doubles as the unit round-trip path (every suffix from
48/// `Unit::suffix()` re-parses — see `tests/unit_roundtrip.rs`), so it
49/// stays exactly as is. Real TOML interchange lives in
50/// `export::value_to_toml_string`.
51pub fn value_to_simple_toml(v: &Value, prefix: &str) -> String {
52    let mut out = String::new();
53    if let Value::Table(map) = v {
54        for (k, val) in map {
55            let path = if prefix.is_empty() {
56                k.clone()
57            } else {
58                format!("{}.{}", prefix, k)
59            };
60            match val {
61                Value::Table(_) => {
62                    out.push_str(&format!("\n[{}]\n", path));
63                    out.push_str(&value_to_simple_toml(val, &path));
64                }
65                _ => {
66                    out.push_str(&format!("{} = {}\n", k, value_to_toml_literal(val)));
67                }
68            }
69        }
70    }
71    out
72}
73
74fn value_to_toml_literal(v: &Value) -> String {
75    match v {
76        Value::Null => "null".into(),
77        Value::Bool(b) => b.to_string(),
78        Value::Int(i) => i.to_string(),
79        Value::Float(f) => f.to_string(),
80        Value::String(s) => format!("\"{}\"", s.replace('"', "\\\"")),
81        Value::DurationMs(ms) => format!("{}ms", ms),
82        Value::Quantity(n, unit) => unit.format_amount(*n),
83        Value::Secret(_) => "\"***SECRET***\"".into(),
84        Value::Array(arr) => {
85            let parts: Vec<String> = arr.iter().map(value_to_toml_literal).collect();
86            format!("[{}]", parts.join(", "))
87        }
88        Value::Table(_) => "{...}".into(),
89    }
90}