1#![allow(missing_docs)]
14
15use crate::error::{Error, Result};
16use crate::ir::{
17 Annotation, AnnotationKind, Constraint, InfinityModule, Inode, InterpPart, NodeId, PatchOp,
18 ProvEntry, ProvValue, Provenance, SchemaFieldSpec, TypeSpec,
19};
20use crate::layout::{DecodeOpts, ANN_KIND_AI_IMMUTABLE, ANN_KIND_CONSTRAINT, ANN_KIND_INTENT};
21use crate::validate::{validate, Validated};
22
23pub fn decode(bytes: &[u8]) -> Result<InfinityModule> {
25 decode_with(bytes, DecodeOpts::default())
26}
27
28pub fn decode_with(bytes: &[u8], opts: DecodeOpts) -> Result<InfinityModule> {
30 let validated = Validated::new(bytes, opts)?;
31 decode_validated(&validated)
32}
33
34pub fn decode_validated(validated: &Validated<'_>) -> Result<InfinityModule> {
36 let count = validated.node_count();
37 let mut nodes = Vec::with_capacity(count as usize);
38 for index in 0..count {
39 nodes.push(decode_node(validated, NodeId(index))?);
40 }
41 let root = validated.root()?;
42 let bindings: Vec<(String, NodeId)> = validated
43 .bindings()?
44 .into_iter()
45 .map(|(name, id)| (name.to_string(), id))
46 .collect();
47
48 let mut annotations =
49 Vec::with_capacity(validated.span(crate::layout::Section::Annotations).count as usize);
50 for index in 0..validated.span(crate::layout::Section::Annotations).count {
51 let (node, kind, a, b) = validated.annotation(index)?;
52 let kind = match kind {
53 ANN_KIND_INTENT => AnnotationKind::Intent(validated.string(a)?.to_string()),
54 ANN_KIND_CONSTRAINT => AnnotationKind::Constraint {
55 source: validated.string(a)?.to_string(),
56 expr: NodeId(b),
57 },
58 ANN_KIND_AI_IMMUTABLE => AnnotationKind::AiImmutable(a == 1),
59 _ => {
60 return Err(Error::Annotation {
61 id: index,
62 reason: "unknown annotation kind",
63 })
64 }
65 };
66 annotations.push(Annotation {
67 node: NodeId(node),
68 kind,
69 });
70 }
71
72 let provenance = validated.provenance()?;
73 let mut module = InfinityModule::from_parts(nodes, root, bindings, annotations, provenance);
74 module.set_extensions(decode_extensions(validated)?);
75 module.validate_structure(u32::MAX as u64, 0)?;
76 Ok(module)
77}
78
79fn decode_extensions(validated: &Validated<'_>) -> Result<Vec<(u64, Vec<u8>)>> {
81 let count = validated.span(crate::layout::Section::Extensions).count;
82 let mut out = Vec::with_capacity(count as usize);
83 for index in 0..count {
84 let (tag, offset, len) = validated.extension_entry(index)?;
85 out.push((tag, validated.extension_payload(offset, len)?.to_vec()));
86 }
87 Ok(out)
88}
89
90fn decode_node(validated: &Validated<'_>, id: NodeId) -> Result<Inode> {
91 let rec = validated.node(id)?;
92 let node = match rec.kind {
93 0 => Inode::Null,
94 1 => Inode::Bool(rec.a == 1),
95 2 => Inode::Int(rec.a as i64),
96 3 => Inode::Float(f64::from_bits(rec.a)),
97 4 => Inode::Str(validated.string(rec.aux)?.to_string()),
98 5 => Inode::DurationMs(rec.a),
99 6 => Inode::Quantity {
100 value: NodeId(rec.aux),
101 unit: validated.unit(rec.a as u32)?,
102 },
103 7 => {
104 let mut elements = Vec::with_capacity(rec.b as usize);
105 for offset in rec.a..rec.a + rec.b {
106 elements.push(NodeId(validated.child(offset as u32)?));
107 }
108 Inode::Array { elements }
109 }
110 8 => {
111 let mut fields = Vec::with_capacity(rec.b as usize);
112 for offset in rec.a..rec.a + rec.b {
113 let (name, node) = validated.field(offset as u32)?;
114 fields.push((validated.string(name)?.to_string(), node));
115 }
116 Inode::Table { fields }
117 }
118 9 => Inode::Secret(crate::ir::SecretBytes::new(
119 validated.secret(rec.a as u32)?.to_vec(),
120 )),
121 10 => Inode::SecretRef {
122 locator: NodeId(rec.a as u32),
123 },
124 11 => Inode::Ref(NodeId(rec.a as u32)),
125 12 => Inode::Env {
126 name: validated.string(rec.aux)?.to_string(),
127 default: if rec.a == u32::MAX as u64 {
128 None
129 } else {
130 Some(NodeId(rec.a as u32))
131 },
132 },
133 13 => Inode::Ident(validated.string(rec.aux)?.to_string()),
134 14 => {
135 let mut parts = Vec::with_capacity(rec.b as usize);
136 for offset in rec.a..rec.a + rec.b {
137 let (tag, value) = validated.part(offset as u32)?;
138 parts.push(match tag {
139 crate::layout::PART_LITERAL => {
140 InterpPart::Literal(validated.string(value)?.to_string())
141 }
142 _ => InterpPart::Expr(NodeId(value)),
143 });
144 }
145 Inode::Interpolate { parts }
146 }
147 15 => Inode::Binary {
148 op: crate::ir::BinOp::from_code(rec.aux).ok_or(Error::Node {
149 id: id.0,
150 reason: "unknown binary operator",
151 })?,
152 lhs: NodeId(rec.a as u32),
153 rhs: NodeId(rec.b as u32),
154 },
155 16 => Inode::Unary {
156 op: crate::ir::UnOp::from_code(rec.aux).ok_or(Error::Node {
157 id: id.0,
158 reason: "unknown unary operator",
159 })?,
160 expr: NodeId(rec.a as u32),
161 },
162 17 => Inode::Constrained {
163 target: NodeId(rec.a as u32),
164 constraint: decode_constraint(validated, id, rec.aux, rec.b as u32, rec.c as u32)?,
165 },
166 18 => Inode::Patch(decode_patch(
167 validated,
168 id,
169 rec.aux,
170 rec.b as u32,
171 rec.c as u32,
172 )?),
173 19 => Inode::SchemaField(decode_schema_field(
174 validated,
175 id,
176 rec.aux,
177 rec.b as u32,
178 rec.c as u32,
179 )?),
180 20 => {
181 let mut fields = Vec::with_capacity(rec.b as usize);
182 for offset in rec.a..rec.a + rec.b {
183 let (name, node) = validated.field(offset as u32)?;
184 fields.push((validated.string(name)?.to_string(), node));
185 }
186 Inode::SchemaTable {
187 fields,
188 closed: rec.flags & crate::layout::NODE_FLAG_0 != 0,
189 }
190 }
191 21 => Inode::SchemaArray {
192 element: NodeId(rec.a as u32),
193 },
194 22 => Inode::Semantic {
195 tag: crate::ir::SemanticTag::from_code(rec.aux).ok_or(Error::Node {
196 id: id.0,
197 reason: "unknown semantic tag",
198 })?,
199 inner: NodeId(rec.a as u32),
200 },
201 23 => {
202 let (hash, _, _) = validated.blob_entry(rec.a as u32)?;
203 let payload = if rec.flags & crate::layout::NODE_FLAG_1 != 0 {
204 Some(validated.blob(rec.a as u32)?.to_vec())
205 } else {
206 None
207 };
208 Inode::Blob {
209 hash,
210 size: rec.b,
211 tainted: rec.flags & crate::layout::NODE_FLAG_0 != 0,
212 media_type: validated.string(rec.aux)?.to_string(),
213 payload,
214 }
215 }
216 24 => Inode::ContentAddressed {
217 media_type: NodeId(rec.a as u32),
218 data: NodeId(rec.b as u32),
219 },
220 _ => {
221 return Err(Error::Node {
222 id: id.0,
223 reason: "unknown node kind",
224 })
225 }
226 };
227 Ok(node)
228}
229
230fn decode_constraint(
231 validated: &Validated<'_>,
232 id: NodeId,
233 kind: u32,
234 start: u32,
235 len: u32,
236) -> Result<Constraint> {
237 let mut min = None;
238 let mut max = None;
239 let mut items = Vec::new();
240 let mut pattern = None;
241 let mut custom = None;
242 for offset in start..start + len {
243 let (tag, value) = validated.aux(offset)?;
244 match tag {
245 crate::layout::AUX_CONSTRAINT_MIN => min = Some(validated.number(value)?),
246 crate::layout::AUX_CONSTRAINT_MAX => max = Some(validated.number(value)?),
247 crate::layout::AUX_CONSTRAINT_ENUM_ITEM => {
248 items.push(validated.string(value)?.to_string())
249 }
250 crate::layout::AUX_CONSTRAINT_PATTERN => {
251 pattern = Some(validated.string(value)?.to_string())
252 }
253 crate::layout::AUX_CONSTRAINT_CUSTOM => {
254 custom = Some(validated.string(value)?.to_string())
255 }
256 _ => {
257 return Err(Error::Node {
258 id: id.0,
259 reason: "unknown constraint aux tag",
260 })
261 }
262 }
263 }
264 let constraint = match kind {
265 crate::layout::CONSTRAINT_KIND_MIN_MAX => Constraint::MinMax { min, max },
266 crate::layout::CONSTRAINT_KIND_ENUM => Constraint::Enum(items),
267 crate::layout::CONSTRAINT_KIND_PATTERN => {
268 Constraint::Pattern(pattern.ok_or(Error::Node {
269 id: id.0,
270 reason: "pattern constraint without a pattern",
271 })?)
272 }
273 crate::layout::CONSTRAINT_KIND_ODD => Constraint::Odd,
274 crate::layout::CONSTRAINT_KIND_CUSTOM => Constraint::Custom(custom.ok_or(Error::Node {
275 id: id.0,
276 reason: "custom constraint without a payload",
277 })?),
278 _ => {
279 return Err(Error::Node {
280 id: id.0,
281 reason: "unknown constraint kind",
282 })
283 }
284 };
285 Ok(constraint)
286}
287
288fn decode_patch(
289 validated: &Validated<'_>,
290 id: NodeId,
291 kind: u32,
292 start: u32,
293 len: u32,
294) -> Result<PatchOp> {
295 let mut path = None;
296 let mut value = None;
297 let mut to = None;
298 for offset in start..start + len {
299 let (tag, item) = validated.aux(offset)?;
300 match tag {
301 crate::layout::AUX_PATCH_PATH => path = Some(validated.string(item)?.to_string()),
302 crate::layout::AUX_PATCH_VALUE => value = Some(NodeId(item)),
303 crate::layout::AUX_PATCH_RENAME_TO => to = Some(validated.string(item)?.to_string()),
304 _ => {
305 return Err(Error::Node {
306 id: id.0,
307 reason: "unknown patch aux tag",
308 })
309 }
310 }
311 }
312 let path = path.ok_or(Error::Node {
313 id: id.0,
314 reason: "patch without a path",
315 })?;
316 let op = match kind {
317 crate::layout::PATCH_KIND_SET => PatchOp::Set {
318 path,
319 value: value.ok_or(Error::Node {
320 id: id.0,
321 reason: "set patch without a value",
322 })?,
323 },
324 crate::layout::PATCH_KIND_REMOVE => PatchOp::Remove { path },
325 crate::layout::PATCH_KIND_RENAME => PatchOp::Rename {
326 from: path,
327 to: to.ok_or(Error::Node {
328 id: id.0,
329 reason: "rename patch without a destination",
330 })?,
331 },
332 _ => {
333 return Err(Error::Node {
334 id: id.0,
335 reason: "unknown patch kind",
336 })
337 }
338 };
339 Ok(op)
340}
341
342fn decode_schema_field(
343 validated: &Validated<'_>,
344 id: NodeId,
345 type_id: u32,
346 start: u32,
347 len: u32,
348) -> Result<SchemaFieldSpec> {
349 let typ = decode_type(validated, type_id)?;
350 let mut spec = SchemaFieldSpec::bare(typ);
351 for offset in start..start + len {
352 let (tag, value) = validated.aux(offset)?;
353 match tag {
354 crate::layout::AUX_SCHEMA_MIN => spec.min = Some(NodeId(value)),
355 crate::layout::AUX_SCHEMA_MAX => spec.max = Some(NodeId(value)),
356 crate::layout::AUX_SCHEMA_MIN_LEN => spec.min_len = Some(NodeId(value)),
357 crate::layout::AUX_SCHEMA_MAX_LEN => spec.max_len = Some(NodeId(value)),
358 crate::layout::AUX_SCHEMA_PATTERN => spec.pattern = Some(NodeId(value)),
359 crate::layout::AUX_SCHEMA_DEFAULT => spec.default = Some(NodeId(value)),
360 crate::layout::AUX_SCHEMA_DEPRECATED => {
361 spec.deprecated = Some(validated.string(value)?.to_string())
362 }
363 _ => {
364 return Err(Error::Node {
365 id: id.0,
366 reason: "unknown schema aux tag",
367 })
368 }
369 }
370 }
371 Ok(spec)
372}
373
374fn decode_type(validated: &Validated<'_>, index: u32) -> Result<TypeSpec> {
375 let (code, flags, start, len) = validated.type_entry(index)?;
376 let spec = match code {
377 0 => TypeSpec::Null,
378 1 => TypeSpec::Bool,
379 2 => TypeSpec::Int,
380 3 => TypeSpec::Float,
381 4 => TypeSpec::String,
382 5 => TypeSpec::Duration,
383 6 => TypeSpec::Bytes,
384 7 => TypeSpec::Table {
385 closed: flags & 1 != 0,
386 },
387 8 => {
388 let mut items = Vec::new();
389 for offset in start..start + len {
390 let (tag, value) = validated.aux(offset)?;
391 if tag != crate::layout::AUX_TYPE_ENUM_ITEM {
392 return Err(Error::Type {
393 id: index,
394 reason: "unexpected aux record",
395 });
396 }
397 items.push(validated.string(value)?.to_string());
398 }
399 TypeSpec::Enum(items)
400 }
401 9 => TypeSpec::Secret,
402 10 => TypeSpec::Any,
403 11 => TypeSpec::Blob,
404 _ => {
405 return Err(Error::Type {
406 id: index,
407 reason: "unknown type code",
408 })
409 }
410 };
411 Ok(spec)
412}
413
414pub fn decode_provenance(bytes: &[u8]) -> Result<Option<Provenance>> {
416 let meta = validate(bytes)?;
417 let _ = meta;
418 let validated = Validated::new(bytes, DecodeOpts::default())?;
419 validated.provenance()
420}
421
422pub fn decode_annotations(bytes: &[u8]) -> Result<Vec<Annotation>> {
424 let validated = Validated::new(bytes, DecodeOpts::default())?;
425 let mut out = Vec::new();
426 for index in 0..validated.span(crate::layout::Section::Annotations).count {
427 let (node, kind, a, b) = validated.annotation(index)?;
428 let kind = match kind {
429 ANN_KIND_INTENT => AnnotationKind::Intent(validated.string(a)?.to_string()),
430 ANN_KIND_CONSTRAINT => AnnotationKind::Constraint {
431 source: validated.string(a)?.to_string(),
432 expr: NodeId(b),
433 },
434 _ => AnnotationKind::AiImmutable(a == 1),
435 };
436 out.push(Annotation {
437 node: NodeId(node),
438 kind,
439 });
440 }
441 Ok(out)
442}
443
444pub fn provenance_pairs(prov: &Provenance) -> Vec<(String, ProvValue)> {
446 let mut pairs: Vec<(String, ProvValue)> = prov
447 .entries
448 .iter()
449 .map(|entry: &ProvEntry| (entry.key.clone(), entry.value.clone()))
450 .collect();
451 pairs.sort_by(|a, b| a.0.cmp(&b.0));
452 pairs
453}