Skip to main content

open_gpui_canvas/
document.rs

1use indexmap::{IndexMap, IndexSet};
2use open_gpui::{Bounds, Pixels, Point, Size};
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value};
5use std::fmt;
6use thiserror::Error;
7
8use crate::format::{
9    CANVAS_DOCUMENT_FORMAT_VERSION, default_document_format_version, migrate_canvas_snapshot,
10};
11use crate::geometry_facts::CanvasGeometryFacts;
12use crate::mutation::{CanvasCommittedMutation, CanvasMutationJournal, CanvasPreparedMutation};
13use crate::relations::{CanvasRecordBindingRelation, CanvasRecordRelations};
14use crate::routing::{CanvasDefaultEdgeRouter, CanvasEdgeRouter, CanvasRoutePath};
15use crate::schema::{CanvasKindRegistry, CanvasSchemaError};
16
17pub type CanvasValue = Map<String, Value>;
18
19macro_rules! canvas_id {
20    ($name:ident) => {
21        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
22        #[serde(transparent)]
23        pub struct $name(String);
24
25        impl $name {
26            pub fn new(id: impl Into<String>) -> Self {
27                Self(id.into())
28            }
29
30            pub fn as_str(&self) -> &str {
31                &self.0
32            }
33        }
34
35        impl From<&str> for $name {
36            fn from(value: &str) -> Self {
37                Self::new(value)
38            }
39        }
40
41        impl From<String> for $name {
42            fn from(value: String) -> Self {
43                Self::new(value)
44            }
45        }
46
47        impl fmt::Display for $name {
48            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49                f.write_str(self.as_str())
50            }
51        }
52    };
53}
54
55canvas_id!(NodeId);
56canvas_id!(EdgeId);
57canvas_id!(ShapeId);
58canvas_id!(HandleId);
59canvas_id!(BindingId);
60
61#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
62#[serde(transparent)]
63pub struct CanvasEdgeRouteKind(String);
64
65impl CanvasEdgeRouteKind {
66    pub const STRAIGHT: &'static str = "straight";
67    pub const POLYLINE: &'static str = "polyline";
68    pub const ORTHOGONAL: &'static str = "orthogonal";
69    pub const CUBIC_BEZIER: &'static str = "cubic-bezier";
70
71    pub fn new(kind: impl Into<String>) -> Self {
72        Self(kind.into())
73    }
74
75    pub fn as_str(&self) -> &str {
76        &self.0
77    }
78}
79
80impl Default for CanvasEdgeRouteKind {
81    fn default() -> Self {
82        Self::new(Self::STRAIGHT)
83    }
84}
85
86impl From<&str> for CanvasEdgeRouteKind {
87    fn from(value: &str) -> Self {
88        Self::new(value)
89    }
90}
91
92impl From<String> for CanvasEdgeRouteKind {
93    fn from(value: String) -> Self {
94        Self::new(value)
95    }
96}
97
98impl fmt::Display for CanvasEdgeRouteKind {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        f.write_str(self.as_str())
101    }
102}
103
104#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
105pub enum CanvasRecordId {
106    Node(NodeId),
107    Edge(EdgeId),
108    Shape(ShapeId),
109}
110
111impl fmt::Display for CanvasRecordId {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        match self {
114            Self::Node(id) => write!(f, "node:{id}"),
115            Self::Edge(id) => write!(f, "edge:{id}"),
116            Self::Shape(id) => write!(f, "shape:{id}"),
117        }
118    }
119}
120
121impl From<NodeId> for CanvasRecordId {
122    fn from(value: NodeId) -> Self {
123        Self::Node(value)
124    }
125}
126
127impl From<EdgeId> for CanvasRecordId {
128    fn from(value: EdgeId) -> Self {
129        Self::Edge(value)
130    }
131}
132
133impl From<ShapeId> for CanvasRecordId {
134    fn from(value: ShapeId) -> Self {
135        Self::Shape(value)
136    }
137}
138
139#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
140pub enum HandleRole {
141    #[default]
142    Any,
143    Source,
144    Target,
145}
146
147#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
148pub enum CanvasConnectionEndpointRole {
149    Source,
150    Target,
151}
152
153#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
154pub struct CanvasStyle {
155    #[serde(default)]
156    pub fill: Option<String>,
157    #[serde(default)]
158    pub stroke: Option<String>,
159    #[serde(default)]
160    pub stroke_width: Pixels,
161}
162
163impl Default for CanvasStyle {
164    fn default() -> Self {
165        Self {
166            fill: None,
167            stroke: None,
168            stroke_width: Pixels::ZERO,
169        }
170    }
171}
172
173#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
174pub struct CanvasHandle {
175    pub id: HandleId,
176    #[serde(default)]
177    pub role: HandleRole,
178    pub position: Point<Pixels>,
179    #[serde(default = "default_handle_size")]
180    pub size: Size<Pixels>,
181    #[serde(default = "default_true")]
182    pub connectable: bool,
183    #[serde(default)]
184    pub hidden: bool,
185}
186
187impl CanvasHandle {
188    pub fn new(id: impl Into<HandleId>, position: Point<Pixels>) -> Self {
189        Self {
190            id: id.into(),
191            role: HandleRole::Any,
192            position,
193            size: default_handle_size(),
194            connectable: true,
195            hidden: false,
196        }
197    }
198
199    pub fn bounds_in_node(&self) -> Bounds<Pixels> {
200        Bounds::centered_at(self.position, self.size)
201    }
202
203    pub fn bounds_in_document(&self, node: &CanvasNode) -> Bounds<Pixels> {
204        let local = self.bounds_in_node();
205        Bounds::new(node.position + local.origin, local.size)
206    }
207
208    pub fn accepts_connection_role(&self, role: CanvasConnectionEndpointRole) -> bool {
209        self.connectable
210            && match role {
211                CanvasConnectionEndpointRole::Source => self.role != HandleRole::Target,
212                CanvasConnectionEndpointRole::Target => self.role != HandleRole::Source,
213            }
214    }
215
216    pub fn is_pickable_connection_endpoint(&self, role: CanvasConnectionEndpointRole) -> bool {
217        !self.hidden && self.accepts_connection_role(role)
218    }
219}
220
221#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
222pub struct CanvasNode {
223    pub id: NodeId,
224    #[serde(default = "default_kind")]
225    pub kind: String,
226    pub position: Point<Pixels>,
227    pub size: Size<Pixels>,
228    #[serde(default)]
229    pub z_index: i32,
230    #[serde(default)]
231    pub hidden: bool,
232    #[serde(default)]
233    pub locked: bool,
234    #[serde(default)]
235    pub handles: Vec<CanvasHandle>,
236    #[serde(default)]
237    pub data: CanvasValue,
238    #[serde(default)]
239    pub style: CanvasStyle,
240}
241
242impl CanvasNode {
243    pub fn new(id: impl Into<NodeId>, position: Point<Pixels>, size: Size<Pixels>) -> Self {
244        Self {
245            id: id.into(),
246            kind: default_kind(),
247            position,
248            size,
249            z_index: 0,
250            hidden: false,
251            locked: false,
252            handles: Vec::new(),
253            data: CanvasValue::new(),
254            style: CanvasStyle::default(),
255        }
256    }
257
258    pub fn bounds(&self) -> Bounds<Pixels> {
259        Bounds::new(self.position, self.size)
260    }
261
262    pub fn handle(&self, id: Option<&HandleId>) -> Option<&CanvasHandle> {
263        match id {
264            Some(id) => self.handles.iter().find(|handle| &handle.id == id),
265            None => self.handles.first(),
266        }
267    }
268}
269
270#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
271pub struct CanvasEndpoint {
272    pub node_id: NodeId,
273    #[serde(default)]
274    pub handle_id: Option<HandleId>,
275}
276
277impl CanvasEndpoint {
278    pub fn new(node_id: impl Into<NodeId>, handle_id: Option<impl Into<HandleId>>) -> Self {
279        Self {
280            node_id: node_id.into(),
281            handle_id: handle_id.map(Into::into),
282        }
283    }
284}
285
286#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
287pub struct CanvasEdgeRoute {
288    #[serde(default)]
289    pub kind: CanvasEdgeRouteKind,
290    #[serde(default)]
291    pub waypoints: Vec<Point<Pixels>>,
292    #[serde(default)]
293    pub control_points: Vec<Point<Pixels>>,
294    #[serde(default = "default_edge_interaction_width")]
295    pub interaction_width: Pixels,
296    #[serde(default)]
297    pub options: CanvasValue,
298}
299
300impl CanvasEdgeRoute {
301    pub fn new(kind: impl Into<CanvasEdgeRouteKind>) -> Self {
302        Self {
303            kind: kind.into(),
304            ..Self::default()
305        }
306    }
307
308    pub fn straight() -> Self {
309        Self::new(CanvasEdgeRouteKind::STRAIGHT)
310    }
311
312    pub fn polyline(waypoints: impl IntoIterator<Item = Point<Pixels>>) -> Self {
313        Self {
314            kind: CanvasEdgeRouteKind::new(CanvasEdgeRouteKind::POLYLINE),
315            waypoints: waypoints.into_iter().collect(),
316            ..Self::default()
317        }
318    }
319
320    pub fn orthogonal() -> Self {
321        Self::new(CanvasEdgeRouteKind::ORTHOGONAL)
322    }
323}
324
325impl Default for CanvasEdgeRoute {
326    fn default() -> Self {
327        Self {
328            kind: CanvasEdgeRouteKind::default(),
329            waypoints: Vec::new(),
330            control_points: Vec::new(),
331            interaction_width: default_edge_interaction_width(),
332            options: CanvasValue::new(),
333        }
334    }
335}
336
337#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
338pub struct CanvasEdge {
339    pub id: EdgeId,
340    #[serde(default = "default_kind")]
341    pub kind: String,
342    pub source: CanvasEndpoint,
343    pub target: CanvasEndpoint,
344    #[serde(default)]
345    pub z_index: i32,
346    #[serde(default)]
347    pub hidden: bool,
348    #[serde(default)]
349    pub locked: bool,
350    #[serde(default)]
351    pub data: CanvasValue,
352    #[serde(default)]
353    pub style: CanvasStyle,
354    #[serde(default)]
355    pub route: CanvasEdgeRoute,
356}
357
358impl CanvasEdge {
359    pub fn new(id: impl Into<EdgeId>, source: CanvasEndpoint, target: CanvasEndpoint) -> Self {
360        Self {
361            id: id.into(),
362            kind: default_kind(),
363            source,
364            target,
365            z_index: 0,
366            hidden: false,
367            locked: false,
368            data: CanvasValue::new(),
369            style: CanvasStyle::default(),
370            route: CanvasEdgeRoute::default(),
371        }
372    }
373}
374
375#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
376pub struct CanvasShape {
377    pub id: ShapeId,
378    #[serde(default = "default_kind")]
379    pub kind: String,
380    pub bounds: Bounds<Pixels>,
381    #[serde(default)]
382    pub z_index: i32,
383    #[serde(default)]
384    pub hidden: bool,
385    #[serde(default)]
386    pub locked: bool,
387    #[serde(default)]
388    pub data: CanvasValue,
389    #[serde(default)]
390    pub style: CanvasStyle,
391}
392
393impl CanvasShape {
394    pub fn new(id: impl Into<ShapeId>, bounds: Bounds<Pixels>) -> Self {
395        Self {
396            id: id.into(),
397            kind: default_kind(),
398            bounds,
399            z_index: 0,
400            hidden: false,
401            locked: false,
402            data: CanvasValue::new(),
403            style: CanvasStyle::default(),
404        }
405    }
406}
407
408#[derive(Debug, Error, Eq, PartialEq)]
409pub enum DocumentError {
410    #[error("unsupported canvas document format version `{found}`, expected `{expected}`")]
411    UnsupportedFormatVersion { expected: u32, found: u32 },
412    #[error("node `{0}` already exists")]
413    DuplicateNode(NodeId),
414    #[error("edge `{0}` already exists")]
415    DuplicateEdge(EdgeId),
416    #[error("shape `{0}` already exists")]
417    DuplicateShape(ShapeId),
418    #[error("node `{0}` was not found")]
419    MissingNode(NodeId),
420    #[error("edge `{0}` was not found")]
421    MissingEdge(EdgeId),
422    #[error("shape `{0}` was not found")]
423    MissingShape(ShapeId),
424    #[error("handle `{handle_id}` was not found on node `{node_id}`")]
425    MissingHandle {
426        node_id: NodeId,
427        handle_id: HandleId,
428    },
429    #[error("handle `{handle_id}` already exists on node `{node_id}`")]
430    DuplicateHandle {
431        node_id: NodeId,
432        handle_id: HandleId,
433    },
434    #[error("handle `{handle_id}` on node `{node_id}` is not connectable")]
435    NonConnectableHandle {
436        node_id: NodeId,
437        handle_id: HandleId,
438    },
439    #[error("handle `{handle_id}` on node `{node_id}` cannot be used as an edge source")]
440    InvalidSourceHandle {
441        node_id: NodeId,
442        handle_id: HandleId,
443    },
444    #[error("handle `{handle_id}` on node `{node_id}` cannot be used as an edge target")]
445    InvalidTargetHandle {
446        node_id: NodeId,
447        handle_id: HandleId,
448    },
449    #[error("edge `{0}` has an empty route kind")]
450    EmptyEdgeRouteKind(EdgeId),
451    #[error("edge `{0}` has an invalid route interaction width")]
452    InvalidEdgeInteractionWidth(EdgeId),
453    #[error("edge `{0}` has an invalid route point")]
454    InvalidEdgeRoutePoint(EdgeId),
455    #[error("canvas relation references missing record `{0}`")]
456    MissingRelationRecord(CanvasRecordId),
457    #[error("canvas record `{0}` cannot be its own parent")]
458    SelfParentRelation(CanvasRecordId),
459    #[error("canvas record relation cycle includes `{0}`")]
460    CyclicRecordRelation(CanvasRecordId),
461    #[error("canvas record `{0}` has more than one parent relation")]
462    DuplicateParentRelation(CanvasRecordId),
463    #[error("canvas group relation from `{group}` to `{member}` is duplicated")]
464    DuplicateGroupRelation {
465        group: CanvasRecordId,
466        member: CanvasRecordId,
467    },
468    #[error("canvas binding relation `{0}` is duplicated")]
469    DuplicateBindingRelation(BindingId),
470    #[error("canvas record `{0}` cannot be bound to itself")]
471    SelfBindingRelation(CanvasRecordId),
472    #[error(transparent)]
473    Schema(#[from] CanvasSchemaError),
474}
475
476#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
477pub enum DocumentCommand {
478    InsertNode(CanvasNode),
479    UpdateNode(CanvasNode),
480    RemoveNode(NodeId),
481    InsertEdge(CanvasEdge),
482    UpdateEdge(CanvasEdge),
483    RemoveEdge(EdgeId),
484    InsertShape(CanvasShape),
485    UpdateShape(CanvasShape),
486    RemoveShape(ShapeId),
487    SetRecordParent {
488        child: CanvasRecordId,
489        parent: CanvasRecordId,
490    },
491    ClearRecordParent {
492        child: CanvasRecordId,
493    },
494    AddRecordToGroup {
495        group: CanvasRecordId,
496        member: CanvasRecordId,
497    },
498    RemoveRecordFromGroup {
499        group: CanvasRecordId,
500        member: CanvasRecordId,
501    },
502    SetRecordBinding(CanvasRecordBindingRelation),
503    RemoveRecordBinding {
504        id: BindingId,
505    },
506}
507
508#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
509pub struct CanvasDocumentDiff {
510    #[serde(default)]
511    pub inserted: IndexSet<CanvasRecordId>,
512    #[serde(default)]
513    pub updated: IndexSet<CanvasRecordId>,
514    #[serde(default)]
515    pub removed: IndexSet<CanvasRecordId>,
516    #[serde(default)]
517    pub metadata_changed: bool,
518    #[serde(default)]
519    pub relations_changed: bool,
520}
521
522impl CanvasDocumentDiff {
523    pub fn is_empty(&self) -> bool {
524        self.inserted.is_empty()
525            && self.updated.is_empty()
526            && self.removed.is_empty()
527            && !self.metadata_changed
528            && !self.relations_changed
529    }
530
531    pub fn record_insert(&mut self, id: impl Into<CanvasRecordId>) {
532        let id = id.into();
533        if self.removed.shift_remove(&id) {
534            self.updated.insert(id);
535        } else {
536            self.inserted.insert(id);
537        }
538    }
539
540    pub fn record_update(&mut self, id: impl Into<CanvasRecordId>) {
541        let id = id.into();
542        if !self.inserted.contains(&id) && !self.removed.contains(&id) {
543            self.updated.insert(id);
544        }
545    }
546
547    pub fn record_remove(&mut self, id: impl Into<CanvasRecordId>) {
548        let id = id.into();
549        if self.inserted.shift_remove(&id) {
550            self.updated.shift_remove(&id);
551        } else {
552            self.updated.shift_remove(&id);
553            self.removed.insert(id);
554        }
555    }
556}
557
558#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
559pub struct CanvasTransaction {
560    #[serde(default)]
561    pub commands: Vec<DocumentCommand>,
562    #[serde(default)]
563    pub metadata: CanvasValue,
564}
565
566impl CanvasTransaction {
567    pub fn new(commands: impl IntoIterator<Item = DocumentCommand>) -> Self {
568        Self {
569            commands: commands.into_iter().collect(),
570            metadata: CanvasValue::new(),
571        }
572    }
573
574    pub fn single(command: DocumentCommand) -> Self {
575        Self::new([command])
576    }
577
578    pub fn push(&mut self, command: DocumentCommand) {
579        self.commands.push(command);
580    }
581
582    pub fn is_empty(&self) -> bool {
583        self.commands.is_empty()
584    }
585}
586
587impl From<DocumentCommand> for CanvasTransaction {
588    fn from(value: DocumentCommand) -> Self {
589        Self::single(value)
590    }
591}
592
593#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
594pub struct CanvasSnapshot {
595    #[serde(default = "default_document_format_version")]
596    pub format_version: u32,
597    #[serde(default)]
598    pub nodes: Vec<CanvasNode>,
599    #[serde(default)]
600    pub edges: Vec<CanvasEdge>,
601    #[serde(default)]
602    pub shapes: Vec<CanvasShape>,
603    #[serde(default)]
604    pub metadata: CanvasValue,
605    #[serde(default)]
606    pub relations: CanvasRecordRelations,
607}
608
609impl Default for CanvasSnapshot {
610    fn default() -> Self {
611        Self {
612            format_version: CANVAS_DOCUMENT_FORMAT_VERSION,
613            nodes: Vec::new(),
614            edges: Vec::new(),
615            shapes: Vec::new(),
616            metadata: CanvasValue::new(),
617            relations: CanvasRecordRelations::default(),
618        }
619    }
620}
621
622impl CanvasSnapshot {
623    pub fn migrate_to_current(self) -> Result<Self, DocumentError> {
624        migrate_canvas_snapshot(self)
625    }
626}
627
628#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
629pub struct CanvasDocument {
630    #[serde(default = "default_document_format_version")]
631    format_version: u32,
632    #[serde(default)]
633    nodes: IndexMap<NodeId, CanvasNode>,
634    #[serde(default)]
635    edges: IndexMap<EdgeId, CanvasEdge>,
636    #[serde(default)]
637    shapes: IndexMap<ShapeId, CanvasShape>,
638    #[serde(default)]
639    metadata: CanvasValue,
640    #[serde(default)]
641    relations: CanvasRecordRelations,
642}
643
644impl Default for CanvasDocument {
645    fn default() -> Self {
646        Self {
647            format_version: CANVAS_DOCUMENT_FORMAT_VERSION,
648            nodes: IndexMap::new(),
649            edges: IndexMap::new(),
650            shapes: IndexMap::new(),
651            metadata: CanvasValue::new(),
652            relations: CanvasRecordRelations::default(),
653        }
654    }
655}
656
657#[derive(Clone, Debug, Default, PartialEq)]
658pub struct CanvasDocumentBuilder {
659    document: CanvasDocument,
660}
661
662impl CanvasDocumentBuilder {
663    pub fn new() -> Self {
664        Self::default()
665    }
666
667    pub fn with_format_version(mut self, format_version: u32) -> Self {
668        self.document.format_version = format_version;
669        self
670    }
671
672    pub fn with_metadata(mut self, metadata: CanvasValue) -> Self {
673        self.document.metadata = metadata;
674        self
675    }
676
677    pub fn with_relations(mut self, relations: CanvasRecordRelations) -> Self {
678        self.document.relations = relations;
679        self
680    }
681
682    pub fn add_node(&mut self, node: CanvasNode) -> Result<&mut Self, DocumentError> {
683        self.document.insert_node_rule(node)?;
684        Ok(self)
685    }
686
687    pub fn add_edge(&mut self, edge: CanvasEdge) -> Result<&mut Self, DocumentError> {
688        self.document.insert_edge_rule(edge)?;
689        Ok(self)
690    }
691
692    pub fn add_shape(&mut self, shape: CanvasShape) -> Result<&mut Self, DocumentError> {
693        self.document.insert_shape_rule(shape)?;
694        Ok(self)
695    }
696
697    pub fn build(mut self) -> Result<CanvasDocument, DocumentError> {
698        self.document.prune_missing_relations();
699        self.document.validate_relations()?;
700        Ok(self.document)
701    }
702
703    pub fn build_with_kind_registry(
704        mut self,
705        kind_registry: &CanvasKindRegistry,
706    ) -> Result<CanvasDocument, DocumentError> {
707        self.document.prune_missing_relations();
708        self.document.validate_relations()?;
709        kind_registry.validate_document(&self.document)?;
710        Ok(self.document)
711    }
712}
713
714impl CanvasDocument {
715    pub fn new() -> Self {
716        Self::default()
717    }
718
719    pub fn builder() -> CanvasDocumentBuilder {
720        CanvasDocumentBuilder::new()
721    }
722
723    pub fn format_version(&self) -> u32 {
724        self.format_version
725    }
726
727    pub fn metadata(&self) -> &CanvasValue {
728        &self.metadata
729    }
730
731    pub fn relations(&self) -> &CanvasRecordRelations {
732        &self.relations
733    }
734
735    pub fn node(&self, id: &NodeId) -> Option<&CanvasNode> {
736        self.nodes.get(id)
737    }
738
739    pub fn edge(&self, id: &EdgeId) -> Option<&CanvasEdge> {
740        self.edges.get(id)
741    }
742
743    pub fn shape(&self, id: &ShapeId) -> Option<&CanvasShape> {
744        self.shapes.get(id)
745    }
746
747    pub fn contains_node(&self, id: &NodeId) -> bool {
748        self.nodes.contains_key(id)
749    }
750
751    pub fn contains_edge(&self, id: &EdgeId) -> bool {
752        self.edges.contains_key(id)
753    }
754
755    pub fn contains_shape(&self, id: &ShapeId) -> bool {
756        self.shapes.contains_key(id)
757    }
758
759    pub fn contains_record(&self, id: &CanvasRecordId) -> bool {
760        match id {
761            CanvasRecordId::Node(id) => self.nodes.contains_key(id),
762            CanvasRecordId::Edge(id) => self.edges.contains_key(id),
763            CanvasRecordId::Shape(id) => self.shapes.contains_key(id),
764        }
765    }
766
767    pub fn nodes(&self) -> impl Iterator<Item = &CanvasNode> + '_ {
768        self.nodes.values()
769    }
770
771    pub fn edges(&self) -> impl Iterator<Item = &CanvasEdge> + '_ {
772        self.edges.values()
773    }
774
775    pub fn shapes(&self) -> impl Iterator<Item = &CanvasShape> + '_ {
776        self.shapes.values()
777    }
778
779    pub fn node_entries(&self) -> impl Iterator<Item = (&NodeId, &CanvasNode)> + '_ {
780        self.nodes.iter()
781    }
782
783    pub fn edge_entries(&self) -> impl Iterator<Item = (&EdgeId, &CanvasEdge)> + '_ {
784        self.edges.iter()
785    }
786
787    pub fn shape_entries(&self) -> impl Iterator<Item = (&ShapeId, &CanvasShape)> + '_ {
788        self.shapes.iter()
789    }
790
791    pub fn node_ids(&self) -> impl Iterator<Item = &NodeId> + '_ {
792        self.nodes.keys()
793    }
794
795    pub fn edge_ids(&self) -> impl Iterator<Item = &EdgeId> + '_ {
796        self.edges.keys()
797    }
798
799    pub fn shape_ids(&self) -> impl Iterator<Item = &ShapeId> + '_ {
800        self.shapes.keys()
801    }
802
803    pub fn node_count(&self) -> usize {
804        self.nodes.len()
805    }
806
807    pub fn edge_count(&self) -> usize {
808        self.edges.len()
809    }
810
811    pub fn shape_count(&self) -> usize {
812        self.shapes.len()
813    }
814
815    pub fn is_empty(&self) -> bool {
816        self.nodes.is_empty() && self.edges.is_empty() && self.shapes.is_empty()
817    }
818
819    pub fn from_snapshot(snapshot: CanvasSnapshot) -> Result<Self, DocumentError> {
820        Self::from_snapshot_with_kind_registry(snapshot, &CanvasKindRegistry::open())
821    }
822
823    pub fn from_snapshot_with_kind_registry(
824        snapshot: CanvasSnapshot,
825        kind_registry: &CanvasKindRegistry,
826    ) -> Result<Self, DocumentError> {
827        let snapshot = migrate_canvas_snapshot(snapshot)?;
828
829        let mut builder = Self::builder()
830            .with_format_version(snapshot.format_version)
831            .with_metadata(snapshot.metadata)
832            .with_relations(snapshot.relations);
833
834        for node in snapshot.nodes {
835            let node = kind_registry.normalize_node(node)?;
836            builder.add_node(node)?;
837        }
838
839        for shape in snapshot.shapes {
840            let shape = kind_registry.normalize_shape(shape)?;
841            builder.add_shape(shape)?;
842        }
843
844        for edge in snapshot.edges {
845            let edge = kind_registry.normalize_edge(edge)?;
846            builder.add_edge(edge)?;
847        }
848
849        builder.build_with_kind_registry(kind_registry)
850    }
851
852    pub fn to_snapshot(&self) -> CanvasSnapshot {
853        CanvasSnapshot {
854            format_version: self.format_version,
855            nodes: self.nodes.values().cloned().collect(),
856            edges: self.edges.values().cloned().collect(),
857            shapes: self.shapes.values().cloned().collect(),
858            metadata: self.metadata.clone(),
859            relations: self.relations.clone(),
860        }
861    }
862
863    pub(crate) fn apply(&mut self, command: DocumentCommand) -> Result<(), DocumentError> {
864        match command {
865            DocumentCommand::InsertNode(node) => self.insert_node_rule(node),
866            DocumentCommand::UpdateNode(node) => self.update_node_rule(node),
867            DocumentCommand::RemoveNode(id) => self.remove_node_rule(&id).map(drop),
868            DocumentCommand::InsertEdge(edge) => self.insert_edge_rule(edge),
869            DocumentCommand::UpdateEdge(edge) => self.update_edge_rule(edge),
870            DocumentCommand::RemoveEdge(id) => self.remove_edge_rule(&id).map(drop),
871            DocumentCommand::InsertShape(shape) => self.insert_shape_rule(shape),
872            DocumentCommand::UpdateShape(shape) => self.update_shape_rule(shape),
873            DocumentCommand::RemoveShape(id) => self.remove_shape_rule(&id).map(drop),
874            DocumentCommand::SetRecordParent { child, parent } => {
875                self.set_record_parent_rule(child, parent)
876            }
877            DocumentCommand::ClearRecordParent { child } => {
878                self.relations.clear_parent(&child);
879                Ok(())
880            }
881            DocumentCommand::AddRecordToGroup { group, member } => {
882                self.add_record_to_group_rule(group, member)
883            }
884            DocumentCommand::RemoveRecordFromGroup { group, member } => {
885                self.relations.remove_from_group(&group, &member);
886                Ok(())
887            }
888            DocumentCommand::SetRecordBinding(binding) => self.set_record_binding_rule(binding),
889            DocumentCommand::RemoveRecordBinding { id } => {
890                self.relations.remove_binding(&id);
891                Ok(())
892            }
893        }
894    }
895
896    pub fn apply_transaction(
897        &mut self,
898        transaction: CanvasTransaction,
899    ) -> Result<(), DocumentError> {
900        self.apply_transaction_with_diff(transaction).map(drop)
901    }
902
903    pub fn apply_transaction_with_diff(
904        &mut self,
905        transaction: CanvasTransaction,
906    ) -> Result<CanvasDocumentDiff, DocumentError> {
907        self.commit_transaction(transaction)
908            .map(CanvasCommittedMutation::into_diff)
909    }
910
911    pub fn commit_transaction(
912        &mut self,
913        transaction: CanvasTransaction,
914    ) -> Result<CanvasCommittedMutation, DocumentError> {
915        CanvasMutationJournal::commit(self, transaction)
916    }
917
918    pub fn commit_transaction_with_kind_registry(
919        &mut self,
920        transaction: CanvasTransaction,
921        kind_registry: &CanvasKindRegistry,
922    ) -> Result<CanvasCommittedMutation, DocumentError> {
923        CanvasMutationJournal::commit_with_kind_registry(self, transaction, kind_registry)
924    }
925
926    pub(crate) fn prepare_transaction_with_kind_registry(
927        &self,
928        transaction: CanvasTransaction,
929        kind_registry: &CanvasKindRegistry,
930    ) -> Result<CanvasPreparedMutation, DocumentError> {
931        CanvasMutationJournal::prepare_with_kind_registry(self, transaction, kind_registry)
932    }
933
934    pub fn invert_transaction(
935        &self,
936        transaction: &CanvasTransaction,
937    ) -> Result<CanvasTransaction, DocumentError> {
938        let mut draft = self.clone();
939        let mut inverse_segments = Vec::new();
940
941        for command in &transaction.commands {
942            inverse_segments.push(draft.inverse_for(command)?);
943            draft.apply(command.clone())?;
944        }
945
946        Ok(CanvasTransaction {
947            commands: inverse_segments.into_iter().rev().flatten().collect(),
948            metadata: CanvasValue::new(),
949        })
950    }
951
952    fn insert_node_rule(&mut self, node: CanvasNode) -> Result<(), DocumentError> {
953        if self.nodes.contains_key(&node.id) {
954            return Err(DocumentError::DuplicateNode(node.id));
955        }
956        Self::validate_node(&node)?;
957
958        self.nodes.insert(node.id.clone(), node);
959        Ok(())
960    }
961
962    fn update_node_rule(&mut self, node: CanvasNode) -> Result<(), DocumentError> {
963        if !self.nodes.contains_key(&node.id) {
964            return Err(DocumentError::MissingNode(node.id));
965        }
966        Self::validate_node(&node)?;
967
968        let mut draft = self.clone();
969        draft.nodes.insert(node.id.clone(), node);
970        draft.validate_integrity()?;
971        *self = draft;
972        Ok(())
973    }
974
975    fn remove_node_rule(&mut self, id: &NodeId) -> Result<CanvasNode, DocumentError> {
976        let Some(node) = self.nodes.shift_remove(id) else {
977            return Err(DocumentError::MissingNode(id.clone()));
978        };
979
980        self.edges
981            .retain(|_, edge| edge.source.node_id != *id && edge.target.node_id != *id);
982        Ok(node)
983    }
984
985    fn insert_edge_rule(&mut self, edge: CanvasEdge) -> Result<(), DocumentError> {
986        if self.edges.contains_key(&edge.id) {
987            return Err(DocumentError::DuplicateEdge(edge.id));
988        }
989        self.validate_edge(&edge)?;
990
991        self.edges.insert(edge.id.clone(), edge);
992        Ok(())
993    }
994
995    fn update_edge_rule(&mut self, edge: CanvasEdge) -> Result<(), DocumentError> {
996        if !self.edges.contains_key(&edge.id) {
997            return Err(DocumentError::MissingEdge(edge.id));
998        }
999        self.validate_edge(&edge)?;
1000
1001        self.edges.insert(edge.id.clone(), edge);
1002        Ok(())
1003    }
1004
1005    fn remove_edge_rule(&mut self, id: &EdgeId) -> Result<CanvasEdge, DocumentError> {
1006        self.edges
1007            .shift_remove(id)
1008            .ok_or_else(|| DocumentError::MissingEdge(id.clone()))
1009    }
1010
1011    fn insert_shape_rule(&mut self, shape: CanvasShape) -> Result<(), DocumentError> {
1012        if self.shapes.contains_key(&shape.id) {
1013            return Err(DocumentError::DuplicateShape(shape.id));
1014        }
1015
1016        self.shapes.insert(shape.id.clone(), shape);
1017        Ok(())
1018    }
1019
1020    fn update_shape_rule(&mut self, shape: CanvasShape) -> Result<(), DocumentError> {
1021        if !self.shapes.contains_key(&shape.id) {
1022            return Err(DocumentError::MissingShape(shape.id));
1023        }
1024
1025        self.shapes.insert(shape.id.clone(), shape);
1026        Ok(())
1027    }
1028
1029    fn remove_shape_rule(&mut self, id: &ShapeId) -> Result<CanvasShape, DocumentError> {
1030        self.shapes
1031            .shift_remove(id)
1032            .ok_or_else(|| DocumentError::MissingShape(id.clone()))
1033    }
1034
1035    fn set_record_parent_rule(
1036        &mut self,
1037        child: CanvasRecordId,
1038        parent: CanvasRecordId,
1039    ) -> Result<(), DocumentError> {
1040        if child == parent {
1041            return Err(DocumentError::SelfParentRelation(child));
1042        }
1043        self.validate_record_id(&child)?;
1044        self.validate_record_id(&parent)?;
1045        self.relations.set_parent(child, parent);
1046        Ok(())
1047    }
1048
1049    fn add_record_to_group_rule(
1050        &mut self,
1051        group: CanvasRecordId,
1052        member: CanvasRecordId,
1053    ) -> Result<(), DocumentError> {
1054        if group == member {
1055            return Err(DocumentError::SelfParentRelation(group));
1056        }
1057        self.validate_record_id(&group)?;
1058        self.validate_record_id(&member)?;
1059        self.relations.add_to_group(group, member);
1060        Ok(())
1061    }
1062
1063    fn set_record_binding_rule(
1064        &mut self,
1065        binding: CanvasRecordBindingRelation,
1066    ) -> Result<(), DocumentError> {
1067        if binding.source == binding.target {
1068            return Err(DocumentError::SelfBindingRelation(binding.source));
1069        }
1070        self.validate_record_id(&binding.source)?;
1071        self.validate_record_id(&binding.target)?;
1072        self.relations.set_binding(binding);
1073        Ok(())
1074    }
1075
1076    // Fixture helpers are test-only; production mutations go through builder or journaled paths.
1077    #[cfg(test)]
1078    pub(crate) fn insert_node(&mut self, node: CanvasNode) -> Result<(), DocumentError> {
1079        self.insert_node_rule(node)
1080    }
1081
1082    #[cfg(test)]
1083    pub(crate) fn remove_node(&mut self, id: &NodeId) -> Result<CanvasNode, DocumentError> {
1084        self.remove_node_rule(id)
1085    }
1086
1087    #[cfg(test)]
1088    pub(crate) fn insert_edge(&mut self, edge: CanvasEdge) -> Result<(), DocumentError> {
1089        self.insert_edge_rule(edge)
1090    }
1091
1092    #[cfg(test)]
1093    pub(crate) fn insert_shape(&mut self, shape: CanvasShape) -> Result<(), DocumentError> {
1094        self.insert_shape_rule(shape)
1095    }
1096
1097    pub fn validate_endpoint(&self, endpoint: &CanvasEndpoint) -> Result<(), DocumentError> {
1098        self.endpoint_parts(endpoint)?;
1099        Ok(())
1100    }
1101
1102    pub fn validate_edge(&self, edge: &CanvasEdge) -> Result<(), DocumentError> {
1103        Self::validate_edge_route(edge)?;
1104        self.validate_source_endpoint(&edge.source)?;
1105        self.validate_target_endpoint(&edge.target)?;
1106        Ok(())
1107    }
1108
1109    pub fn validate_integrity(&self) -> Result<(), DocumentError> {
1110        for node in self.nodes.values() {
1111            Self::validate_node(node)?;
1112        }
1113
1114        for edge in self.edges.values() {
1115            self.validate_edge(edge)?;
1116        }
1117
1118        self.validate_relations()?;
1119
1120        Ok(())
1121    }
1122
1123    pub fn endpoint_position(
1124        &self,
1125        endpoint: &CanvasEndpoint,
1126    ) -> Result<Point<Pixels>, DocumentError> {
1127        CanvasGeometryFacts::new(self).endpoint_position(endpoint)
1128    }
1129
1130    pub fn edge_route_path(&self, edge: &CanvasEdge) -> Result<CanvasRoutePath, DocumentError> {
1131        self.edge_route_path_with_router(edge, &CanvasDefaultEdgeRouter)
1132    }
1133
1134    pub fn edge_route_path_with_router<R>(
1135        &self,
1136        edge: &CanvasEdge,
1137        router: &R,
1138    ) -> Result<CanvasRoutePath, DocumentError>
1139    where
1140        R: CanvasEdgeRouter + ?Sized,
1141    {
1142        CanvasGeometryFacts::with_router(self, router).edge_route_path(edge)
1143    }
1144
1145    pub fn edge_bounds(&self, edge: &CanvasEdge) -> Result<Bounds<Pixels>, DocumentError> {
1146        self.edge_bounds_with_router(edge, &CanvasDefaultEdgeRouter)
1147    }
1148
1149    pub fn edge_bounds_with_router<R>(
1150        &self,
1151        edge: &CanvasEdge,
1152        router: &R,
1153    ) -> Result<Bounds<Pixels>, DocumentError>
1154    where
1155        R: CanvasEdgeRouter + ?Sized,
1156    {
1157        CanvasGeometryFacts::with_router(self, router).edge_bounds(edge)
1158    }
1159
1160    pub fn edge_route_points(
1161        &self,
1162        edge: &CanvasEdge,
1163    ) -> Result<Vec<Point<Pixels>>, DocumentError> {
1164        Ok(self.edge_route_path(edge)?.document_points())
1165    }
1166
1167    pub fn diff_against(&self, previous: &CanvasDocument) -> CanvasDocumentDiff {
1168        let mut diff = CanvasDocumentDiff::default();
1169
1170        for id in previous.nodes.keys() {
1171            if !self.nodes.contains_key(id) {
1172                diff.record_remove(id.clone());
1173            }
1174        }
1175
1176        for (id, node) in &self.nodes {
1177            match previous.nodes.get(id) {
1178                None => diff.record_insert(id.clone()),
1179                Some(previous_node) if previous_node != node => diff.record_update(id.clone()),
1180                Some(_) => {}
1181            }
1182        }
1183
1184        for id in previous.edges.keys() {
1185            if !self.edges.contains_key(id) {
1186                diff.record_remove(id.clone());
1187            }
1188        }
1189
1190        for (id, edge) in &self.edges {
1191            match previous.edges.get(id) {
1192                None => diff.record_insert(id.clone()),
1193                Some(previous_edge) if previous_edge != edge => diff.record_update(id.clone()),
1194                Some(_) => {}
1195            }
1196        }
1197
1198        for id in previous.shapes.keys() {
1199            if !self.shapes.contains_key(id) {
1200                diff.record_remove(id.clone());
1201            }
1202        }
1203
1204        for (id, shape) in &self.shapes {
1205            match previous.shapes.get(id) {
1206                None => diff.record_insert(id.clone()),
1207                Some(previous_shape) if previous_shape != shape => diff.record_update(id.clone()),
1208                Some(_) => {}
1209            }
1210        }
1211
1212        diff.metadata_changed = self.metadata != previous.metadata;
1213        diff.relations_changed = self.relations != previous.relations;
1214        diff
1215    }
1216
1217    pub(crate) fn prune_missing_relations(&mut self) -> bool {
1218        let existing = self.record_id_set();
1219        self.relations.prune_missing_records(&existing)
1220    }
1221
1222    pub fn validate_relations(&self) -> Result<(), DocumentError> {
1223        let mut parent_children = IndexSet::new();
1224        for relation in self.relations.parents() {
1225            if !parent_children.insert(relation.child.clone()) {
1226                return Err(DocumentError::DuplicateParentRelation(
1227                    relation.child.clone(),
1228                ));
1229            }
1230            if relation.child == relation.parent {
1231                return Err(DocumentError::SelfParentRelation(relation.child.clone()));
1232            }
1233            self.validate_record_id(&relation.child)?;
1234            self.validate_record_id(&relation.parent)?;
1235        }
1236
1237        let mut group_relations = IndexSet::new();
1238        for relation in self.relations.groups() {
1239            if !group_relations.insert((relation.group.clone(), relation.member.clone())) {
1240                return Err(DocumentError::DuplicateGroupRelation {
1241                    group: relation.group.clone(),
1242                    member: relation.member.clone(),
1243                });
1244            }
1245            if relation.group == relation.member {
1246                return Err(DocumentError::SelfParentRelation(relation.group.clone()));
1247            }
1248            self.validate_record_id(&relation.group)?;
1249            self.validate_record_id(&relation.member)?;
1250        }
1251
1252        let mut binding_ids = IndexSet::new();
1253        for relation in self.relations.bindings() {
1254            if !binding_ids.insert(relation.id.clone()) {
1255                return Err(DocumentError::DuplicateBindingRelation(relation.id.clone()));
1256            }
1257            if relation.source == relation.target {
1258                return Err(DocumentError::SelfBindingRelation(relation.source.clone()));
1259            }
1260            self.validate_record_id(&relation.source)?;
1261            self.validate_record_id(&relation.target)?;
1262        }
1263
1264        self.validate_relation_graph_is_acyclic()?;
1265
1266        Ok(())
1267    }
1268
1269    fn validate_relation_graph_is_acyclic(&self) -> Result<(), DocumentError> {
1270        let mut graph = IndexMap::<CanvasRecordId, Vec<CanvasRecordId>>::new();
1271        for relation in self.relations.parents() {
1272            graph
1273                .entry(relation.parent.clone())
1274                .or_default()
1275                .push(relation.child.clone());
1276        }
1277        for relation in self.relations.groups() {
1278            graph
1279                .entry(relation.group.clone())
1280                .or_default()
1281                .push(relation.member.clone());
1282        }
1283
1284        let mut visited = IndexSet::new();
1285        let mut visiting = IndexSet::new();
1286        for record_id in graph.keys() {
1287            self.validate_relation_subgraph_is_acyclic(
1288                record_id,
1289                &graph,
1290                &mut visited,
1291                &mut visiting,
1292            )?;
1293        }
1294        Ok(())
1295    }
1296
1297    fn validate_relation_subgraph_is_acyclic(
1298        &self,
1299        record_id: &CanvasRecordId,
1300        graph: &IndexMap<CanvasRecordId, Vec<CanvasRecordId>>,
1301        visited: &mut IndexSet<CanvasRecordId>,
1302        visiting: &mut IndexSet<CanvasRecordId>,
1303    ) -> Result<(), DocumentError> {
1304        if visited.contains(record_id) {
1305            return Ok(());
1306        }
1307        if !visiting.insert(record_id.clone()) {
1308            return Err(DocumentError::CyclicRecordRelation(record_id.clone()));
1309        }
1310
1311        if let Some(children) = graph.get(record_id) {
1312            for child in children {
1313                self.validate_relation_subgraph_is_acyclic(child, graph, visited, visiting)?;
1314            }
1315        }
1316
1317        visiting.shift_remove(record_id);
1318        visited.insert(record_id.clone());
1319        Ok(())
1320    }
1321
1322    fn validate_record_id(&self, id: &CanvasRecordId) -> Result<(), DocumentError> {
1323        if self.contains_record(id) {
1324            Ok(())
1325        } else {
1326            Err(DocumentError::MissingRelationRecord(id.clone()))
1327        }
1328    }
1329
1330    fn record_id_set(&self) -> IndexSet<CanvasRecordId> {
1331        self.nodes
1332            .keys()
1333            .cloned()
1334            .map(CanvasRecordId::Node)
1335            .chain(self.edges.keys().cloned().map(CanvasRecordId::Edge))
1336            .chain(self.shapes.keys().cloned().map(CanvasRecordId::Shape))
1337            .collect()
1338    }
1339
1340    fn validate_node(node: &CanvasNode) -> Result<(), DocumentError> {
1341        let mut handle_ids = IndexSet::new();
1342        for handle in &node.handles {
1343            if !handle_ids.insert(handle.id.clone()) {
1344                return Err(DocumentError::DuplicateHandle {
1345                    node_id: node.id.clone(),
1346                    handle_id: handle.id.clone(),
1347                });
1348            }
1349        }
1350
1351        Ok(())
1352    }
1353
1354    fn validate_edge_route(edge: &CanvasEdge) -> Result<(), DocumentError> {
1355        if edge.route.kind.as_str().trim().is_empty() {
1356            return Err(DocumentError::EmptyEdgeRouteKind(edge.id.clone()));
1357        }
1358
1359        if !edge.route.interaction_width.as_f32().is_finite()
1360            || edge.route.interaction_width < Pixels::ZERO
1361        {
1362            return Err(DocumentError::InvalidEdgeInteractionWidth(edge.id.clone()));
1363        }
1364
1365        for point in edge
1366            .route
1367            .waypoints
1368            .iter()
1369            .chain(edge.route.control_points.iter())
1370        {
1371            if !point.x.as_f32().is_finite() || !point.y.as_f32().is_finite() {
1372                return Err(DocumentError::InvalidEdgeRoutePoint(edge.id.clone()));
1373            }
1374        }
1375
1376        Ok(())
1377    }
1378
1379    fn validate_source_endpoint(&self, endpoint: &CanvasEndpoint) -> Result<(), DocumentError> {
1380        let Some(handle) = self.endpoint_parts(endpoint)?.1 else {
1381            return Ok(());
1382        };
1383        self.validate_connectable_handle(endpoint, handle)?;
1384
1385        if handle.role == HandleRole::Target {
1386            return Err(DocumentError::InvalidSourceHandle {
1387                node_id: endpoint.node_id.clone(),
1388                handle_id: handle.id.clone(),
1389            });
1390        }
1391
1392        Ok(())
1393    }
1394
1395    fn validate_target_endpoint(&self, endpoint: &CanvasEndpoint) -> Result<(), DocumentError> {
1396        let Some(handle) = self.endpoint_parts(endpoint)?.1 else {
1397            return Ok(());
1398        };
1399        self.validate_connectable_handle(endpoint, handle)?;
1400
1401        if handle.role == HandleRole::Source {
1402            return Err(DocumentError::InvalidTargetHandle {
1403                node_id: endpoint.node_id.clone(),
1404                handle_id: handle.id.clone(),
1405            });
1406        }
1407
1408        Ok(())
1409    }
1410
1411    fn validate_connectable_handle(
1412        &self,
1413        endpoint: &CanvasEndpoint,
1414        handle: &CanvasHandle,
1415    ) -> Result<(), DocumentError> {
1416        if !handle.connectable {
1417            return Err(DocumentError::NonConnectableHandle {
1418                node_id: endpoint.node_id.clone(),
1419                handle_id: handle.id.clone(),
1420            });
1421        }
1422
1423        Ok(())
1424    }
1425
1426    fn endpoint_parts(
1427        &self,
1428        endpoint: &CanvasEndpoint,
1429    ) -> Result<(&CanvasNode, Option<&CanvasHandle>), DocumentError> {
1430        let node = self
1431            .nodes
1432            .get(&endpoint.node_id)
1433            .ok_or_else(|| DocumentError::MissingNode(endpoint.node_id.clone()))?;
1434
1435        let Some(handle_id) = &endpoint.handle_id else {
1436            return Ok((node, None));
1437        };
1438
1439        let handle = node
1440            .handle(Some(handle_id))
1441            .ok_or_else(|| DocumentError::MissingHandle {
1442                node_id: endpoint.node_id.clone(),
1443                handle_id: handle_id.clone(),
1444            })?;
1445
1446        Ok((node, Some(handle)))
1447    }
1448
1449    fn inverse_for(
1450        &self,
1451        command: &DocumentCommand,
1452    ) -> Result<Vec<DocumentCommand>, DocumentError> {
1453        match command {
1454            DocumentCommand::InsertNode(node) => {
1455                if self.nodes.contains_key(&node.id) {
1456                    return Err(DocumentError::DuplicateNode(node.id.clone()));
1457                }
1458                Self::validate_node(node)?;
1459                Ok(vec![DocumentCommand::RemoveNode(node.id.clone())])
1460            }
1461            DocumentCommand::UpdateNode(node) => Ok(vec![DocumentCommand::UpdateNode(
1462                self.nodes
1463                    .get(&node.id)
1464                    .ok_or_else(|| DocumentError::MissingNode(node.id.clone()))?
1465                    .clone(),
1466            )]),
1467            DocumentCommand::RemoveNode(id) => {
1468                let node = self
1469                    .nodes
1470                    .get(id)
1471                    .ok_or_else(|| DocumentError::MissingNode(id.clone()))?
1472                    .clone();
1473                let mut inverse = vec![DocumentCommand::InsertNode(node)];
1474                inverse.extend(
1475                    self.edges
1476                        .values()
1477                        .filter(|edge| edge.source.node_id == *id || edge.target.node_id == *id)
1478                        .cloned()
1479                        .map(DocumentCommand::InsertEdge),
1480                );
1481                Ok(inverse)
1482            }
1483            DocumentCommand::InsertEdge(edge) => {
1484                if self.edges.contains_key(&edge.id) {
1485                    return Err(DocumentError::DuplicateEdge(edge.id.clone()));
1486                }
1487                self.validate_edge(edge)?;
1488                Ok(vec![DocumentCommand::RemoveEdge(edge.id.clone())])
1489            }
1490            DocumentCommand::UpdateEdge(edge) => Ok(vec![DocumentCommand::UpdateEdge(
1491                self.edges
1492                    .get(&edge.id)
1493                    .ok_or_else(|| DocumentError::MissingEdge(edge.id.clone()))?
1494                    .clone(),
1495            )]),
1496            DocumentCommand::RemoveEdge(id) => Ok(vec![DocumentCommand::InsertEdge(
1497                self.edges
1498                    .get(id)
1499                    .ok_or_else(|| DocumentError::MissingEdge(id.clone()))?
1500                    .clone(),
1501            )]),
1502            DocumentCommand::InsertShape(shape) => {
1503                if self.shapes.contains_key(&shape.id) {
1504                    return Err(DocumentError::DuplicateShape(shape.id.clone()));
1505                }
1506                Ok(vec![DocumentCommand::RemoveShape(shape.id.clone())])
1507            }
1508            DocumentCommand::UpdateShape(shape) => Ok(vec![DocumentCommand::UpdateShape(
1509                self.shapes
1510                    .get(&shape.id)
1511                    .ok_or_else(|| DocumentError::MissingShape(shape.id.clone()))?
1512                    .clone(),
1513            )]),
1514            DocumentCommand::RemoveShape(id) => Ok(vec![DocumentCommand::InsertShape(
1515                self.shapes
1516                    .get(id)
1517                    .ok_or_else(|| DocumentError::MissingShape(id.clone()))?
1518                    .clone(),
1519            )]),
1520            DocumentCommand::SetRecordParent { child, parent } => {
1521                if child == parent {
1522                    return Err(DocumentError::SelfParentRelation(child.clone()));
1523                }
1524                self.validate_record_id(child)?;
1525                self.validate_record_id(parent)?;
1526                Ok(match self.relations.parent_of(child).cloned() {
1527                    Some(previous) => vec![DocumentCommand::SetRecordParent {
1528                        child: child.clone(),
1529                        parent: previous,
1530                    }],
1531                    None => vec![DocumentCommand::ClearRecordParent {
1532                        child: child.clone(),
1533                    }],
1534                })
1535            }
1536            DocumentCommand::ClearRecordParent { child } => {
1537                self.validate_record_id(child)?;
1538                Ok(match self.relations.parent_of(child).cloned() {
1539                    Some(parent) => vec![DocumentCommand::SetRecordParent {
1540                        child: child.clone(),
1541                        parent,
1542                    }],
1543                    None => Vec::new(),
1544                })
1545            }
1546            DocumentCommand::AddRecordToGroup { group, member } => {
1547                if group == member {
1548                    return Err(DocumentError::SelfParentRelation(group.clone()));
1549                }
1550                self.validate_record_id(group)?;
1551                self.validate_record_id(member)?;
1552                let already_member = self.relations.groups_for(member).any(|id| id == group);
1553                Ok(if already_member {
1554                    Vec::new()
1555                } else {
1556                    vec![DocumentCommand::RemoveRecordFromGroup {
1557                        group: group.clone(),
1558                        member: member.clone(),
1559                    }]
1560                })
1561            }
1562            DocumentCommand::RemoveRecordFromGroup { group, member } => {
1563                self.validate_record_id(group)?;
1564                self.validate_record_id(member)?;
1565                let already_member = self.relations.groups_for(member).any(|id| id == group);
1566                Ok(if already_member {
1567                    vec![DocumentCommand::AddRecordToGroup {
1568                        group: group.clone(),
1569                        member: member.clone(),
1570                    }]
1571                } else {
1572                    Vec::new()
1573                })
1574            }
1575            DocumentCommand::SetRecordBinding(binding) => {
1576                if binding.source == binding.target {
1577                    return Err(DocumentError::SelfBindingRelation(binding.source.clone()));
1578                }
1579                self.validate_record_id(&binding.source)?;
1580                self.validate_record_id(&binding.target)?;
1581                Ok(match self.relations.binding(&binding.id).cloned() {
1582                    Some(previous) => vec![DocumentCommand::SetRecordBinding(previous)],
1583                    None => vec![DocumentCommand::RemoveRecordBinding {
1584                        id: binding.id.clone(),
1585                    }],
1586                })
1587            }
1588            DocumentCommand::RemoveRecordBinding { id } => Ok(match self.relations.binding(id) {
1589                Some(binding) => vec![DocumentCommand::SetRecordBinding(binding.clone())],
1590                None => Vec::new(),
1591            }),
1592        }
1593    }
1594}
1595
1596impl TryFrom<CanvasSnapshot> for CanvasDocument {
1597    type Error = DocumentError;
1598
1599    fn try_from(value: CanvasSnapshot) -> Result<Self, Self::Error> {
1600        Self::from_snapshot(value)
1601    }
1602}
1603
1604impl From<&CanvasDocument> for CanvasSnapshot {
1605    fn from(value: &CanvasDocument) -> Self {
1606        value.to_snapshot()
1607    }
1608}
1609
1610fn default_true() -> bool {
1611    true
1612}
1613
1614fn default_kind() -> String {
1615    "default".to_string()
1616}
1617
1618fn default_handle_size() -> Size<Pixels> {
1619    Size::new(Pixels::from(12.0), Pixels::from(12.0))
1620}
1621
1622fn default_edge_interaction_width() -> Pixels {
1623    Pixels::from(12.0)
1624}
1625
1626#[cfg(test)]
1627mod tests {
1628    use super::*;
1629    use crate::test_support::{
1630        CanvasCommandGenerator, TestRng, connected_pair_fixture, document_fixture,
1631    };
1632    use crate::{
1633        CANVAS_DOCUMENT_MIN_SUPPORTED_FORMAT_VERSION, CANVAS_SNAPSHOT_MIGRATIONS,
1634        CanvasRecordParentRelation,
1635    };
1636    use open_gpui::{point, px, size};
1637
1638    #[test]
1639    fn defaults_to_current_format_version() {
1640        let document = CanvasDocument::default();
1641
1642        assert_eq!(document.format_version, CANVAS_DOCUMENT_FORMAT_VERSION);
1643    }
1644
1645    #[test]
1646    fn deserializes_missing_format_version_to_current_version() {
1647        let document: CanvasDocument = serde_json::from_str(
1648            r#"{
1649                "nodes": {},
1650                "edges": {},
1651                "shapes": {},
1652                "metadata": {}
1653            }"#,
1654        )
1655        .unwrap();
1656
1657        assert_eq!(document.format_version, CANVAS_DOCUMENT_FORMAT_VERSION);
1658    }
1659
1660    #[test]
1661    fn snapshot_round_trips_array_records() {
1662        let document = connected_pair_fixture().build();
1663
1664        let snapshot = document.to_snapshot();
1665        assert_eq!(snapshot.nodes.len(), 2);
1666        assert_eq!(snapshot.edges.len(), 1);
1667
1668        let restored = CanvasDocument::from_snapshot(snapshot).unwrap();
1669        assert_eq!(restored.nodes.len(), 2);
1670        assert_eq!(restored.edges.len(), 1);
1671    }
1672
1673    #[test]
1674    fn snapshot_round_trips_record_relations() {
1675        let mut document = document_fixture()
1676            .node(CanvasNode::new(
1677                "child",
1678                point(px(0.0), px(0.0)),
1679                size(px(10.0), px(10.0)),
1680            ))
1681            .shape(CanvasShape::new(
1682                "group",
1683                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
1684            ))
1685            .build();
1686        let child = CanvasRecordId::Node(NodeId::from("child"));
1687        let group = CanvasRecordId::Shape(ShapeId::from("group"));
1688        let binding = CanvasRecordBindingRelation::new("binding", child.clone(), group.clone());
1689        document
1690            .apply_transaction(CanvasTransaction::new([
1691                DocumentCommand::SetRecordParent {
1692                    child: child.clone(),
1693                    parent: group.clone(),
1694                },
1695                DocumentCommand::AddRecordToGroup {
1696                    group: group.clone(),
1697                    member: child.clone(),
1698                },
1699                DocumentCommand::SetRecordBinding(binding.clone()),
1700            ]))
1701            .unwrap();
1702
1703        let restored = CanvasDocument::from_snapshot(document.to_snapshot()).unwrap();
1704
1705        assert_eq!(restored.relations().parent_of(&child), Some(&group));
1706        assert_eq!(
1707            restored
1708                .relations()
1709                .members_of(&group)
1710                .cloned()
1711                .collect::<Vec<_>>(),
1712            vec![child.clone()]
1713        );
1714        assert_eq!(restored.relations().binding(&binding.id), Some(&binding));
1715    }
1716
1717    #[test]
1718    fn builder_rejects_duplicate_records_during_construction() {
1719        let mut builder = CanvasDocument::builder();
1720        builder
1721            .add_node(CanvasNode::new(
1722                "duplicate",
1723                point(px(0.0), px(0.0)),
1724                size(px(10.0), px(10.0)),
1725            ))
1726            .unwrap();
1727
1728        let error = builder
1729            .add_node(CanvasNode::new(
1730                "duplicate",
1731                point(px(20.0), px(0.0)),
1732                size(px(10.0), px(10.0)),
1733            ))
1734            .unwrap_err();
1735
1736        assert_eq!(
1737            error,
1738            DocumentError::DuplicateNode(NodeId::from("duplicate"))
1739        );
1740    }
1741
1742    #[test]
1743    fn builder_rejects_invalid_edge_endpoints_during_construction() {
1744        let mut builder = CanvasDocument::builder();
1745        builder
1746            .add_node(CanvasNode::new(
1747                "source",
1748                point(px(0.0), px(0.0)),
1749                size(px(10.0), px(10.0)),
1750            ))
1751            .unwrap();
1752
1753        let error = builder
1754            .add_edge(CanvasEdge::new(
1755                "edge",
1756                CanvasEndpoint::new("source", None::<&str>),
1757                CanvasEndpoint::new("missing", None::<&str>),
1758            ))
1759            .unwrap_err();
1760
1761        assert_eq!(error, DocumentError::MissingNode(NodeId::from("missing")));
1762    }
1763
1764    #[test]
1765    fn builder_prunes_dangling_relations_at_build_time() {
1766        let child = CanvasRecordId::Node(NodeId::from("child"));
1767        let existing_group = CanvasRecordId::Shape(ShapeId::from("group"));
1768        let missing_group = CanvasRecordId::Shape(ShapeId::from("missing"));
1769
1770        let mut relations = CanvasRecordRelations::default();
1771        relations.set_parent(child.clone(), missing_group.clone());
1772        relations.add_to_group(existing_group.clone(), child.clone());
1773        relations.add_to_group(missing_group.clone(), child.clone());
1774        relations.set_binding(CanvasRecordBindingRelation::new(
1775            "binding",
1776            child.clone(),
1777            missing_group,
1778        ));
1779
1780        let mut builder = CanvasDocument::builder().with_relations(relations);
1781        builder
1782            .add_node(CanvasNode::new(
1783                "child",
1784                point(px(0.0), px(0.0)),
1785                size(px(10.0), px(10.0)),
1786            ))
1787            .unwrap();
1788        builder
1789            .add_shape(CanvasShape::new(
1790                "group",
1791                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
1792            ))
1793            .unwrap();
1794
1795        let document = builder.build().unwrap();
1796
1797        assert_eq!(document.relations().parent_of(&child), None);
1798        assert_eq!(
1799            document
1800                .relations()
1801                .members_of(&existing_group)
1802                .cloned()
1803                .collect::<Vec<_>>(),
1804            vec![child]
1805        );
1806        assert!(document.relations().bindings().next().is_none());
1807    }
1808
1809    #[test]
1810    fn builder_rejects_duplicate_parent_relations_at_build_time() {
1811        let mut document = document_fixture()
1812            .node(CanvasNode::new(
1813                "child",
1814                point(px(0.0), px(0.0)),
1815                size(px(10.0), px(10.0)),
1816            ))
1817            .build();
1818        for id in ["frame-a", "frame-b"] {
1819            document
1820                .insert_shape(CanvasShape::new(
1821                    id,
1822                    Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
1823                ))
1824                .unwrap();
1825        }
1826        document
1827            .apply_transaction(CanvasTransaction::single(
1828                DocumentCommand::SetRecordParent {
1829                    child: CanvasRecordId::Node(NodeId::from("child")),
1830                    parent: CanvasRecordId::Shape(ShapeId::from("frame-a")),
1831                },
1832            ))
1833            .unwrap();
1834        let mut value = serde_json::to_value(document.to_snapshot()).unwrap();
1835        let parents = value["relations"]["parents"].as_array_mut().unwrap();
1836        let mut duplicate = parents[0].clone();
1837        duplicate["parent"] =
1838            serde_json::to_value(CanvasRecordId::Shape(ShapeId::from("frame-b"))).unwrap();
1839        parents.push(duplicate);
1840        let snapshot: CanvasSnapshot = serde_json::from_value(value).unwrap();
1841
1842        let mut builder = CanvasDocument::builder().with_relations(snapshot.relations);
1843        for node in snapshot.nodes {
1844            builder.add_node(node).unwrap();
1845        }
1846        for shape in snapshot.shapes {
1847            builder.add_shape(shape).unwrap();
1848        }
1849
1850        assert_eq!(
1851            builder.build().unwrap_err(),
1852            DocumentError::DuplicateParentRelation(CanvasRecordId::Node(NodeId::from("child")))
1853        );
1854    }
1855
1856    #[test]
1857    fn from_snapshot_rejects_duplicate_parent_relations_for_one_child() {
1858        let mut document = document_fixture()
1859            .node(CanvasNode::new(
1860                "child",
1861                point(px(0.0), px(0.0)),
1862                size(px(10.0), px(10.0)),
1863            ))
1864            .build();
1865        for id in ["frame-a", "frame-b"] {
1866            document
1867                .insert_shape(CanvasShape::new(
1868                    id,
1869                    Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
1870                ))
1871                .unwrap();
1872        }
1873
1874        document
1875            .apply_transaction(CanvasTransaction::single(
1876                DocumentCommand::SetRecordParent {
1877                    child: CanvasRecordId::Node(NodeId::from("child")),
1878                    parent: CanvasRecordId::Shape(ShapeId::from("frame-a")),
1879                },
1880            ))
1881            .unwrap();
1882        let mut value = serde_json::to_value(document.to_snapshot()).unwrap();
1883        let parents = value["relations"]["parents"].as_array_mut().unwrap();
1884        let mut duplicate = parents[0].clone();
1885        duplicate["parent"] =
1886            serde_json::to_value(CanvasRecordId::Shape(ShapeId::from("frame-b"))).unwrap();
1887        parents.push(duplicate);
1888        let snapshot = serde_json::from_value(value).unwrap();
1889
1890        assert_eq!(
1891            CanvasDocument::from_snapshot(snapshot).unwrap_err(),
1892            DocumentError::DuplicateParentRelation(CanvasRecordId::Node(NodeId::from("child")))
1893        );
1894    }
1895
1896    #[test]
1897    fn from_snapshot_rejects_duplicate_group_relations() {
1898        let mut document = document_fixture()
1899            .node(CanvasNode::new(
1900                "child",
1901                point(px(0.0), px(0.0)),
1902                size(px(10.0), px(10.0)),
1903            ))
1904            .shape(CanvasShape::new(
1905                "group",
1906                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
1907            ))
1908            .build();
1909        let group = CanvasRecordId::Shape(ShapeId::from("group"));
1910        let member = CanvasRecordId::Node(NodeId::from("child"));
1911        document
1912            .apply_transaction(CanvasTransaction::single(
1913                DocumentCommand::AddRecordToGroup {
1914                    group: group.clone(),
1915                    member: member.clone(),
1916                },
1917            ))
1918            .unwrap();
1919        let mut value = serde_json::to_value(document.to_snapshot()).unwrap();
1920        let groups = value["relations"]["groups"].as_array_mut().unwrap();
1921        groups.push(groups[0].clone());
1922        let snapshot = serde_json::from_value(value).unwrap();
1923
1924        assert_eq!(
1925            CanvasDocument::from_snapshot(snapshot).unwrap_err(),
1926            DocumentError::DuplicateGroupRelation { group, member }
1927        );
1928    }
1929
1930    #[test]
1931    fn relation_commands_reject_parent_cycles() {
1932        let mut document = document_fixture()
1933            .shape(CanvasShape::new(
1934                "frame-a",
1935                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
1936            ))
1937            .shape(CanvasShape::new(
1938                "frame-b",
1939                Bounds::new(point(px(20.0), px(20.0)), size(px(40.0), px(40.0))),
1940            ))
1941            .build();
1942        document
1943            .apply_transaction(CanvasTransaction::single(
1944                DocumentCommand::SetRecordParent {
1945                    child: CanvasRecordId::Shape(ShapeId::from("frame-b")),
1946                    parent: CanvasRecordId::Shape(ShapeId::from("frame-a")),
1947                },
1948            ))
1949            .unwrap();
1950
1951        let err = document
1952            .apply_transaction(CanvasTransaction::single(
1953                DocumentCommand::SetRecordParent {
1954                    child: CanvasRecordId::Shape(ShapeId::from("frame-a")),
1955                    parent: CanvasRecordId::Shape(ShapeId::from("frame-b")),
1956                },
1957            ))
1958            .unwrap_err();
1959
1960        assert_eq!(
1961            err,
1962            DocumentError::CyclicRecordRelation(CanvasRecordId::Shape(ShapeId::from("frame-a")))
1963        );
1964    }
1965
1966    #[test]
1967    fn relation_commands_reject_group_cycles() {
1968        let mut document = document_fixture()
1969            .shape(CanvasShape::new(
1970                "group-a",
1971                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
1972            ))
1973            .shape(CanvasShape::new(
1974                "group-b",
1975                Bounds::new(point(px(20.0), px(20.0)), size(px(40.0), px(40.0))),
1976            ))
1977            .build();
1978        document
1979            .apply_transaction(CanvasTransaction::single(
1980                DocumentCommand::AddRecordToGroup {
1981                    group: CanvasRecordId::Shape(ShapeId::from("group-a")),
1982                    member: CanvasRecordId::Shape(ShapeId::from("group-b")),
1983                },
1984            ))
1985            .unwrap();
1986
1987        let err = document
1988            .apply_transaction(CanvasTransaction::single(
1989                DocumentCommand::AddRecordToGroup {
1990                    group: CanvasRecordId::Shape(ShapeId::from("group-b")),
1991                    member: CanvasRecordId::Shape(ShapeId::from("group-a")),
1992                },
1993            ))
1994            .unwrap_err();
1995
1996        assert_eq!(
1997            err,
1998            DocumentError::CyclicRecordRelation(CanvasRecordId::Shape(ShapeId::from("group-a")))
1999        );
2000    }
2001
2002    #[test]
2003    fn from_snapshot_rejects_relation_cycles() {
2004        let mut document = document_fixture()
2005            .shape(CanvasShape::new(
2006                "frame-a",
2007                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
2008            ))
2009            .shape(CanvasShape::new(
2010                "frame-b",
2011                Bounds::new(point(px(20.0), px(20.0)), size(px(40.0), px(40.0))),
2012            ))
2013            .build();
2014        document
2015            .apply_transaction(CanvasTransaction::single(
2016                DocumentCommand::SetRecordParent {
2017                    child: CanvasRecordId::Shape(ShapeId::from("frame-b")),
2018                    parent: CanvasRecordId::Shape(ShapeId::from("frame-a")),
2019                },
2020            ))
2021            .unwrap();
2022        let mut value = serde_json::to_value(document.to_snapshot()).unwrap();
2023        let parents = value["relations"]["parents"].as_array_mut().unwrap();
2024        parents.push(
2025            serde_json::to_value(CanvasRecordParentRelation::new(
2026                CanvasRecordId::Shape(ShapeId::from("frame-a")),
2027                CanvasRecordId::Shape(ShapeId::from("frame-b")),
2028            ))
2029            .unwrap(),
2030        );
2031        let snapshot = serde_json::from_value(value).unwrap();
2032
2033        assert_eq!(
2034            CanvasDocument::from_snapshot(snapshot).unwrap_err(),
2035            DocumentError::CyclicRecordRelation(CanvasRecordId::Shape(ShapeId::from("frame-a")))
2036        );
2037    }
2038
2039    #[test]
2040    fn from_snapshot_rejects_duplicate_binding_relations() {
2041        let mut document = document_fixture()
2042            .node(CanvasNode::new(
2043                "source",
2044                point(px(0.0), px(0.0)),
2045                size(px(10.0), px(10.0)),
2046            ))
2047            .shape(CanvasShape::new(
2048                "target",
2049                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
2050            ))
2051            .build();
2052        let binding = CanvasRecordBindingRelation::new(
2053            "binding",
2054            CanvasRecordId::Node(NodeId::from("source")),
2055            CanvasRecordId::Shape(ShapeId::from("target")),
2056        );
2057        document
2058            .apply_transaction(CanvasTransaction::single(
2059                DocumentCommand::SetRecordBinding(binding),
2060            ))
2061            .unwrap();
2062        let mut value = serde_json::to_value(document.to_snapshot()).unwrap();
2063        let bindings = value["relations"]["bindings"].as_array_mut().unwrap();
2064        bindings.push(bindings[0].clone());
2065        let snapshot = serde_json::from_value(value).unwrap();
2066
2067        assert_eq!(
2068            CanvasDocument::from_snapshot(snapshot).unwrap_err(),
2069            DocumentError::DuplicateBindingRelation(BindingId::from("binding"))
2070        );
2071    }
2072
2073    #[test]
2074    fn relation_commands_reject_self_binding() {
2075        let mut document = document_fixture()
2076            .node(CanvasNode::new(
2077                "source",
2078                point(px(0.0), px(0.0)),
2079                size(px(10.0), px(10.0)),
2080            ))
2081            .build();
2082        let source = CanvasRecordId::Node(NodeId::from("source"));
2083
2084        let err = document
2085            .apply_transaction(CanvasTransaction::single(
2086                DocumentCommand::SetRecordBinding(CanvasRecordBindingRelation::new(
2087                    "binding",
2088                    source.clone(),
2089                    source.clone(),
2090                )),
2091            ))
2092            .unwrap_err();
2093
2094        assert_eq!(err, DocumentError::SelfBindingRelation(source));
2095    }
2096
2097    #[test]
2098    fn current_snapshot_migration_is_noop() {
2099        let mut snapshot = CanvasSnapshot::default();
2100        snapshot.nodes.push(CanvasNode::new(
2101            "a",
2102            point(px(0.0), px(0.0)),
2103            size(px(10.0), px(10.0)),
2104        ));
2105        snapshot.metadata.insert("title".into(), "Canvas".into());
2106
2107        let migrated = snapshot.clone().migrate_to_current().unwrap();
2108
2109        assert_eq!(migrated, snapshot);
2110        assert_eq!(migrated.format_version, CANVAS_DOCUMENT_FORMAT_VERSION);
2111    }
2112
2113    #[test]
2114    fn from_snapshot_accepts_current_snapshot_through_migration_boundary() {
2115        let snapshot = CanvasSnapshot {
2116            nodes: vec![CanvasNode::new(
2117                "a",
2118                point(px(0.0), px(0.0)),
2119                size(px(10.0), px(10.0)),
2120            )],
2121            ..CanvasSnapshot::default()
2122        };
2123
2124        let document = CanvasDocument::from_snapshot(snapshot).unwrap();
2125
2126        assert_eq!(document.format_version, CANVAS_DOCUMENT_FORMAT_VERSION);
2127        assert_eq!(document.nodes.len(), 1);
2128    }
2129
2130    #[test]
2131    fn rejects_future_snapshot_version() {
2132        let snapshot = CanvasSnapshot {
2133            format_version: CANVAS_DOCUMENT_FORMAT_VERSION + 1,
2134            ..CanvasSnapshot::default()
2135        };
2136
2137        assert_eq!(
2138            CanvasDocument::from_snapshot(snapshot).unwrap_err(),
2139            DocumentError::UnsupportedFormatVersion {
2140                expected: CANVAS_DOCUMENT_FORMAT_VERSION,
2141                found: CANVAS_DOCUMENT_FORMAT_VERSION + 1,
2142            }
2143        );
2144    }
2145
2146    #[test]
2147    fn rejects_snapshot_version_below_minimum_supported_version() {
2148        let snapshot = CanvasSnapshot {
2149            format_version: CANVAS_DOCUMENT_MIN_SUPPORTED_FORMAT_VERSION - 1,
2150            ..CanvasSnapshot::default()
2151        };
2152
2153        assert_eq!(
2154            migrate_canvas_snapshot(snapshot).unwrap_err(),
2155            DocumentError::UnsupportedFormatVersion {
2156                expected: CANVAS_DOCUMENT_FORMAT_VERSION,
2157                found: CANVAS_DOCUMENT_MIN_SUPPORTED_FORMAT_VERSION - 1,
2158            }
2159        );
2160    }
2161
2162    #[test]
2163    fn snapshot_migration_table_is_monotonic() {
2164        for migration in CANVAS_SNAPSHOT_MIGRATIONS {
2165            assert!(migration.from_version < migration.to_version);
2166        }
2167
2168        for migrations in CANVAS_SNAPSHOT_MIGRATIONS.windows(2) {
2169            assert_eq!(migrations[0].to_version, migrations[1].from_version);
2170        }
2171    }
2172
2173    #[test]
2174    fn removes_edges_when_node_is_removed() {
2175        let mut document = connected_pair_fixture().build();
2176
2177        document.remove_node(&NodeId::from("a")).unwrap();
2178
2179        assert!(document.edges.is_empty());
2180    }
2181
2182    #[test]
2183    fn validates_edge_handles() {
2184        let mut node = CanvasNode::new("a", point(px(0.0), px(0.0)), size(px(10.0), px(10.0)));
2185        node.handles
2186            .push(CanvasHandle::new("out", point(px(10.0), px(5.0))));
2187        let mut document = document_fixture()
2188            .node(node)
2189            .node(CanvasNode::new(
2190                "b",
2191                point(px(20.0), px(0.0)),
2192                size(px(10.0), px(10.0)),
2193            ))
2194            .build();
2195
2196        let err = document
2197            .insert_edge(CanvasEdge::new(
2198                "bad",
2199                CanvasEndpoint::new("a", Some("missing")),
2200                CanvasEndpoint::new("b", None::<&str>),
2201            ))
2202            .unwrap_err();
2203
2204        assert_eq!(
2205            err,
2206            DocumentError::MissingHandle {
2207                node_id: NodeId::from("a"),
2208                handle_id: HandleId::from("missing")
2209            }
2210        );
2211    }
2212
2213    #[test]
2214    fn handle_connection_role_helpers_respect_roles_and_pickability() {
2215        let mut source_only = CanvasHandle::new("out", point(px(10.0), px(5.0)));
2216        source_only.role = HandleRole::Source;
2217        assert!(source_only.accepts_connection_role(CanvasConnectionEndpointRole::Source));
2218        assert!(!source_only.accepts_connection_role(CanvasConnectionEndpointRole::Target));
2219        assert!(source_only.is_pickable_connection_endpoint(CanvasConnectionEndpointRole::Source));
2220
2221        source_only.hidden = true;
2222        assert!(source_only.accepts_connection_role(CanvasConnectionEndpointRole::Source));
2223        assert!(!source_only.is_pickable_connection_endpoint(CanvasConnectionEndpointRole::Source));
2224
2225        let mut target_only = CanvasHandle::new("in", point(px(0.0), px(5.0)));
2226        target_only.role = HandleRole::Target;
2227        target_only.connectable = false;
2228        assert!(!target_only.accepts_connection_role(CanvasConnectionEndpointRole::Target));
2229        assert!(!target_only.is_pickable_connection_endpoint(CanvasConnectionEndpointRole::Target));
2230    }
2231
2232    #[test]
2233    fn rejects_duplicate_handles_on_node_insert() {
2234        let mut node = CanvasNode::new("a", point(px(0.0), px(0.0)), size(px(10.0), px(10.0)));
2235        node.handles
2236            .push(CanvasHandle::new("out", point(px(10.0), px(5.0))));
2237        node.handles
2238            .push(CanvasHandle::new("out", point(px(0.0), px(5.0))));
2239
2240        let mut document = document_fixture().build();
2241        let err = document.insert_node(node).unwrap_err();
2242
2243        assert_eq!(
2244            err,
2245            DocumentError::DuplicateHandle {
2246                node_id: NodeId::from("a"),
2247                handle_id: HandleId::from("out")
2248            }
2249        );
2250    }
2251
2252    #[test]
2253    fn validates_handle_roles_for_edges() {
2254        let mut source = CanvasNode::new("a", point(px(0.0), px(0.0)), size(px(10.0), px(10.0)));
2255        let mut target_only = CanvasHandle::new("in", point(px(10.0), px(5.0)));
2256        target_only.role = HandleRole::Target;
2257        source.handles.push(target_only);
2258
2259        let mut target = CanvasNode::new("b", point(px(20.0), px(0.0)), size(px(10.0), px(10.0)));
2260        let mut source_only = CanvasHandle::new("out", point(px(0.0), px(5.0)));
2261        source_only.role = HandleRole::Source;
2262        target.handles.push(source_only);
2263
2264        let mut document = document_fixture().node(source).node(target).build();
2265
2266        let err = document
2267            .insert_edge(CanvasEdge::new(
2268                "bad",
2269                CanvasEndpoint::new("a", Some("in")),
2270                CanvasEndpoint::new("b", Some("out")),
2271            ))
2272            .unwrap_err();
2273
2274        assert_eq!(
2275            err,
2276            DocumentError::InvalidSourceHandle {
2277                node_id: NodeId::from("a"),
2278                handle_id: HandleId::from("in")
2279            }
2280        );
2281    }
2282
2283    #[test]
2284    fn rejects_non_connectable_edge_handles() {
2285        let mut source = CanvasNode::new("a", point(px(0.0), px(0.0)), size(px(10.0), px(10.0)));
2286        let mut handle = CanvasHandle::new("out", point(px(10.0), px(5.0)));
2287        handle.connectable = false;
2288        source.handles.push(handle);
2289
2290        let mut document = document_fixture()
2291            .node(source)
2292            .node(CanvasNode::new(
2293                "b",
2294                point(px(20.0), px(0.0)),
2295                size(px(10.0), px(10.0)),
2296            ))
2297            .build();
2298
2299        let err = document
2300            .insert_edge(CanvasEdge::new(
2301                "bad",
2302                CanvasEndpoint::new("a", Some("out")),
2303                CanvasEndpoint::new("b", None::<&str>),
2304            ))
2305            .unwrap_err();
2306
2307        assert_eq!(
2308            err,
2309            DocumentError::NonConnectableHandle {
2310                node_id: NodeId::from("a"),
2311                handle_id: HandleId::from("out")
2312            }
2313        );
2314    }
2315
2316    #[test]
2317    fn edge_route_defaults_keep_legacy_edges_readable() {
2318        let edge: CanvasEdge = serde_json::from_str(
2319            r#"{
2320                "id": "a-b",
2321                "source": { "node_id": "a" },
2322                "target": { "node_id": "b" }
2323            }"#,
2324        )
2325        .unwrap();
2326
2327        assert_eq!(
2328            edge.route.kind,
2329            CanvasEdgeRouteKind::from(CanvasEdgeRouteKind::STRAIGHT)
2330        );
2331        assert!(edge.route.waypoints.is_empty());
2332        assert!(edge.route.control_points.is_empty());
2333        assert_eq!(edge.route.interaction_width, px(12.0));
2334    }
2335
2336    #[test]
2337    fn edge_route_points_include_waypoints_between_endpoints() {
2338        let document = document_fixture()
2339            .node(CanvasNode::new(
2340                "a",
2341                point(px(0.0), px(0.0)),
2342                size(px(10.0), px(10.0)),
2343            ))
2344            .node(CanvasNode::new(
2345                "b",
2346                point(px(100.0), px(0.0)),
2347                size(px(10.0), px(10.0)),
2348            ))
2349            .build();
2350        let mut edge = CanvasEdge::new(
2351            "a-b",
2352            CanvasEndpoint::new("a", None::<&str>),
2353            CanvasEndpoint::new("b", None::<&str>),
2354        );
2355        edge.route =
2356            CanvasEdgeRoute::polyline([point(px(40.0), px(50.0)), point(px(80.0), px(50.0))]);
2357
2358        let route_points = document.edge_route_points(&edge).unwrap();
2359
2360        assert_eq!(
2361            route_points,
2362            vec![
2363                point(px(5.0), px(5.0)),
2364                point(px(40.0), px(50.0)),
2365                point(px(80.0), px(50.0)),
2366                point(px(105.0), px(5.0)),
2367            ]
2368        );
2369    }
2370
2371    #[test]
2372    fn edge_bounds_include_route_points_and_interaction_width() {
2373        let document = document_fixture()
2374            .node(CanvasNode::new(
2375                "a",
2376                point(px(0.0), px(0.0)),
2377                size(px(10.0), px(10.0)),
2378            ))
2379            .node(CanvasNode::new(
2380                "b",
2381                point(px(100.0), px(0.0)),
2382                size(px(10.0), px(10.0)),
2383            ))
2384            .build();
2385        let mut edge = CanvasEdge::new(
2386            "a-b",
2387            CanvasEndpoint::new("a", None::<&str>),
2388            CanvasEndpoint::new("b", None::<&str>),
2389        );
2390        edge.route = CanvasEdgeRoute::polyline([point(px(40.0), px(50.0))]);
2391        edge.route.interaction_width = px(20.0);
2392
2393        let bounds = document.edge_bounds(&edge).unwrap();
2394
2395        assert_eq!(bounds.origin, point(px(-5.0), px(-5.0)));
2396        assert_eq!(bounds.size, size(px(120.0), px(65.0)));
2397    }
2398
2399    #[test]
2400    fn edge_bounds_can_use_custom_router_path() {
2401        struct OffsetRouter;
2402
2403        impl crate::CanvasEdgeRouter for OffsetRouter {
2404            fn route_edge(&self, request: crate::CanvasRouteRequest<'_>) -> crate::CanvasRoutePath {
2405                crate::CanvasRoutePath::polyline([
2406                    request.source,
2407                    point(px(50.0), px(120.0)),
2408                    request.target,
2409                ])
2410            }
2411        }
2412
2413        let document = document_fixture()
2414            .node(CanvasNode::new(
2415                "a",
2416                point(px(0.0), px(0.0)),
2417                size(px(10.0), px(10.0)),
2418            ))
2419            .node(CanvasNode::new(
2420                "b",
2421                point(px(100.0), px(0.0)),
2422                size(px(10.0), px(10.0)),
2423            ))
2424            .build();
2425        let mut edge = CanvasEdge::new(
2426            "a-b",
2427            CanvasEndpoint::new("a", None::<&str>),
2428            CanvasEndpoint::new("b", None::<&str>),
2429        );
2430        edge.route.interaction_width = px(10.0);
2431
2432        let path = document
2433            .edge_route_path_with_router(&edge, &OffsetRouter)
2434            .unwrap();
2435        let bounds = document
2436            .edge_bounds_with_router(&edge, &OffsetRouter)
2437            .unwrap();
2438
2439        assert_eq!(
2440            path.document_points(),
2441            vec![
2442                point(px(5.0), px(5.0)),
2443                point(px(50.0), px(120.0)),
2444                point(px(105.0), px(5.0)),
2445            ]
2446        );
2447        assert_eq!(bounds.origin, point(px(0.0), px(0.0)));
2448        assert_eq!(bounds.size, size(px(110.0), px(125.0)));
2449    }
2450
2451    #[test]
2452    fn rejects_invalid_edge_route_metadata() {
2453        let mut document = document_fixture()
2454            .node(CanvasNode::new(
2455                "a",
2456                point(px(0.0), px(0.0)),
2457                size(px(10.0), px(10.0)),
2458            ))
2459            .node(CanvasNode::new(
2460                "b",
2461                point(px(100.0), px(0.0)),
2462                size(px(10.0), px(10.0)),
2463            ))
2464            .build();
2465
2466        let mut empty_kind = CanvasEdge::new(
2467            "empty-kind",
2468            CanvasEndpoint::new("a", None::<&str>),
2469            CanvasEndpoint::new("b", None::<&str>),
2470        );
2471        empty_kind.route.kind = CanvasEdgeRouteKind::new("");
2472        assert_eq!(
2473            document.insert_edge(empty_kind).unwrap_err(),
2474            DocumentError::EmptyEdgeRouteKind(EdgeId::from("empty-kind"))
2475        );
2476
2477        let mut negative_width = CanvasEdge::new(
2478            "negative-width",
2479            CanvasEndpoint::new("a", None::<&str>),
2480            CanvasEndpoint::new("b", None::<&str>),
2481        );
2482        negative_width.route.interaction_width = px(-1.0);
2483        assert_eq!(
2484            document.insert_edge(negative_width).unwrap_err(),
2485            DocumentError::InvalidEdgeInteractionWidth(EdgeId::from("negative-width"))
2486        );
2487
2488        let mut invalid_point = CanvasEdge::new(
2489            "invalid-point",
2490            CanvasEndpoint::new("a", None::<&str>),
2491            CanvasEndpoint::new("b", None::<&str>),
2492        );
2493        invalid_point
2494            .route
2495            .waypoints
2496            .push(point(px(f32::NAN), px(0.0)));
2497        assert_eq!(
2498            document.insert_edge(invalid_point).unwrap_err(),
2499            DocumentError::InvalidEdgeRoutePoint(EdgeId::from("invalid-point"))
2500        );
2501    }
2502
2503    #[test]
2504    fn node_update_cannot_break_existing_edge_endpoints() {
2505        let mut node = CanvasNode::new("a", point(px(0.0), px(0.0)), size(px(10.0), px(10.0)));
2506        node.handles
2507            .push(CanvasHandle::new("out", point(px(10.0), px(5.0))));
2508
2509        let mut document = document_fixture()
2510            .node(node.clone())
2511            .node(CanvasNode::new(
2512                "b",
2513                point(px(20.0), px(0.0)),
2514                size(px(10.0), px(10.0)),
2515            ))
2516            .edge(CanvasEdge::new(
2517                "a-b",
2518                CanvasEndpoint::new("a", Some("out")),
2519                CanvasEndpoint::new("b", None::<&str>),
2520            ))
2521            .build();
2522
2523        node.handles.clear();
2524        let err = document
2525            .apply_transaction_with_diff(CanvasTransaction::single(DocumentCommand::UpdateNode(
2526                node,
2527            )))
2528            .unwrap_err();
2529
2530        assert_eq!(
2531            err,
2532            DocumentError::MissingHandle {
2533                node_id: NodeId::from("a"),
2534                handle_id: HandleId::from("out")
2535            }
2536        );
2537        assert!(
2538            document.nodes[&NodeId::from("a")]
2539                .handle(Some(&HandleId::from("out")))
2540                .is_some()
2541        );
2542    }
2543
2544    #[test]
2545    fn applies_transaction_atomically() {
2546        let mut document = document_fixture().build();
2547        let transaction = CanvasTransaction::new([
2548            DocumentCommand::InsertNode(CanvasNode::new(
2549                "a",
2550                point(px(0.0), px(0.0)),
2551                size(px(10.0), px(10.0)),
2552            )),
2553            DocumentCommand::InsertEdge(CanvasEdge::new(
2554                "bad",
2555                CanvasEndpoint::new("a", None::<&str>),
2556                CanvasEndpoint::new("missing", None::<&str>),
2557            )),
2558        ]);
2559
2560        let err = document.apply_transaction(transaction).unwrap_err();
2561
2562        assert_eq!(err, DocumentError::MissingNode(NodeId::from("missing")));
2563        assert!(document.nodes.is_empty());
2564        assert!(document.edges.is_empty());
2565    }
2566
2567    #[test]
2568    fn transaction_inverse_restores_document() {
2569        let mut document = document_fixture()
2570            .node(CanvasNode::new(
2571                "a",
2572                point(px(0.0), px(0.0)),
2573                size(px(10.0), px(10.0)),
2574            ))
2575            .build();
2576
2577        let before = document.clone();
2578        let transaction = CanvasTransaction::new([
2579            DocumentCommand::InsertNode(CanvasNode::new(
2580                "b",
2581                point(px(20.0), px(0.0)),
2582                size(px(10.0), px(10.0)),
2583            )),
2584            DocumentCommand::InsertEdge(CanvasEdge::new(
2585                "a-b",
2586                CanvasEndpoint::new("a", None::<&str>),
2587                CanvasEndpoint::new("b", None::<&str>),
2588            )),
2589        ]);
2590        let inverse = document.invert_transaction(&transaction).unwrap();
2591
2592        document.apply_transaction(transaction).unwrap();
2593        assert_ne!(document, before);
2594
2595        document.apply_transaction(inverse).unwrap();
2596        assert_eq!(document, before);
2597    }
2598
2599    #[test]
2600    fn transaction_diff_tracks_record_changes() {
2601        let mut document = document_fixture()
2602            .node(CanvasNode::new(
2603                "a",
2604                point(px(0.0), px(0.0)),
2605                size(px(10.0), px(10.0)),
2606            ))
2607            .build();
2608
2609        let moved_a = CanvasNode::new("a", point(px(5.0), px(0.0)), size(px(10.0), px(10.0)));
2610        let transaction = CanvasTransaction::new([
2611            DocumentCommand::UpdateNode(moved_a),
2612            DocumentCommand::InsertNode(CanvasNode::new(
2613                "b",
2614                point(px(20.0), px(0.0)),
2615                size(px(10.0), px(10.0)),
2616            )),
2617            DocumentCommand::InsertEdge(CanvasEdge::new(
2618                "a-b",
2619                CanvasEndpoint::new("a", None::<&str>),
2620                CanvasEndpoint::new("b", None::<&str>),
2621            )),
2622        ]);
2623
2624        let diff = document.apply_transaction_with_diff(transaction).unwrap();
2625
2626        assert_eq!(
2627            diff.updated.iter().cloned().collect::<Vec<_>>(),
2628            vec![CanvasRecordId::Node(NodeId::from("a"))]
2629        );
2630        assert_eq!(
2631            diff.inserted.iter().cloned().collect::<Vec<_>>(),
2632            vec![
2633                CanvasRecordId::Node(NodeId::from("b")),
2634                CanvasRecordId::Edge(EdgeId::from("a-b")),
2635            ]
2636        );
2637        assert!(diff.removed.is_empty());
2638    }
2639
2640    #[test]
2641    fn transaction_diff_tracks_relation_changes() {
2642        let mut document = document_fixture()
2643            .node(CanvasNode::new(
2644                "child",
2645                point(px(0.0), px(0.0)),
2646                size(px(10.0), px(10.0)),
2647            ))
2648            .shape(CanvasShape::new(
2649                "group",
2650                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
2651            ))
2652            .build();
2653
2654        let diff = document
2655            .apply_transaction_with_diff(CanvasTransaction::single(
2656                DocumentCommand::SetRecordParent {
2657                    child: CanvasRecordId::Node(NodeId::from("child")),
2658                    parent: CanvasRecordId::Shape(ShapeId::from("group")),
2659                },
2660            ))
2661            .unwrap();
2662
2663        assert!(diff.relations_changed);
2664        assert!(!diff.is_empty());
2665        assert!(diff.inserted.is_empty());
2666        assert!(diff.updated.is_empty());
2667        assert!(diff.removed.is_empty());
2668    }
2669
2670    #[test]
2671    fn relation_commands_reject_dangling_records() {
2672        let mut document = document_fixture()
2673            .node(CanvasNode::new(
2674                "child",
2675                point(px(0.0), px(0.0)),
2676                size(px(10.0), px(10.0)),
2677            ))
2678            .build();
2679
2680        let err = document
2681            .apply_transaction(CanvasTransaction::single(
2682                DocumentCommand::SetRecordParent {
2683                    child: CanvasRecordId::Node(NodeId::from("child")),
2684                    parent: CanvasRecordId::Shape(ShapeId::from("missing")),
2685                },
2686            ))
2687            .unwrap_err();
2688
2689        assert_eq!(
2690            err,
2691            DocumentError::MissingRelationRecord(CanvasRecordId::Shape(ShapeId::from("missing")))
2692        );
2693        assert!(document.relations().is_empty());
2694
2695        let err = document
2696            .apply_transaction(CanvasTransaction::single(
2697                DocumentCommand::SetRecordBinding(CanvasRecordBindingRelation::new(
2698                    "binding",
2699                    CanvasRecordId::Node(NodeId::from("child")),
2700                    CanvasRecordId::Shape(ShapeId::from("missing")),
2701                )),
2702            ))
2703            .unwrap_err();
2704
2705        assert_eq!(
2706            err,
2707            DocumentError::MissingRelationRecord(CanvasRecordId::Shape(ShapeId::from("missing")))
2708        );
2709    }
2710
2711    #[test]
2712    fn deleting_records_prunes_relations() {
2713        let mut document = document_fixture()
2714            .node(CanvasNode::new(
2715                "child",
2716                point(px(0.0), px(0.0)),
2717                size(px(10.0), px(10.0)),
2718            ))
2719            .shape(CanvasShape::new(
2720                "group",
2721                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
2722            ))
2723            .build();
2724        document
2725            .apply_transaction(CanvasTransaction::new([
2726                DocumentCommand::SetRecordParent {
2727                    child: CanvasRecordId::Node(NodeId::from("child")),
2728                    parent: CanvasRecordId::Shape(ShapeId::from("group")),
2729                },
2730                DocumentCommand::AddRecordToGroup {
2731                    group: CanvasRecordId::Shape(ShapeId::from("group")),
2732                    member: CanvasRecordId::Node(NodeId::from("child")),
2733                },
2734                DocumentCommand::SetRecordBinding(CanvasRecordBindingRelation::new(
2735                    "binding",
2736                    CanvasRecordId::Node(NodeId::from("child")),
2737                    CanvasRecordId::Shape(ShapeId::from("group")),
2738                )),
2739            ]))
2740            .unwrap();
2741
2742        let diff = document
2743            .apply_transaction_with_diff(CanvasTransaction::single(DocumentCommand::RemoveNode(
2744                NodeId::from("child"),
2745            )))
2746            .unwrap();
2747
2748        assert!(diff.relations_changed);
2749        assert!(document.relations().is_empty());
2750    }
2751
2752    #[test]
2753    fn relation_inverse_restores_previous_relations() {
2754        let mut document = document_fixture()
2755            .node(CanvasNode::new(
2756                "child",
2757                point(px(0.0), px(0.0)),
2758                size(px(10.0), px(10.0)),
2759            ))
2760            .shape(CanvasShape::new(
2761                "group",
2762                Bounds::new(point(px(0.0), px(0.0)), size(px(100.0), px(100.0))),
2763            ))
2764            .build();
2765        let before = document.clone();
2766        let transaction = CanvasTransaction::new([
2767            DocumentCommand::SetRecordParent {
2768                child: CanvasRecordId::Node(NodeId::from("child")),
2769                parent: CanvasRecordId::Shape(ShapeId::from("group")),
2770            },
2771            DocumentCommand::AddRecordToGroup {
2772                group: CanvasRecordId::Shape(ShapeId::from("group")),
2773                member: CanvasRecordId::Node(NodeId::from("child")),
2774            },
2775            DocumentCommand::SetRecordBinding(CanvasRecordBindingRelation::new(
2776                "binding",
2777                CanvasRecordId::Node(NodeId::from("child")),
2778                CanvasRecordId::Shape(ShapeId::from("group")),
2779            )),
2780        ]);
2781        let inverse = document.invert_transaction(&transaction).unwrap();
2782
2783        document.apply_transaction(transaction).unwrap();
2784        assert!(!document.relations().is_empty());
2785
2786        document.apply_transaction(inverse).unwrap();
2787        assert_eq!(document, before);
2788    }
2789
2790    #[test]
2791    fn transaction_diff_compacts_insert_then_remove() {
2792        let mut document = document_fixture().build();
2793        let transaction = CanvasTransaction::new([
2794            DocumentCommand::InsertNode(CanvasNode::new(
2795                "temp",
2796                point(px(0.0), px(0.0)),
2797                size(px(10.0), px(10.0)),
2798            )),
2799            DocumentCommand::RemoveNode(NodeId::from("temp")),
2800        ]);
2801
2802        let diff = document.apply_transaction_with_diff(transaction).unwrap();
2803
2804        assert!(diff.is_empty());
2805        assert!(document.nodes.is_empty());
2806    }
2807
2808    #[test]
2809    fn transaction_diff_includes_edges_removed_with_node() {
2810        let mut document = connected_pair_fixture().build();
2811
2812        let diff = document
2813            .apply_transaction_with_diff(CanvasTransaction::single(DocumentCommand::RemoveNode(
2814                NodeId::from("a"),
2815            )))
2816            .unwrap();
2817
2818        assert_eq!(
2819            diff.removed.iter().cloned().collect::<Vec<_>>(),
2820            vec![
2821                CanvasRecordId::Node(NodeId::from("a")),
2822                CanvasRecordId::Edge(EdgeId::from("a-b")),
2823            ]
2824        );
2825        assert!(document.edges.is_empty());
2826    }
2827
2828    #[test]
2829    fn document_diff_tracks_metadata_changes() {
2830        let previous = document_fixture().build();
2831        let mut document = previous.clone();
2832        document
2833            .metadata
2834            .insert("title".to_string(), serde_json::json!("Canvas"));
2835
2836        let diff = document.diff_against(&previous);
2837
2838        assert!(diff.metadata_changed);
2839        assert!(!diff.is_empty());
2840    }
2841
2842    #[test]
2843    fn randomized_transaction_batches_match_final_diff_and_inverse() {
2844        let mut rng = TestRng::new(0x7a99_21c8_5f01_4d3b);
2845        let mut generator = CanvasCommandGenerator::default();
2846        let mut document = document_fixture().build();
2847
2848        for _ in 0..96 {
2849            let before = document.clone();
2850            let mut draft = before.clone();
2851            let mut commands = Vec::new();
2852
2853            for _ in 0..(1 + rng.usize(6)) {
2854                let command = generator.next_command(&draft, &mut rng);
2855                draft.apply(command.clone()).unwrap();
2856                commands.push(command);
2857            }
2858
2859            let transaction = CanvasTransaction::new(commands);
2860            let inverse = before.invert_transaction(&transaction).unwrap();
2861            let diff = document
2862                .apply_transaction_with_diff(transaction.clone())
2863                .unwrap();
2864
2865            assert_eq!(document, draft);
2866            assert_eq!(diff, document.diff_against(&before));
2867            document.validate_integrity().unwrap();
2868
2869            document.apply_transaction(inverse).unwrap();
2870            assert_eq!(document, before);
2871
2872            document.apply_transaction(transaction).unwrap();
2873            assert_eq!(document, draft);
2874        }
2875    }
2876}