forge_ir/value.rs
1//! Structured value used for defaults, examples, extensions, link
2//! parameters, and constraint values.
3//!
4//! Plugins do not link a JSON parser to read these — the host populates
5//! them from the parsed spec.
6//!
7//! # Pool design (issue #85 follow-up, ADR-0007 amendment realised)
8//!
9//! WIT does not support recursive variants. `list<value>` and
10//! `object(list<tuple<string, value>>)` cannot be represented at the
11//! boundary directly, so the compound arms hold [`ValueRef`] indices
12//! into [`crate::Ir::values`] rather than recursing by value. Every
13//! `Value`-shaped IR field stores a `ValueRef`; the pool is the only
14//! place that owns a `Value`.
15//!
16//! - `Value::List { items }` — `items: Vec<ValueRef>`
17//! - `Value::Object { fields }` — `fields: Vec<(String, ValueRef)>`
18//!
19//! The pool is structurally deduplicated by the parser: pushing a `Value`
20//! that is already present at index `i` returns `i`. SDK helpers
21//! (`resolve`, `to_json`) walk the pool to materialise tree-shaped
22//! representations on demand.
23//!
24//! See ADR-0006 for the parallel type-pool design.
25
26use serde::{Deserialize, Serialize};
27
28/// Index into [`crate::Ir::values`].
29pub type ValueRef = u32;
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32#[serde(tag = "kind", rename_all = "kebab-case")]
33pub enum Value {
34 Null,
35 Bool {
36 value: bool,
37 },
38 Int {
39 value: i64,
40 },
41 Float {
42 value: f64,
43 },
44 String {
45 value: String,
46 },
47 /// JSON array, stored as a list of pool indices.
48 List {
49 items: Vec<ValueRef>,
50 },
51 /// JSON object, stored as a list of `(key, pool-index)` pairs in
52 /// declared order.
53 Object {
54 fields: Vec<(String, ValueRef)>,
55 },
56}
57
58impl Value {
59 /// Convenience constructor for a string literal.
60 pub fn s(s: impl Into<String>) -> Self {
61 Value::String { value: s.into() }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn json_roundtrip_scalars() {
71 for v in [
72 Value::Null,
73 Value::Bool { value: true },
74 Value::Int { value: 42 },
75 Value::Float { value: 1.5 },
76 Value::s("hello"),
77 ] {
78 let s = serde_json::to_string(&v).unwrap();
79 let back: Value = serde_json::from_str(&s).unwrap();
80 assert_eq!(v, back);
81 }
82 }
83
84 #[test]
85 fn json_roundtrip_compound() {
86 let list = Value::List {
87 items: vec![0, 1, 2],
88 };
89 let obj = Value::Object {
90 fields: vec![("a".into(), 0), ("b".into(), 1)],
91 };
92 for v in [list, obj] {
93 let s = serde_json::to_string(&v).unwrap();
94 let back: Value = serde_json::from_str(&s).unwrap();
95 assert_eq!(v, back);
96 }
97 }
98}