// Package layoutguard protects layout engines that cannot be interrupted once // their native layout call begins.
package layoutguard import ( ) const ( // Dagre and ELK do not accept a context during their native layout calls. // Keep their raw inputs at or below the compiler's calibrated wildcard // boundary so direct graph callers cannot bypass compiler admission. maxLayoutObjects = 1_024 maxLayoutEdges = 1_024 // Small graphs are safe to pass through without topology accounting and may // intentionally contain many parallel or self-referential edges. unconditionallyAllowedEdges = 256 // Edge interaction work is the sum, for every edge, of the smaller incident // degree of its endpoints. It remains low for stars, trees, and other sparse // diagrams, but grows quickly for dense subgraphs and cannot be diluted by // adding unrelated objects. maxEdgeInteractionWork int64 = 8_192 ) // CheckGraph rejects malformed, oversized, or dense graphs before passing // control to a layout engine that cannot observe context cancellation during // its native layout call. func ( context.Context, string, *d2graph.Graph) error { if == nil { = context.Background() } if := .Err(); != nil { return } if == nil { return fmt.Errorf("%s layout requires a graph", ) } if len(.Objects) > maxLayoutObjects { return fmt.Errorf("%s layout object count %d exceeds safe limit of %d", , len(.Objects), maxLayoutObjects) } if len(.Edges) > maxLayoutEdges { return fmt.Errorf("%s layout edge count %d exceeds safe limit of %d", , len(.Edges), maxLayoutEdges) } := make(map[*d2graph.Object]int, len(.Objects)) for , := range .Edges { if %64 == 0 { if := .Err(); != nil { return } } if == nil || .Src == nil || .Dst == nil { return fmt.Errorf("%s layout graph contains an edge without endpoints", ) } [.Src]++ [.Dst]++ } if len(.Edges) <= unconditionallyAllowedEdges { return nil } var int64 for , := range .Edges { if %64 == 0 { if := .Err(); != nil { return } } := [.Src] if [.Dst] < { = [.Dst] } if int64() > maxEdgeInteractionWork- { return fmt.Errorf( "%s layout graph is too interconnected: edge interaction work exceeds safe limit of %d", , maxEdgeInteractionWork, ) } += int64() } return nil }