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 #[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}