From 3311bfee47681b9ae961e078461c88d563ba9393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Sat, 18 Feb 2023 11:54:58 -0300 Subject: [PATCH 1/6] compare graphs --- d2graph/d2graph.go | 313 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 1300289d6..094ffd9fe 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -1582,3 +1582,316 @@ func (g *Graph) SortEdgesByAST() { }) g.Edges = edges } + +func (g *Graph) Compare(other *Graph) error { + if len(g.Objects) != len(other.Objects) { + return fmt.Errorf("object count differs: g=%d, other=%d", len(g.Objects), len(other.Objects)) + } + + if len(g.Edges) != len(other.Edges) { + return fmt.Errorf("edge count differs: g=%d, other=%d", len(g.Edges), len(other.Edges)) + } + + if err := g.Root.Compare(other.Root); err != nil { + return fmt.Errorf("root differs: %w", err) + } + + for i := 0; i < len(g.Objects); i++ { + if err := g.Objects[i].Compare(other.Objects[i]); err != nil { + return fmt.Errorf( + "objects differ at %d [g=%s, other=%s]: %w", + i, + g.Objects[i].ID, + other.Objects[i].ID, + err, + ) + } + } + + for i := 0; i < len(g.Edges); i++ { + if err := g.Edges[i].Compare(other.Edges[i]); err != nil { + return fmt.Errorf( + "edges differ at %d [g=%s, other=%s]: %w", + i, + g.Edges[i].AbsID(), + other.Edges[i].AbsID(), + err, + ) + } + } + + return nil +} + +func (obj *Object) Compare(other *Object) error { + if obj != nil && other == nil { + return fmt.Errorf("other is nil") + } else if obj == nil && other != nil { + return fmt.Errorf("obj is nil") + } else if obj == nil { + // both are nil + return nil + } + + if obj.ID != other.ID { + return fmt.Errorf("ids differ: obj=%s, other=%s", obj.ID, other.ID) + } + + if obj.AbsID() != other.AbsID() { + return fmt.Errorf("absolute ids differ: obj=%s, other=%s", obj.AbsID(), other.AbsID()) + } + + if obj.Box != nil && other.Box == nil { + return fmt.Errorf("other should have a box") + } else if obj.Box == nil && other.Box != nil { + return fmt.Errorf("other should not have a box") + } else if obj.Box != nil { + if obj.Width != other.Width { + return fmt.Errorf("widths differ: obj=%f, other=%f", obj.Width, other.Width) + } + + if obj.Height != other.Height { + return fmt.Errorf("heights differ: obj=%f, other=%f", obj.Height, other.Height) + } + } + + if obj.Parent != nil && other.Parent == nil { + return fmt.Errorf("other should have a parent") + } else if obj.Parent == nil && other.Parent != nil { + return fmt.Errorf("other should not have a parent") + } else if obj.Parent != nil && obj.Parent.ID != other.Parent.ID { + return fmt.Errorf("parent differs: obj=%s, other=%s", obj.Parent.ID, other.Parent.ID) + } + + if len(obj.Children) != len(other.Children) { + return fmt.Errorf("children count differs: obj=%d, other=%d", len(obj.Children), len(other.Children)) + } + + for childID, objChild := range obj.Children { + if otherChild, exists := other.Children[childID]; exists { + if err := objChild.Compare(otherChild); err != nil { + return fmt.Errorf("children differ at key %s: %w", childID, err) + } + } else { + return fmt.Errorf("child %s does not exist in other", childID) + } + } + + if len(obj.ChildrenArray) != len(other.ChildrenArray) { + return fmt.Errorf("childrenArray count differs: obj=%d, other=%d", len(obj.ChildrenArray), len(other.ChildrenArray)) + } + + for i := 0; i < len(obj.ChildrenArray); i++ { + if err := obj.ChildrenArray[i].Compare(other.ChildrenArray[i]); err != nil { + return fmt.Errorf("childrenArray differs at %d: %w", i, err) + } + } + + if obj.Attributes != nil && other.Attributes == nil { + return fmt.Errorf("other should have attributes") + } else if obj.Attributes == nil && other.Attributes != nil { + return fmt.Errorf("other should not have attributes") + } else if obj.Attributes != nil { + if d2target.IsShape(obj.Attributes.Shape.Value) != d2target.IsShape(other.Attributes.Shape.Value) { + return fmt.Errorf( + "shapes differ: obj=%s, other=%s", + obj.Attributes.Shape.Value, + other.Attributes.Shape.Value, + ) + } + + if obj.Attributes.Icon == nil && other.Attributes.Icon != nil { + return fmt.Errorf("other does not have an icon") + } else if obj.Attributes.Icon != nil && other.Attributes.Icon == nil { + return fmt.Errorf("obj does not have an icon") + } + + if obj.Attributes.Direction.Value != other.Attributes.Direction.Value { + return fmt.Errorf( + "directions differ: obj=%s, other=%s", + obj.Attributes.Direction.Value, + other.Attributes.Direction.Value, + ) + } + + if obj.Attributes.Label.Value != other.Attributes.Label.Value { + return fmt.Errorf( + "labels differ: obj=%s, other=%s", + obj.Attributes.Label.Value, + other.Attributes.Label.Value, + ) + } + + if obj.Attributes.NearKey != nil { + if other.Attributes.NearKey == nil { + return fmt.Errorf("other does not have near") + } + objKey := strings.Join(Key(obj.Attributes.NearKey), ".") + deserKey := strings.Join(Key(other.Attributes.NearKey), ".") + if objKey != deserKey { + return fmt.Errorf( + "near differs: obj=%s, other=%s", + objKey, + deserKey, + ) + } + } else if other.Attributes.NearKey != nil { + return fmt.Errorf("other should not have near") + } + } + + if obj.SQLTable == nil && other.SQLTable != nil { + return fmt.Errorf("other is not a sql table") + } else if obj.SQLTable != nil && other.SQLTable == nil { + return fmt.Errorf("obj is not a sql table") + } + + if obj.SQLTable != nil { + if len(obj.SQLTable.Columns) != len(other.SQLTable.Columns) { + return fmt.Errorf( + "table columns count differ: obj=%d, other=%d", + len(obj.SQLTable.Columns), + len(other.SQLTable.Columns), + ) + } + } + + if obj.LabelWidth != nil { + if other.LabelWidth == nil { + return fmt.Errorf("other does not have a label width") + } + if *obj.LabelWidth != *other.LabelWidth { + return fmt.Errorf( + "label widths differ: obj=%d, other=%d", + *obj.LabelWidth, + *other.LabelWidth, + ) + } + } else if other.LabelWidth != nil { + return fmt.Errorf("other should not have label width") + } + + if obj.LabelHeight != nil { + if other.LabelHeight == nil { + return fmt.Errorf("other does not have a label height") + } + if *obj.LabelHeight != *other.LabelHeight { + return fmt.Errorf( + "label heights differ: obj=%d, other=%d", + *obj.LabelHeight, + *other.LabelHeight, + ) + } + } else if other.LabelHeight != nil { + return fmt.Errorf("other should not have label height") + } + + return nil +} + +func (edge *Edge) Compare(other *Edge) error { + if edge.AbsID() != other.AbsID() { + return fmt.Errorf( + "absolute ids differ: edge=%s, other=%s", + edge.AbsID(), + other.AbsID(), + ) + } + + if edge.Src.AbsID() != other.Src.AbsID() { + return fmt.Errorf( + "sources differ: edge=%s, other=%s", + edge.Src.AbsID(), + other.Src.AbsID(), + ) + } + + if edge.Dst.AbsID() != other.Dst.AbsID() { + return fmt.Errorf( + "targets differ: edge=%s, other=%s", + edge.Dst.AbsID(), + other.Dst.AbsID(), + ) + } + + if edge.SrcArrow != other.SrcArrow { + return fmt.Errorf( + "source arrows differ: edge=%t, other=%t", + edge.SrcArrow, + other.SrcArrow, + ) + } + + if edge.DstArrow != other.DstArrow { + return fmt.Errorf( + "target arrows differ: edge=%t, other=%t", + edge.DstArrow, + other.DstArrow, + ) + } + + if edge.MinWidth != other.MinWidth { + return fmt.Errorf( + "min width differs: edge=%d, other=%d", + edge.MinWidth, + other.MinWidth, + ) + } + + if edge.MinHeight != other.MinHeight { + return fmt.Errorf( + "min height differs: edge=%d, other=%d", + edge.MinHeight, + other.MinHeight, + ) + } + + if edge.Attributes.Label.Value != other.Attributes.Label.Value { + return fmt.Errorf( + "labels differ: edge=%s, other=%s", + edge.Attributes.Label.Value, + other.Attributes.Label.Value, + ) + } + + if edge.LabelDimensions.Width != other.LabelDimensions.Width { + return fmt.Errorf( + "label width differs: edge=%d, other=%d", + edge.LabelDimensions.Width, + other.LabelDimensions.Width, + ) + } + + if edge.LabelDimensions.Height != other.LabelDimensions.Height { + return fmt.Errorf( + "label hieght differs: edge=%d, other=%d", + edge.LabelDimensions.Height, + other.LabelDimensions.Height, + ) + } + + if edge.SrcTableColumnIndex != nil && other.SrcTableColumnIndex == nil { + return fmt.Errorf("other should have src column index") + } else if other.SrcTableColumnIndex != nil && edge.SrcTableColumnIndex == nil { + return fmt.Errorf("other should not have src column index") + } else if other.SrcTableColumnIndex != nil { + edgeColumn := *edge.SrcTableColumnIndex + otherColumn := *other.SrcTableColumnIndex + if edgeColumn != otherColumn { + return fmt.Errorf("src column differs: edge=%d, other=%d", edgeColumn, otherColumn) + } + } + + if edge.DstTableColumnIndex != nil && other.DstTableColumnIndex == nil { + return fmt.Errorf("other should have dst column index") + } else if other.DstTableColumnIndex != nil && edge.DstTableColumnIndex == nil { + return fmt.Errorf("other should not have dst column index") + } else if other.DstTableColumnIndex != nil { + edgeColumn := *edge.DstTableColumnIndex + otherColumn := *other.DstTableColumnIndex + if edgeColumn != otherColumn { + return fmt.Errorf("dst column differs: edge=%d, other=%d", edgeColumn, otherColumn) + } + } + return nil +} From 51764057f150eea87328177acf725588c2dfed80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Sat, 18 Feb 2023 12:01:11 -0300 Subject: [PATCH 2/6] compare graphs on after serialization --- e2etests/e2e_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go index 5a5b7c57d..8150f5171 100644 --- a/e2etests/e2e_test.go +++ b/e2etests/e2e_test.go @@ -115,6 +115,7 @@ func serde(t *testing.T, tc testCase, ruler *textmeasure.Ruler) { var newG d2graph.Graph err = d2graph.DeserializeGraph(b, &newG) trequire.Nil(t, err) + trequire.Nil(t, g.Compare(&newG)) } func run(t *testing.T, tc testCase) { From 8105f02dc7c90c919d5618170c3ab8a32e479302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Sat, 18 Feb 2023 12:20:28 -0300 Subject: [PATCH 3/6] fix root serialization --- d2graph/serde.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/d2graph/serde.go b/d2graph/serde.go index 1e1778577..e56e23f32 100644 --- a/d2graph/serde.go +++ b/d2graph/serde.go @@ -24,10 +24,10 @@ func DeserializeGraph(bytes []byte, g *Graph) error { return err } - g.Root = &Object{ - Graph: g, - Children: make(map[string]*Object), - } + var root Object + convert(sg.Root, &root) + g.Root = &root + root.Graph = g idToObj := make(map[string]*Object) idToObj[""] = g.Root From 4b9fc8f7a1fd634df5b7d28918079238d9908e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Sat, 18 Feb 2023 12:20:47 -0300 Subject: [PATCH 4/6] fix near constant removal --- d2layouts/d2near/layout.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d2layouts/d2near/layout.go b/d2layouts/d2near/layout.go index 057ca405a..8a57e9a3f 100644 --- a/d2layouts/d2near/layout.go +++ b/d2layouts/d2near/layout.go @@ -98,7 +98,7 @@ func WithoutConstantNears(ctx context.Context, g *d2graph.Graph) (nears []*d2gra nears = append(nears, obj) g.Objects = append(g.Objects[:i], g.Objects[i+1:]...) i-- - delete(obj.Parent.Children, obj.ID) + delete(obj.Parent.Children, strings.ToLower(obj.ID)) for i := 0; i < len(obj.Parent.ChildrenArray); i++ { if obj.Parent.ChildrenArray[i] == obj { obj.Parent.ChildrenArray = append(obj.Parent.ChildrenArray[:i], obj.Parent.ChildrenArray[i+1:]...) From 92f774ba53db48863dfaac0bbbb94e98a633bb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Sat, 18 Feb 2023 12:20:58 -0300 Subject: [PATCH 5/6] fix error messages --- d2graph/d2graph.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 094ffd9fe..4c10bef59 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -1593,13 +1593,13 @@ func (g *Graph) Compare(other *Graph) error { } if err := g.Root.Compare(other.Root); err != nil { - return fmt.Errorf("root differs: %w", err) + return fmt.Errorf("root differs: %v", err) } for i := 0; i < len(g.Objects); i++ { if err := g.Objects[i].Compare(other.Objects[i]); err != nil { return fmt.Errorf( - "objects differ at %d [g=%s, other=%s]: %w", + "objects differ at %d [g=%s, other=%s]: %v", i, g.Objects[i].ID, other.Objects[i].ID, @@ -1611,7 +1611,7 @@ func (g *Graph) Compare(other *Graph) error { for i := 0; i < len(g.Edges); i++ { if err := g.Edges[i].Compare(other.Edges[i]); err != nil { return fmt.Errorf( - "edges differ at %d [g=%s, other=%s]: %w", + "edges differ at %d [g=%s, other=%s]: %v", i, g.Edges[i].AbsID(), other.Edges[i].AbsID(), @@ -1670,7 +1670,7 @@ func (obj *Object) Compare(other *Object) error { for childID, objChild := range obj.Children { if otherChild, exists := other.Children[childID]; exists { if err := objChild.Compare(otherChild); err != nil { - return fmt.Errorf("children differ at key %s: %w", childID, err) + return fmt.Errorf("children differ at key %s: %v", childID, err) } } else { return fmt.Errorf("child %s does not exist in other", childID) @@ -1683,7 +1683,7 @@ func (obj *Object) Compare(other *Object) error { for i := 0; i < len(obj.ChildrenArray); i++ { if err := obj.ChildrenArray[i].Compare(other.ChildrenArray[i]); err != nil { - return fmt.Errorf("childrenArray differs at %d: %w", i, err) + return fmt.Errorf("childrenArray differs at %d: %v", i, err) } } From 8d7dadd2ea9504a023cf117b5af61614651686e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Sat, 18 Feb 2023 13:56:17 -0300 Subject: [PATCH 6/6] move comparison to serde --- d2graph/d2graph.go | 313 ------------------------------------------ d2graph/serde.go | 315 +++++++++++++++++++++++++++++++++++++++++++ e2etests/e2e_test.go | 2 +- 3 files changed, 316 insertions(+), 314 deletions(-) diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 4c10bef59..1300289d6 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -1582,316 +1582,3 @@ func (g *Graph) SortEdgesByAST() { }) g.Edges = edges } - -func (g *Graph) Compare(other *Graph) error { - if len(g.Objects) != len(other.Objects) { - return fmt.Errorf("object count differs: g=%d, other=%d", len(g.Objects), len(other.Objects)) - } - - if len(g.Edges) != len(other.Edges) { - return fmt.Errorf("edge count differs: g=%d, other=%d", len(g.Edges), len(other.Edges)) - } - - if err := g.Root.Compare(other.Root); err != nil { - return fmt.Errorf("root differs: %v", err) - } - - for i := 0; i < len(g.Objects); i++ { - if err := g.Objects[i].Compare(other.Objects[i]); err != nil { - return fmt.Errorf( - "objects differ at %d [g=%s, other=%s]: %v", - i, - g.Objects[i].ID, - other.Objects[i].ID, - err, - ) - } - } - - for i := 0; i < len(g.Edges); i++ { - if err := g.Edges[i].Compare(other.Edges[i]); err != nil { - return fmt.Errorf( - "edges differ at %d [g=%s, other=%s]: %v", - i, - g.Edges[i].AbsID(), - other.Edges[i].AbsID(), - err, - ) - } - } - - return nil -} - -func (obj *Object) Compare(other *Object) error { - if obj != nil && other == nil { - return fmt.Errorf("other is nil") - } else if obj == nil && other != nil { - return fmt.Errorf("obj is nil") - } else if obj == nil { - // both are nil - return nil - } - - if obj.ID != other.ID { - return fmt.Errorf("ids differ: obj=%s, other=%s", obj.ID, other.ID) - } - - if obj.AbsID() != other.AbsID() { - return fmt.Errorf("absolute ids differ: obj=%s, other=%s", obj.AbsID(), other.AbsID()) - } - - if obj.Box != nil && other.Box == nil { - return fmt.Errorf("other should have a box") - } else if obj.Box == nil && other.Box != nil { - return fmt.Errorf("other should not have a box") - } else if obj.Box != nil { - if obj.Width != other.Width { - return fmt.Errorf("widths differ: obj=%f, other=%f", obj.Width, other.Width) - } - - if obj.Height != other.Height { - return fmt.Errorf("heights differ: obj=%f, other=%f", obj.Height, other.Height) - } - } - - if obj.Parent != nil && other.Parent == nil { - return fmt.Errorf("other should have a parent") - } else if obj.Parent == nil && other.Parent != nil { - return fmt.Errorf("other should not have a parent") - } else if obj.Parent != nil && obj.Parent.ID != other.Parent.ID { - return fmt.Errorf("parent differs: obj=%s, other=%s", obj.Parent.ID, other.Parent.ID) - } - - if len(obj.Children) != len(other.Children) { - return fmt.Errorf("children count differs: obj=%d, other=%d", len(obj.Children), len(other.Children)) - } - - for childID, objChild := range obj.Children { - if otherChild, exists := other.Children[childID]; exists { - if err := objChild.Compare(otherChild); err != nil { - return fmt.Errorf("children differ at key %s: %v", childID, err) - } - } else { - return fmt.Errorf("child %s does not exist in other", childID) - } - } - - if len(obj.ChildrenArray) != len(other.ChildrenArray) { - return fmt.Errorf("childrenArray count differs: obj=%d, other=%d", len(obj.ChildrenArray), len(other.ChildrenArray)) - } - - for i := 0; i < len(obj.ChildrenArray); i++ { - if err := obj.ChildrenArray[i].Compare(other.ChildrenArray[i]); err != nil { - return fmt.Errorf("childrenArray differs at %d: %v", i, err) - } - } - - if obj.Attributes != nil && other.Attributes == nil { - return fmt.Errorf("other should have attributes") - } else if obj.Attributes == nil && other.Attributes != nil { - return fmt.Errorf("other should not have attributes") - } else if obj.Attributes != nil { - if d2target.IsShape(obj.Attributes.Shape.Value) != d2target.IsShape(other.Attributes.Shape.Value) { - return fmt.Errorf( - "shapes differ: obj=%s, other=%s", - obj.Attributes.Shape.Value, - other.Attributes.Shape.Value, - ) - } - - if obj.Attributes.Icon == nil && other.Attributes.Icon != nil { - return fmt.Errorf("other does not have an icon") - } else if obj.Attributes.Icon != nil && other.Attributes.Icon == nil { - return fmt.Errorf("obj does not have an icon") - } - - if obj.Attributes.Direction.Value != other.Attributes.Direction.Value { - return fmt.Errorf( - "directions differ: obj=%s, other=%s", - obj.Attributes.Direction.Value, - other.Attributes.Direction.Value, - ) - } - - if obj.Attributes.Label.Value != other.Attributes.Label.Value { - return fmt.Errorf( - "labels differ: obj=%s, other=%s", - obj.Attributes.Label.Value, - other.Attributes.Label.Value, - ) - } - - if obj.Attributes.NearKey != nil { - if other.Attributes.NearKey == nil { - return fmt.Errorf("other does not have near") - } - objKey := strings.Join(Key(obj.Attributes.NearKey), ".") - deserKey := strings.Join(Key(other.Attributes.NearKey), ".") - if objKey != deserKey { - return fmt.Errorf( - "near differs: obj=%s, other=%s", - objKey, - deserKey, - ) - } - } else if other.Attributes.NearKey != nil { - return fmt.Errorf("other should not have near") - } - } - - if obj.SQLTable == nil && other.SQLTable != nil { - return fmt.Errorf("other is not a sql table") - } else if obj.SQLTable != nil && other.SQLTable == nil { - return fmt.Errorf("obj is not a sql table") - } - - if obj.SQLTable != nil { - if len(obj.SQLTable.Columns) != len(other.SQLTable.Columns) { - return fmt.Errorf( - "table columns count differ: obj=%d, other=%d", - len(obj.SQLTable.Columns), - len(other.SQLTable.Columns), - ) - } - } - - if obj.LabelWidth != nil { - if other.LabelWidth == nil { - return fmt.Errorf("other does not have a label width") - } - if *obj.LabelWidth != *other.LabelWidth { - return fmt.Errorf( - "label widths differ: obj=%d, other=%d", - *obj.LabelWidth, - *other.LabelWidth, - ) - } - } else if other.LabelWidth != nil { - return fmt.Errorf("other should not have label width") - } - - if obj.LabelHeight != nil { - if other.LabelHeight == nil { - return fmt.Errorf("other does not have a label height") - } - if *obj.LabelHeight != *other.LabelHeight { - return fmt.Errorf( - "label heights differ: obj=%d, other=%d", - *obj.LabelHeight, - *other.LabelHeight, - ) - } - } else if other.LabelHeight != nil { - return fmt.Errorf("other should not have label height") - } - - return nil -} - -func (edge *Edge) Compare(other *Edge) error { - if edge.AbsID() != other.AbsID() { - return fmt.Errorf( - "absolute ids differ: edge=%s, other=%s", - edge.AbsID(), - other.AbsID(), - ) - } - - if edge.Src.AbsID() != other.Src.AbsID() { - return fmt.Errorf( - "sources differ: edge=%s, other=%s", - edge.Src.AbsID(), - other.Src.AbsID(), - ) - } - - if edge.Dst.AbsID() != other.Dst.AbsID() { - return fmt.Errorf( - "targets differ: edge=%s, other=%s", - edge.Dst.AbsID(), - other.Dst.AbsID(), - ) - } - - if edge.SrcArrow != other.SrcArrow { - return fmt.Errorf( - "source arrows differ: edge=%t, other=%t", - edge.SrcArrow, - other.SrcArrow, - ) - } - - if edge.DstArrow != other.DstArrow { - return fmt.Errorf( - "target arrows differ: edge=%t, other=%t", - edge.DstArrow, - other.DstArrow, - ) - } - - if edge.MinWidth != other.MinWidth { - return fmt.Errorf( - "min width differs: edge=%d, other=%d", - edge.MinWidth, - other.MinWidth, - ) - } - - if edge.MinHeight != other.MinHeight { - return fmt.Errorf( - "min height differs: edge=%d, other=%d", - edge.MinHeight, - other.MinHeight, - ) - } - - if edge.Attributes.Label.Value != other.Attributes.Label.Value { - return fmt.Errorf( - "labels differ: edge=%s, other=%s", - edge.Attributes.Label.Value, - other.Attributes.Label.Value, - ) - } - - if edge.LabelDimensions.Width != other.LabelDimensions.Width { - return fmt.Errorf( - "label width differs: edge=%d, other=%d", - edge.LabelDimensions.Width, - other.LabelDimensions.Width, - ) - } - - if edge.LabelDimensions.Height != other.LabelDimensions.Height { - return fmt.Errorf( - "label hieght differs: edge=%d, other=%d", - edge.LabelDimensions.Height, - other.LabelDimensions.Height, - ) - } - - if edge.SrcTableColumnIndex != nil && other.SrcTableColumnIndex == nil { - return fmt.Errorf("other should have src column index") - } else if other.SrcTableColumnIndex != nil && edge.SrcTableColumnIndex == nil { - return fmt.Errorf("other should not have src column index") - } else if other.SrcTableColumnIndex != nil { - edgeColumn := *edge.SrcTableColumnIndex - otherColumn := *other.SrcTableColumnIndex - if edgeColumn != otherColumn { - return fmt.Errorf("src column differs: edge=%d, other=%d", edgeColumn, otherColumn) - } - } - - if edge.DstTableColumnIndex != nil && other.DstTableColumnIndex == nil { - return fmt.Errorf("other should have dst column index") - } else if other.DstTableColumnIndex != nil && edge.DstTableColumnIndex == nil { - return fmt.Errorf("other should not have dst column index") - } else if other.DstTableColumnIndex != nil { - edgeColumn := *edge.DstTableColumnIndex - otherColumn := *other.DstTableColumnIndex - if edgeColumn != otherColumn { - return fmt.Errorf("dst column differs: edge=%d, other=%d", edgeColumn, otherColumn) - } - } - return nil -} diff --git a/d2graph/serde.go b/d2graph/serde.go index e56e23f32..0b7c49ca1 100644 --- a/d2graph/serde.go +++ b/d2graph/serde.go @@ -2,8 +2,10 @@ package d2graph import ( "encoding/json" + "fmt" "strings" + "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/util-go/go2" ) @@ -158,3 +160,316 @@ func convert[T, Q any](from T, to *Q) error { } return nil } + +func CompareSerializedGraph(g, other *Graph) error { + if len(g.Objects) != len(other.Objects) { + return fmt.Errorf("object count differs: g=%d, other=%d", len(g.Objects), len(other.Objects)) + } + + if len(g.Edges) != len(other.Edges) { + return fmt.Errorf("edge count differs: g=%d, other=%d", len(g.Edges), len(other.Edges)) + } + + if err := CompareSerializedObject(g.Root, other.Root); err != nil { + return fmt.Errorf("root differs: %v", err) + } + + for i := 0; i < len(g.Objects); i++ { + if err := CompareSerializedObject(g.Objects[i], other.Objects[i]); err != nil { + return fmt.Errorf( + "objects differ at %d [g=%s, other=%s]: %v", + i, + g.Objects[i].ID, + other.Objects[i].ID, + err, + ) + } + } + + for i := 0; i < len(g.Edges); i++ { + if err := CompareSerializedEdge(g.Edges[i], other.Edges[i]); err != nil { + return fmt.Errorf( + "edges differ at %d [g=%s, other=%s]: %v", + i, + g.Edges[i].AbsID(), + other.Edges[i].AbsID(), + err, + ) + } + } + + return nil +} + +func CompareSerializedObject(obj, other *Object) error { + if obj != nil && other == nil { + return fmt.Errorf("other is nil") + } else if obj == nil && other != nil { + return fmt.Errorf("obj is nil") + } else if obj == nil { + // both are nil + return nil + } + + if obj.ID != other.ID { + return fmt.Errorf("ids differ: obj=%s, other=%s", obj.ID, other.ID) + } + + if obj.AbsID() != other.AbsID() { + return fmt.Errorf("absolute ids differ: obj=%s, other=%s", obj.AbsID(), other.AbsID()) + } + + if obj.Box != nil && other.Box == nil { + return fmt.Errorf("other should have a box") + } else if obj.Box == nil && other.Box != nil { + return fmt.Errorf("other should not have a box") + } else if obj.Box != nil { + if obj.Width != other.Width { + return fmt.Errorf("widths differ: obj=%f, other=%f", obj.Width, other.Width) + } + + if obj.Height != other.Height { + return fmt.Errorf("heights differ: obj=%f, other=%f", obj.Height, other.Height) + } + } + + if obj.Parent != nil && other.Parent == nil { + return fmt.Errorf("other should have a parent") + } else if obj.Parent == nil && other.Parent != nil { + return fmt.Errorf("other should not have a parent") + } else if obj.Parent != nil && obj.Parent.ID != other.Parent.ID { + return fmt.Errorf("parent differs: obj=%s, other=%s", obj.Parent.ID, other.Parent.ID) + } + + if len(obj.Children) != len(other.Children) { + return fmt.Errorf("children count differs: obj=%d, other=%d", len(obj.Children), len(other.Children)) + } + + for childID, objChild := range obj.Children { + if otherChild, exists := other.Children[childID]; exists { + if err := CompareSerializedObject(objChild, otherChild); err != nil { + return fmt.Errorf("children differ at key %s: %v", childID, err) + } + } else { + return fmt.Errorf("child %s does not exist in other", childID) + } + } + + if len(obj.ChildrenArray) != len(other.ChildrenArray) { + return fmt.Errorf("childrenArray count differs: obj=%d, other=%d", len(obj.ChildrenArray), len(other.ChildrenArray)) + } + + for i := 0; i < len(obj.ChildrenArray); i++ { + if err := CompareSerializedObject(obj.ChildrenArray[i], other.ChildrenArray[i]); err != nil { + return fmt.Errorf("childrenArray differs at %d: %v", i, err) + } + } + + if obj.Attributes != nil && other.Attributes == nil { + return fmt.Errorf("other should have attributes") + } else if obj.Attributes == nil && other.Attributes != nil { + return fmt.Errorf("other should not have attributes") + } else if obj.Attributes != nil { + if d2target.IsShape(obj.Attributes.Shape.Value) != d2target.IsShape(other.Attributes.Shape.Value) { + return fmt.Errorf( + "shapes differ: obj=%s, other=%s", + obj.Attributes.Shape.Value, + other.Attributes.Shape.Value, + ) + } + + if obj.Attributes.Icon == nil && other.Attributes.Icon != nil { + return fmt.Errorf("other does not have an icon") + } else if obj.Attributes.Icon != nil && other.Attributes.Icon == nil { + return fmt.Errorf("obj does not have an icon") + } + + if obj.Attributes.Direction.Value != other.Attributes.Direction.Value { + return fmt.Errorf( + "directions differ: obj=%s, other=%s", + obj.Attributes.Direction.Value, + other.Attributes.Direction.Value, + ) + } + + if obj.Attributes.Label.Value != other.Attributes.Label.Value { + return fmt.Errorf( + "labels differ: obj=%s, other=%s", + obj.Attributes.Label.Value, + other.Attributes.Label.Value, + ) + } + + if obj.Attributes.NearKey != nil { + if other.Attributes.NearKey == nil { + return fmt.Errorf("other does not have near") + } + objKey := strings.Join(Key(obj.Attributes.NearKey), ".") + deserKey := strings.Join(Key(other.Attributes.NearKey), ".") + if objKey != deserKey { + return fmt.Errorf( + "near differs: obj=%s, other=%s", + objKey, + deserKey, + ) + } + } else if other.Attributes.NearKey != nil { + return fmt.Errorf("other should not have near") + } + } + + if obj.SQLTable == nil && other.SQLTable != nil { + return fmt.Errorf("other is not a sql table") + } else if obj.SQLTable != nil && other.SQLTable == nil { + return fmt.Errorf("obj is not a sql table") + } + + if obj.SQLTable != nil { + if len(obj.SQLTable.Columns) != len(other.SQLTable.Columns) { + return fmt.Errorf( + "table columns count differ: obj=%d, other=%d", + len(obj.SQLTable.Columns), + len(other.SQLTable.Columns), + ) + } + } + + if obj.LabelWidth != nil { + if other.LabelWidth == nil { + return fmt.Errorf("other does not have a label width") + } + if *obj.LabelWidth != *other.LabelWidth { + return fmt.Errorf( + "label widths differ: obj=%d, other=%d", + *obj.LabelWidth, + *other.LabelWidth, + ) + } + } else if other.LabelWidth != nil { + return fmt.Errorf("other should not have label width") + } + + if obj.LabelHeight != nil { + if other.LabelHeight == nil { + return fmt.Errorf("other does not have a label height") + } + if *obj.LabelHeight != *other.LabelHeight { + return fmt.Errorf( + "label heights differ: obj=%d, other=%d", + *obj.LabelHeight, + *other.LabelHeight, + ) + } + } else if other.LabelHeight != nil { + return fmt.Errorf("other should not have label height") + } + + return nil +} + +func CompareSerializedEdge(edge, other *Edge) error { + if edge.AbsID() != other.AbsID() { + return fmt.Errorf( + "absolute ids differ: edge=%s, other=%s", + edge.AbsID(), + other.AbsID(), + ) + } + + if edge.Src.AbsID() != other.Src.AbsID() { + return fmt.Errorf( + "sources differ: edge=%s, other=%s", + edge.Src.AbsID(), + other.Src.AbsID(), + ) + } + + if edge.Dst.AbsID() != other.Dst.AbsID() { + return fmt.Errorf( + "targets differ: edge=%s, other=%s", + edge.Dst.AbsID(), + other.Dst.AbsID(), + ) + } + + if edge.SrcArrow != other.SrcArrow { + return fmt.Errorf( + "source arrows differ: edge=%t, other=%t", + edge.SrcArrow, + other.SrcArrow, + ) + } + + if edge.DstArrow != other.DstArrow { + return fmt.Errorf( + "target arrows differ: edge=%t, other=%t", + edge.DstArrow, + other.DstArrow, + ) + } + + if edge.MinWidth != other.MinWidth { + return fmt.Errorf( + "min width differs: edge=%d, other=%d", + edge.MinWidth, + other.MinWidth, + ) + } + + if edge.MinHeight != other.MinHeight { + return fmt.Errorf( + "min height differs: edge=%d, other=%d", + edge.MinHeight, + other.MinHeight, + ) + } + + if edge.Attributes.Label.Value != other.Attributes.Label.Value { + return fmt.Errorf( + "labels differ: edge=%s, other=%s", + edge.Attributes.Label.Value, + other.Attributes.Label.Value, + ) + } + + if edge.LabelDimensions.Width != other.LabelDimensions.Width { + return fmt.Errorf( + "label width differs: edge=%d, other=%d", + edge.LabelDimensions.Width, + other.LabelDimensions.Width, + ) + } + + if edge.LabelDimensions.Height != other.LabelDimensions.Height { + return fmt.Errorf( + "label hieght differs: edge=%d, other=%d", + edge.LabelDimensions.Height, + other.LabelDimensions.Height, + ) + } + + if edge.SrcTableColumnIndex != nil && other.SrcTableColumnIndex == nil { + return fmt.Errorf("other should have src column index") + } else if other.SrcTableColumnIndex != nil && edge.SrcTableColumnIndex == nil { + return fmt.Errorf("other should not have src column index") + } else if other.SrcTableColumnIndex != nil { + edgeColumn := *edge.SrcTableColumnIndex + otherColumn := *other.SrcTableColumnIndex + if edgeColumn != otherColumn { + return fmt.Errorf("src column differs: edge=%d, other=%d", edgeColumn, otherColumn) + } + } + + if edge.DstTableColumnIndex != nil && other.DstTableColumnIndex == nil { + return fmt.Errorf("other should have dst column index") + } else if other.DstTableColumnIndex != nil && edge.DstTableColumnIndex == nil { + return fmt.Errorf("other should not have dst column index") + } else if other.DstTableColumnIndex != nil { + edgeColumn := *edge.DstTableColumnIndex + otherColumn := *other.DstTableColumnIndex + if edgeColumn != otherColumn { + return fmt.Errorf("dst column differs: edge=%d, other=%d", edgeColumn, otherColumn) + } + } + return nil +} diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go index 8150f5171..ab4059cd4 100644 --- a/e2etests/e2e_test.go +++ b/e2etests/e2e_test.go @@ -115,7 +115,7 @@ func serde(t *testing.T, tc testCase, ruler *textmeasure.Ruler) { var newG d2graph.Graph err = d2graph.DeserializeGraph(b, &newG) trequire.Nil(t, err) - trequire.Nil(t, g.Compare(&newG)) + trequire.Nil(t, d2graph.CompareSerializedGraph(g, &newG)) } func run(t *testing.T, tc testCase) {