Skip to main content

synx_core/
value.rs

1//! SYNX value types, metadata, and options.
2
3use std::collections::HashMap;
4
5/// SYNX value types.
6#[derive(Clone, PartialEq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[cfg_attr(feature = "serde", serde(untagged))]
9pub enum Value {
10    String(std::string::String),
11    Int(i64),
12    Float(f64),
13    Bool(bool),
14    Null,
15    Array(Vec<Value>),
16    Object(HashMap<std::string::String, Value>),
17    /// Secret value — displays as [SECRET], real value accessible via as_secret()
18    Secret(std::string::String),
19}
20
21// Hand-written so `{:?}` in a log line never prints a secret.
22impl std::fmt::Debug for Value {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            Value::String(s) => f.debug_tuple("String").field(s).finish(),
26            Value::Int(i) => f.debug_tuple("Int").field(i).finish(),
27            Value::Float(x) => f.debug_tuple("Float").field(x).finish(),
28            Value::Bool(b) => f.debug_tuple("Bool").field(b).finish(),
29            Value::Null => f.write_str("Null"),
30            Value::Array(a) => f.debug_tuple("Array").field(a).finish(),
31            Value::Object(o) => f.debug_tuple("Object").field(o).finish(),
32            Value::Secret(_) => f.write_str("Secret([SECRET])"),
33        }
34    }
35}
36
37impl Value {
38    pub fn as_str(&self) -> Option<&str> {
39        match self {
40            Value::String(s) | Value::Secret(s) => Some(s),
41            _ => None,
42        }
43    }
44
45    pub fn as_int(&self) -> Option<i64> {
46        match self {
47            Value::Int(n) => Some(*n),
48            _ => None,
49        }
50    }
51
52    pub fn as_float(&self) -> Option<f64> {
53        match self {
54            Value::Float(f) => Some(*f),
55            Value::Int(n) => Some(*n as f64),
56            _ => None,
57        }
58    }
59
60    pub fn as_bool(&self) -> Option<bool> {
61        match self {
62            Value::Bool(b) => Some(*b),
63            _ => None,
64        }
65    }
66
67    pub fn as_object(&self) -> Option<&HashMap<std::string::String, Value>> {
68        match self {
69            Value::Object(m) => Some(m),
70            _ => None,
71        }
72    }
73
74    pub fn as_object_mut(&mut self) -> Option<&mut HashMap<std::string::String, Value>> {
75        match self {
76            Value::Object(m) => Some(m),
77            _ => None,
78        }
79    }
80
81    pub fn as_array(&self) -> Option<&Vec<Value>> {
82        match self {
83            Value::Array(a) => Some(a),
84            _ => None,
85        }
86    }
87
88    pub fn as_array_mut(&mut self) -> Option<&mut Vec<Value>> {
89        match self {
90            Value::Array(a) => Some(a),
91            _ => None,
92        }
93    }
94
95    pub fn as_secret(&self) -> Option<&str> {
96        match self {
97            Value::Secret(s) => Some(s),
98            _ => None,
99        }
100    }
101
102    pub fn is_null(&self) -> bool {
103        matches!(self, Value::Null)
104    }
105
106    pub fn as_number_f64(&self) -> Option<f64> {
107        match self {
108            Value::Int(n) => Some(*n as f64),
109            Value::Float(f) => Some(*f),
110            _ => None,
111        }
112    }
113}
114
115impl std::fmt::Display for Value {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        match self {
118            Value::String(s) | Value::Secret(s) => write!(f, "{}", s),
119            Value::Int(n) => write!(f, "{}", n),
120            Value::Float(fl) => {
121                let s = fl.to_string();
122                if s.contains('.') { write!(f, "{}", s) } else { write!(f, "{}.0", s) }
123            }
124            Value::Bool(b) => write!(f, "{}", b),
125            Value::Null => write!(f, "null"),
126            Value::Array(arr) => {
127                write!(f, "[")?;
128                for (i, item) in arr.iter().enumerate() {
129                    if i > 0 { write!(f, ", ")?; }
130                    write!(f, "{}", item)?;
131                }
132                write!(f, "]")
133            }
134            Value::Object(_) => write!(f, "[Object]"),
135        }
136    }
137}
138
139impl std::ops::Index<&str> for Value {
140    type Output = Value;
141    fn index(&self, key: &str) -> &Value {
142        match self {
143            Value::Object(map) => map.get(key).expect("key not found"),
144            _ => panic!("not an object"),
145        }
146    }
147}
148
149/// File mode.
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum Mode {
152    Static,
153    Active,
154}
155
156/// Metadata for a single key (markers, args, constraints).
157#[derive(Debug, Clone, PartialEq)]
158pub struct Meta {
159    pub markers: Vec<String>,
160    pub args: Vec<String>,
161    pub type_hint: Option<String>,
162    pub constraints: Option<Constraints>,
163}
164
165/// Constraints from [min:3, max:30, required, type:int].
166#[derive(Debug, Clone, Default, PartialEq)]
167pub struct Constraints {
168    pub min: Option<f64>,
169    pub max: Option<f64>,
170    pub type_name: Option<String>,
171    pub required: bool,
172    pub readonly: bool,
173    pub pattern: Option<String>,
174    pub enum_values: Option<Vec<String>>,
175}
176
177/// Map of key → metadata for one object level.
178pub type MetaMap = HashMap<String, Meta>;
179
180/// Include directive: !include path [alias]
181#[derive(Debug, Clone)]
182pub struct IncludeDirective {
183    pub path: String,
184    pub alias: String,
185}
186
187/// Use directive: !use @scope/name [as alias]
188#[derive(Debug, Clone)]
189pub struct UseDirective {
190    /// Full package name, e.g. "@aperture/synx-defaults"
191    pub package: String,
192    /// Namespace alias (defaults to last segment: "synx-defaults")
193    pub alias: String,
194}
195
196/// Full parse result with metadata.
197#[derive(Debug)]
198pub struct ParseResult {
199    pub root: Value,
200    pub mode: Mode,
201    pub locked: bool,
202    /// `!tool` directive — file describes an LLM tool call or schema.
203    pub tool: bool,
204    /// `!schema` directive (combined with `!tool`) — file describes tool definitions.
205    pub schema: bool,
206    /// `!llm` directive — file is an LLM-oriented envelope (semantic hints for tools; data tree unchanged).
207    pub llm: bool,
208    /// Metadata for each nesting level, keyed by dot-path prefix.
209    /// "" = root level, "server" = server sub-object, etc.
210    pub metadata: HashMap<String, MetaMap>,
211    /// !include directives parsed from the file.
212    pub includes: Vec<IncludeDirective>,
213    /// !use directives parsed from the file (package imports).
214    pub uses: Vec<UseDirective>,
215    /// One of the parser's resource limits dropped input: the total byte cap,
216    /// the indexed-line cap, or a multiline block body cap.
217    ///
218    /// Callers that treat "parsed without complaint" as "nothing was lost"
219    /// need this — SYNXL §11.2 turns it into a `RecordTruncated` diagnostic
220    /// rather than discarding the overflow in silence.
221    pub truncated: bool,
222}
223
224/// Options for active mode resolution.
225#[derive(Debug, Clone, Default)]
226pub struct Options {
227    pub env: Option<HashMap<String, String>>,
228    pub region: Option<String>,
229    pub lang: Option<String>,
230    pub base_path: Option<String>,
231    /// Maximum depth for nested :include / :import / :watch file reads (default: 16).
232    pub max_include_depth: Option<usize>,
233    /// Internal counter — do not set manually.
234    pub _include_depth: usize,
235    /// Path to installed packages directory (default: "./synx_packages").
236    pub packages_path: Option<String>,
237    /// Loaded WASM marker runtime (populated by engine from `type markers` packages).
238    #[cfg(feature = "wasm")]
239    pub wasm_runtime: Option<std::sync::Arc<crate::wasm::WasmMarkerRuntime>>,
240}