| 472 | } |
| 473 | |
| 474 | func readGraphDataFile(path string) (GraphData, error) { |
| 475 | clean := filepath.Clean(path) |
| 476 | b, err := os.ReadFile(clean) //#nosec G304 -- user/agent-specified graph data file, read-only |
| 477 | if err != nil { |
| 478 | return GraphData{}, fmt.Errorf("reading file %q: %w", path, err) |
| 479 | } |
| 480 | var in struct { |
| 481 | Nodes []gvNodeInput `json:"nodes"` |
| 482 | Edges []gvEdgeInput `json:"edges"` |
| 483 | } |
| 484 | if err := json.Unmarshal(b, &in); err != nil { |
| 485 | return GraphData{}, fmt.Errorf("parsing %q: %w", path, err) |
| 486 | } |
| 487 | return GraphData{Nodes: normalizeNodeInputs(in.Nodes), Edges: normalizeEdgeInputs(in.Edges)}, nil |
| 488 | } |
| 489 | |
| 490 | // sanitizeGraphData dedupes nodes by ID and drops edges whose endpoints are |
| 491 | // missing, so the rendered graph is always well-formed. |