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