1use std::collections::{BTreeMap, BTreeSet};
55use std::sync::Arc;
56
57use beck_diag::{FileId, Span};
58use serde::{Deserialize, Serialize};
59
60use crate::command;
61use crate::core::{Arm, Const, Core, CoreKind, Pattern, Prim, VarId};
62use crate::split::Placed;
63use crate::ty::{Tier, Ty};
64
65pub const FORMAT: u32 = 2;
73
74#[derive(Clone, Debug)]
76pub struct Bundle {
77 pub component: Arc<str>,
80 pub wire_id: String,
84 pub view: Core,
86 pub validate: Core,
88 pub fold: Core,
90 pub init: Core,
93 pub defs: BTreeMap<Arc<str>, Core>,
95 pub command: command::Schema,
98 pub optimistic: bool,
102 pub reads_freshness: bool,
111 pub gestures: Option<Gestures>,
119}
120
121#[derive(Clone, Debug)]
123pub struct Gestures {
124 pub step: Core,
126 pub init: Core,
129 pub schema: command::Schema,
136}
137
138#[derive(Clone, Debug, PartialEq, Eq)]
140pub enum BadBundle {
141 Format {
143 found: u32,
144 expected: u32,
145 },
146 Shape {
148 found: String,
149 expected: String,
150 },
151 Malformed(String),
152}
153
154impl std::fmt::Display for BadBundle {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 match self {
157 BadBundle::Format { found, expected } => write!(
158 f,
159 "this bundle is format {found} and this kernel reads format {expected}"
160 ),
161 BadBundle::Shape { found, expected } => write!(
162 f,
163 "this bundle was compiled by a different compiler \
164 (primitives {found}, this kernel {expected})"
165 ),
166 BadBundle::Malformed(why) => write!(f, "this bundle is malformed: {why}"),
167 }
168 }
169}
170
171impl std::error::Error for BadBundle {}
172
173pub fn shape_id() -> String {
177 let mut hasher = blake3::Hasher::new();
178 for (_, prim, _) in crate::prelude::prims() {
179 hasher.update(prim.name().as_bytes());
180 hasher.update(b"=");
181 hasher.update((prim as u32).to_le_bytes().as_slice());
182 hasher.update(b";");
183 }
184 hasher.finalize().to_hex()[..16].to_string()
185}
186
187impl Bundle {
188 pub fn of(placed: &Placed) -> Bundle {
190 let roles = &placed.roles;
191 let mut defs = BTreeMap::new();
192 let mut seen = BTreeSet::new();
193 for role in [&roles.view, &roles.validate, &roles.fold, &roles.init] {
194 reachable(role, placed, &mut seen, &mut defs);
195 }
196 if let Some(g) = &roles.gestures {
200 reachable(&g.step, placed, &mut seen, &mut defs);
201 reachable(&g.init, placed, &mut seen, &mut defs);
202 }
203 Bundle {
204 component: roles.page_name.clone(),
205 wire_id: placed.wire_id.clone(),
206 view: roles.view.clone(),
207 validate: roles.validate.clone(),
208 fold: roles.fold.clone(),
209 init: roles.init.clone(),
210 defs,
211 command: command::Schema::of(placed),
212 optimistic: placed.render.optimistic,
215 reads_freshness: placed.render.reads_freshness,
216 gestures: roles.gestures.as_ref().map(|g| Gestures {
217 step: g.step.clone(),
218 init: g.init.clone(),
219 schema: command::Schema::of_union(
220 placed,
221 g.gesture_ty.con_name().unwrap_or_default(),
222 ),
223 }),
224 }
225 }
226
227 pub fn to_bytes(&self) -> Vec<u8> {
228 postcard::to_allocvec(&Wire::of(self)).expect("a bundle is encodable")
231 }
232
233 pub fn from_bytes(bytes: &[u8]) -> Result<Bundle, BadBundle> {
234 let wire: Wire =
235 postcard::from_bytes(bytes).map_err(|e| BadBundle::Malformed(e.to_string()))?;
236 if wire.format != FORMAT {
237 return Err(BadBundle::Format {
238 found: wire.format,
239 expected: FORMAT,
240 });
241 }
242 let expected = shape_id();
243 if wire.shape != expected {
244 return Err(BadBundle::Shape {
245 found: wire.shape,
246 expected,
247 });
248 }
249 Ok(wire.to_bundle())
250 }
251
252 pub fn nodes(&self) -> usize {
255 let mut n = 0;
256 for code in [&self.view, &self.validate, &self.fold, &self.init]
257 .into_iter()
258 .chain(self.defs.values())
259 {
260 count(code, &mut n);
261 }
262 n
263 }
264}
265
266fn reachable(
268 code: &Core,
269 placed: &Placed,
270 seen: &mut BTreeSet<Arc<str>>,
271 defs: &mut BTreeMap<Arc<str>, Core>,
272) {
273 let mut names = Vec::new();
274 globals(code, &mut names);
275 for name in names {
276 if !seen.insert(name.clone()) {
277 continue;
278 }
279 let Some(def) = placed.program.defs.get(&name) else {
283 continue;
284 };
285 defs.insert(name, def.body.clone());
286 reachable(&def.body, placed, seen, defs);
287 }
288}
289
290fn globals(code: &Core, out: &mut Vec<Arc<str>>) {
291 if let CoreKind::Global(name) = &code.kind {
292 out.push(name.clone());
293 }
294 walk(code, &mut |c| globals(c, out));
295}
296
297fn count(code: &Core, n: &mut usize) {
298 *n += 1;
299 walk(code, &mut |c| count(c, n));
300}
301
302fn walk(code: &Core, f: &mut dyn FnMut(&Core)) {
308 match &code.kind {
309 CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => {}
310 CoreKind::Lam { body, .. } => f(body),
311 CoreKind::App { func, args } => {
312 f(func);
313 args.iter().for_each(&mut *f);
314 }
315 CoreKind::Prim { args, .. } => args.iter().for_each(&mut *f),
316 CoreKind::Let { value, body, .. } => {
317 f(value);
318 f(body);
319 }
320 CoreKind::If { cond, then, alt } => {
321 f(cond);
322 f(then);
323 f(alt);
324 }
325 CoreKind::Match { scrutinee, arms } => {
326 f(scrutinee);
327 for arm in arms {
328 arm.exprs().for_each(&mut *f);
329 }
330 }
331 CoreKind::Make { fields, .. } => fields.iter().for_each(|(_, c)| f(c)),
332 CoreKind::Field { base, .. } => f(base),
333 CoreKind::With { base, fields } => {
334 f(base);
335 fields.iter().for_each(|(_, c)| f(c));
336 }
337 CoreKind::ListLit(xs) => xs.iter().for_each(&mut *f),
338 CoreKind::MapLit(kvs) => kvs.iter().for_each(|(k, v)| {
339 f(k);
340 f(v);
341 }),
342 }
343}
344
345#[derive(Serialize, Deserialize)]
348struct Wire {
349 format: u32,
350 shape: String,
351 component: String,
352 wire_id: String,
353 view: WCore,
354 validate: WCore,
355 fold: WCore,
356 init: WCore,
357 defs: Vec<(String, WCore)>,
358 command: command::Schema,
359 optimistic: bool,
360 reads_freshness: bool,
361 gestures: Option<(WCore, WCore, command::Schema)>,
362}
363
364impl Wire {
365 fn of(b: &Bundle) -> Wire {
366 Wire {
367 format: FORMAT,
368 shape: shape_id(),
369 component: b.component.to_string(),
370 wire_id: b.wire_id.clone(),
371 view: WCore::of(&b.view),
372 validate: WCore::of(&b.validate),
373 fold: WCore::of(&b.fold),
374 init: WCore::of(&b.init),
375 defs: b
376 .defs
377 .iter()
378 .map(|(name, code)| (name.to_string(), WCore::of(code)))
379 .collect(),
380 command: b.command.clone(),
381 optimistic: b.optimistic,
382 reads_freshness: b.reads_freshness,
383 gestures: b
384 .gestures
385 .as_ref()
386 .map(|g| (WCore::of(&g.step), WCore::of(&g.init), g.schema.clone())),
387 }
388 }
389
390 fn to_bundle(&self) -> Bundle {
391 Bundle {
392 component: Arc::from(self.component.as_str()),
393 wire_id: self.wire_id.clone(),
394 view: self.view.to_core(),
395 validate: self.validate.to_core(),
396 fold: self.fold.to_core(),
397 init: self.init.to_core(),
398 defs: self
399 .defs
400 .iter()
401 .map(|(name, code)| (Arc::from(name.as_str()), code.to_core()))
402 .collect(),
403 command: self.command.clone(),
404 optimistic: self.optimistic,
405 reads_freshness: self.reads_freshness,
406 gestures: self.gestures.as_ref().map(|(step, init, schema)| Gestures {
407 step: step.to_core(),
408 init: init.to_core(),
409 schema: schema.clone(),
410 }),
411 }
412 }
413}
414
415#[derive(Serialize, Deserialize)]
417struct WCore {
418 kind: WKind,
419 span: (u32, u32, u32),
421 tier: u8,
422 last_use: bool,
426 order: u32,
427 locals: u32,
428}
429
430#[derive(Serialize, Deserialize)]
431enum WKind {
432 Const(WConst),
433 Var(VarId),
434 Global(String),
435 Lam {
436 params: Vec<VarId>,
437 body: Box<WCore>,
438 },
439 App {
440 func: Box<WCore>,
441 args: Vec<WCore>,
442 },
443 Prim {
444 op: WPrim,
445 args: Vec<WCore>,
446 },
447 Let {
448 var: VarId,
449 value: Box<WCore>,
450 body: Box<WCore>,
451 },
452 If {
453 cond: Box<WCore>,
454 then: Box<WCore>,
455 alt: Box<WCore>,
456 },
457 Match {
458 scrutinee: Box<WCore>,
459 arms: Vec<WArm>,
460 },
461 Make {
462 ty: String,
463 variant: Option<String>,
464 fields: Vec<(String, WCore)>,
465 },
466 Field {
467 base: Box<WCore>,
468 name: String,
469 },
470 With {
471 base: Box<WCore>,
472 fields: Vec<(String, WCore)>,
473 },
474 ListLit(Vec<WCore>),
475 MapLit(Vec<(WCore, WCore)>),
476}
477
478#[derive(Serialize, Deserialize)]
479enum WConst {
480 Unit,
481 Bool(bool),
482 Int(i64),
483 Float(u64),
485 Str(String),
486}
487
488#[derive(Serialize, Deserialize)]
489struct WArm {
490 pattern: WPattern,
491 guard: Option<WCore>,
492 body: WCore,
493 span: (u32, u32, u32),
494}
495
496#[derive(Serialize, Deserialize)]
497enum WPattern {
498 Wildcard,
499 Bind(VarId),
500 Const(WConst),
501 Ctor {
502 variant: String,
503 binds: Vec<(String, WPattern)>,
504 },
505 At {
506 var: VarId,
507 inner: Box<WPattern>,
508 },
509 Or(Vec<WPattern>),
510 List {
511 items: Vec<WPattern>,
512 rest: Option<Option<VarId>>,
513 },
514}
515
516fn span_of(s: Span) -> (u32, u32, u32) {
517 (s.file.0, s.start, s.end)
518}
519
520fn to_span(s: (u32, u32, u32)) -> Span {
521 Span {
522 file: FileId(s.0),
523 start: s.1,
524 end: s.2,
525 }
526}
527
528impl WConst {
529 fn of(c: &Const) -> WConst {
530 match c {
531 Const::Unit => WConst::Unit,
532 Const::Bool(b) => WConst::Bool(*b),
533 Const::Int(i) => WConst::Int(*i),
534 Const::Float(f) => WConst::Float(f.to_bits()),
535 Const::Str(s) => WConst::Str(s.to_string()),
536 }
537 }
538
539 fn to_const(&self) -> Const {
540 match self {
541 WConst::Unit => Const::Unit,
542 WConst::Bool(b) => Const::Bool(*b),
543 WConst::Int(i) => Const::Int(*i),
544 WConst::Float(bits) => Const::Float(f64::from_bits(*bits)),
545 WConst::Str(s) => Const::Str(Arc::from(s.as_str())),
546 }
547 }
548}
549
550impl WPattern {
551 fn of(p: &Pattern) -> WPattern {
552 match p {
553 Pattern::Wildcard => WPattern::Wildcard,
554 Pattern::Bind(v) => WPattern::Bind(*v),
555 Pattern::Const(c) => WPattern::Const(WConst::of(c)),
556 Pattern::Ctor { variant, binds } => WPattern::Ctor {
557 variant: variant.to_string(),
558 binds: binds
559 .iter()
560 .map(|(f, p)| (f.to_string(), WPattern::of(p)))
561 .collect(),
562 },
563 Pattern::At { var, inner } => WPattern::At {
564 var: *var,
565 inner: Box::new(WPattern::of(inner)),
566 },
567 Pattern::Or(alts) => WPattern::Or(alts.iter().map(WPattern::of).collect()),
568 Pattern::List { items, rest } => WPattern::List {
569 items: items.iter().map(WPattern::of).collect(),
570 rest: *rest,
571 },
572 }
573 }
574
575 fn to_pattern(&self) -> Pattern {
576 match self {
577 WPattern::Wildcard => Pattern::Wildcard,
578 WPattern::Bind(v) => Pattern::Bind(*v),
579 WPattern::Const(c) => Pattern::Const(c.to_const()),
580 WPattern::Ctor { variant, binds } => Pattern::Ctor {
581 variant: Arc::from(variant.as_str()),
582 binds: binds
583 .iter()
584 .map(|(f, p)| (Arc::from(f.as_str()), p.to_pattern()))
585 .collect(),
586 },
587 WPattern::At { var, inner } => Pattern::At {
588 var: *var,
589 inner: Box::new(inner.to_pattern()),
590 },
591 WPattern::Or(alts) => Pattern::Or(alts.iter().map(WPattern::to_pattern).collect()),
592 WPattern::List { items, rest } => Pattern::List {
593 items: items.iter().map(WPattern::to_pattern).collect(),
594 rest: *rest,
595 },
596 }
597 }
598}
599
600impl WCore {
601 fn of(c: &Core) -> WCore {
602 WCore {
603 kind: WKind::of(&c.kind),
604 span: span_of(c.span),
605 tier: c.tier as u8,
606 last_use: c.last_use,
607 order: c.order,
608 locals: c.locals,
609 }
610 }
611
612 fn to_core(&self) -> Core {
613 let mut core = Core::new(self.kind.to_kind(), Ty::unit(), to_span(self.span));
617 core.tier = tier_of(self.tier);
618 core.last_use = self.last_use;
619 core.order = self.order;
620 core.locals = self.locals;
621 core
622 }
623}
624
625fn tier_of(byte: u8) -> Tier {
626 match byte {
630 b if b == Tier::Client as u8 => Tier::Client,
631 b if b == Tier::Server as u8 => Tier::Server,
632 b if b == Tier::Data as u8 => Tier::Data,
633 _ => Tier::Any,
634 }
635}
636
637impl WKind {
638 fn of(k: &CoreKind) -> WKind {
639 let fields = |fs: &Vec<(Arc<str>, Core)>| {
640 fs.iter()
641 .map(|(n, c)| (n.to_string(), WCore::of(c)))
642 .collect()
643 };
644 match k {
645 CoreKind::Const(c) => WKind::Const(WConst::of(c)),
646 CoreKind::Var(v) => WKind::Var(*v),
647 CoreKind::Global(name) => WKind::Global(name.to_string()),
648 CoreKind::Lam { params, body } => WKind::Lam {
649 params: params.to_vec(),
650 body: Box::new(WCore::of(body)),
651 },
652 CoreKind::App { func, args } => WKind::App {
653 func: Box::new(WCore::of(func)),
654 args: args.iter().map(WCore::of).collect(),
655 },
656 CoreKind::Prim { op, args } => WKind::Prim {
657 op: WPrim(*op),
658 args: args.iter().map(WCore::of).collect(),
659 },
660 CoreKind::Let { var, value, body } => WKind::Let {
661 var: *var,
662 value: Box::new(WCore::of(value)),
663 body: Box::new(WCore::of(body)),
664 },
665 CoreKind::If { cond, then, alt } => WKind::If {
666 cond: Box::new(WCore::of(cond)),
667 then: Box::new(WCore::of(then)),
668 alt: Box::new(WCore::of(alt)),
669 },
670 CoreKind::Match { scrutinee, arms } => WKind::Match {
671 scrutinee: Box::new(WCore::of(scrutinee)),
672 arms: arms
673 .iter()
674 .map(|a| WArm {
675 pattern: WPattern::of(&a.pattern),
676 guard: a.guard.as_ref().map(WCore::of),
677 body: WCore::of(&a.body),
678 span: span_of(a.span),
679 })
680 .collect(),
681 },
682 CoreKind::Make {
683 ty,
684 variant,
685 fields: fs,
686 } => WKind::Make {
687 ty: ty.to_string(),
688 variant: variant.as_ref().map(|v| v.to_string()),
689 fields: fields(fs),
690 },
691 CoreKind::Field { base, name } => WKind::Field {
692 base: Box::new(WCore::of(base)),
693 name: name.to_string(),
694 },
695 CoreKind::With { base, fields: fs } => WKind::With {
696 base: Box::new(WCore::of(base)),
697 fields: fields(fs),
698 },
699 CoreKind::ListLit(xs) => WKind::ListLit(xs.iter().map(WCore::of).collect()),
700 CoreKind::MapLit(kvs) => WKind::MapLit(
701 kvs.iter()
702 .map(|(k, v)| (WCore::of(k), WCore::of(v)))
703 .collect(),
704 ),
705 }
706 }
707
708 fn to_kind(&self) -> CoreKind {
709 let fields = |fs: &Vec<(String, WCore)>| {
710 fs.iter()
711 .map(|(n, c)| (Arc::from(n.as_str()), c.to_core()))
712 .collect()
713 };
714 match self {
715 WKind::Const(c) => CoreKind::Const(c.to_const()),
716 WKind::Var(v) => CoreKind::Var(*v),
717 WKind::Global(name) => CoreKind::Global(Arc::from(name.as_str())),
718 WKind::Lam { params, body } => CoreKind::Lam {
719 params: params.as_slice().into(),
720 body: Arc::new(body.to_core()),
721 },
722 WKind::App { func, args } => CoreKind::App {
723 func: Box::new(func.to_core()),
724 args: args.iter().map(WCore::to_core).collect(),
725 },
726 WKind::Prim { op, args } => CoreKind::Prim {
727 op: op.0,
728 args: args.iter().map(WCore::to_core).collect(),
729 },
730 WKind::Let { var, value, body } => CoreKind::Let {
731 var: *var,
732 value: Box::new(value.to_core()),
733 body: Box::new(body.to_core()),
734 },
735 WKind::If { cond, then, alt } => CoreKind::If {
736 cond: Box::new(cond.to_core()),
737 then: Box::new(then.to_core()),
738 alt: Box::new(alt.to_core()),
739 },
740 WKind::Match { scrutinee, arms } => CoreKind::Match {
741 scrutinee: Box::new(scrutinee.to_core()),
742 arms: arms
743 .iter()
744 .map(|a| Arm {
745 pattern: a.pattern.to_pattern(),
746 guard: a.guard.as_ref().map(WCore::to_core),
747 body: a.body.to_core(),
748 span: to_span(a.span),
749 })
750 .collect(),
751 },
752 WKind::Make {
753 ty,
754 variant,
755 fields: fs,
756 } => CoreKind::Make {
757 ty: Arc::from(ty.as_str()),
758 variant: variant.as_ref().map(|v| Arc::from(v.as_str())),
759 fields: fields(fs),
760 },
761 WKind::Field { base, name } => CoreKind::Field {
762 base: Box::new(base.to_core()),
763 name: Arc::from(name.as_str()),
764 },
765 WKind::With { base, fields: fs } => CoreKind::With {
766 base: Box::new(base.to_core()),
767 fields: fields(fs),
768 },
769 WKind::ListLit(xs) => CoreKind::ListLit(xs.iter().map(WCore::to_core).collect()),
770 WKind::MapLit(kvs) => CoreKind::MapLit(
771 kvs.iter()
772 .map(|(k, v)| (k.to_core(), v.to_core()))
773 .collect(),
774 ),
775 }
776 }
777}
778
779#[derive(Clone, Copy)]
785struct WPrim(Prim);
786
787impl Serialize for WPrim {
788 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
789 s.serialize_u32(self.0 as u32)
790 }
791}
792
793impl<'de> Deserialize<'de> for WPrim {
794 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<WPrim, D::Error> {
795 let n = u32::deserialize(d)?;
796 table()
797 .get(n as usize)
798 .copied()
799 .flatten()
800 .map(WPrim)
801 .ok_or_else(|| serde::de::Error::custom(format!("no primitive is numbered {n}")))
802 }
803}
804
805fn table() -> &'static [Option<Prim>] {
811 static TABLE: std::sync::OnceLock<Vec<Option<Prim>>> = std::sync::OnceLock::new();
812 TABLE.get_or_init(|| {
813 let prims: Vec<Prim> = crate::prelude::prims()
814 .into_iter()
815 .map(|(_, p, _)| p)
816 .collect();
817 let width = prims.iter().map(|p| *p as usize).max().map_or(0, |m| m + 1);
818 let mut table = vec![None; width];
819 for p in prims {
820 table[p as usize] = Some(p);
821 }
822 table
823 })
824}
825
826#[cfg(test)]
827mod tests {
828 use super::*;
829
830 fn placed(src: &str) -> Placed {
831 let (placed, diags, map) = crate::compile_str("t.beck", src);
832 assert!(!diags.has_errors(), "{}", diags.render(&map));
833 placed.expect("compiles")
834 }
835
836 const TODO: &str = r#"
837model Todo:
838 id: Str
839 text: Str
840 done: Bool
841
842model State:
843 todos: list[Todo]
844
845union Command:
846 Add(id: Str, text: Str)
847 Toggle(id: Str)
848
849union Event:
850 Added(id: Str, text: Str)
851 Toggled(id: Str)
852
853union Rejection:
854 Blank
855
856def apply_event(s: State, env: Envelope[Event]) -> State:
857 match env.body:
858 case Added(id, text):
859 return s.with(todos=list_append(s.todos, Todo(id=id, text=text, done=False)))
860 case Toggled(id):
861 return s
862
863def validate(s: State, p: Proposal) -> Result[list[Event], Rejection]:
864 match p.command:
865 case Add(id, text):
866 if str_is_empty(text):
867 return Err(error=Blank)
868 return Ok(value=[Added(id=id, text=text)])
869 case Toggle(id):
870 return Ok(value=[Toggled(id=id)])
871
872def label(t: Todo) -> Str:
873 return t.text
874
875def render(s: State) -> Html:
876 return ui:
877 ul:
878 for t in s.todos:
879 li: label(t)
880
881@on(server)
882proposals: Stream[Proposal] = merge_clients()
883
884@on(server)
885events: Stream[Event] = decide(proposals, todos, validate)
886
887@on(data)
888todos: Signal[State] = durable(fold(apply_event, State(todos=[]), events))
889
890@on(client)
891page: Signal[Html] = signal_map(todos, render)
892"#;
893
894 #[test]
895 fn a_bundle_round_trips_through_its_bytes() {
896 let placed = placed(TODO);
897 let bundle = Bundle::of(&placed);
898 let bytes = bundle.to_bytes();
899 let back = Bundle::from_bytes(&bytes).expect("reads back");
900
901 assert_eq!(back.component, bundle.component);
902 assert_eq!(back.wire_id, bundle.wire_id);
903 assert_eq!(back.optimistic, bundle.optimistic);
904 assert_eq!(back.nodes(), bundle.nodes());
905 assert_eq!(
906 back.defs.keys().collect::<Vec<_>>(),
907 bundle.defs.keys().collect::<Vec<_>>()
908 );
909 }
910
911 #[test]
912 fn a_bundle_carries_what_its_roles_reach_and_not_the_rest() {
913 let placed = placed(TODO);
914 let bundle = Bundle::of(&placed);
915 assert!(
917 bundle.defs.contains_key("label"),
918 "{:?}",
919 bundle.defs.keys()
920 );
921 assert!(!bundle.defs.contains_key("page"));
923 }
924
925 #[test]
926 fn a_bundle_from_a_differently_numbered_compiler_is_refused() {
927 let placed = placed(TODO);
928 let bytes = Bundle::of(&placed).to_bytes();
929 let mut wire: Wire = postcard::from_bytes(&bytes).expect("decodes");
930 wire.shape = "0000000000000000".to_string();
931 let forged = postcard::to_allocvec(&wire).expect("encodes");
932
933 match Bundle::from_bytes(&forged) {
934 Err(BadBundle::Shape { found, .. }) => assert_eq!(found, "0000000000000000"),
935 other => panic!("expected a shape refusal, got {other:?}"),
936 }
937 }
938
939 #[test]
940 fn a_bundle_from_a_later_format_is_refused() {
941 let placed = placed(TODO);
942 let bytes = Bundle::of(&placed).to_bytes();
943 let mut wire: Wire = postcard::from_bytes(&bytes).expect("decodes");
944 wire.format = FORMAT + 1;
945 let forged = postcard::to_allocvec(&wire).expect("encodes");
946
947 assert!(matches!(
948 Bundle::from_bytes(&forged),
949 Err(BadBundle::Format { .. })
950 ));
951 }
952}