fix(#2): harden network data parsing and link resolution

This commit is contained in:
Josh Creek
2026-02-22 14:03:01 +00:00
parent c9e7882e27
commit d484a15c89
3 changed files with 37 additions and 12 deletions
+9 -1
View File
@@ -20,7 +20,15 @@ export default async function loadNetworkData(jsonPath: string): Promise<Network
throw new Error(`Failed to load ${jsonPath} (${response.status} ${response.statusText})`);
}
const rawData: unknown = await response.json();
let rawData: unknown;
try {
rawData = await response.json();
} catch (error) {
throw new Error(`Failed to parse JSON at ${jsonPath}`, {
cause: error
});
}
if (!isNetworkData(rawData)) {
throw new Error(`JSON at ${jsonPath} is not valid network data`);
}
+23 -6
View File
@@ -2,11 +2,16 @@ import type { ConnectedPortRef, NetworkData, Port } from '../types';
import type { GraphEdgeElement, GraphNodeElement, GraphTransformResult } from './types';
interface EndpointOwner {
kind: 'machine' | 'device';
nodeId: string;
name: string;
ports: Map<string, Port>;
}
function toOwnerKey(kind: EndpointOwner['kind'], name: string): string {
return `${kind}:${name}`;
}
function toMachineNodeId(name: string): string {
return `machine:${encodeURIComponent(name)}`;
}
@@ -58,7 +63,7 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe
vmCount: number;
}
> = {};
const ownersByName = new Map<string, EndpointOwner>();
const ownersByKey = new Map<string, EndpointOwner>();
const seenWarnings = new Set<string>();
const warn = (message: string) => {
@@ -69,13 +74,14 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe
};
const addOwner = (owner: EndpointOwner) => {
if (ownersByName.has(owner.name)) {
const ownerKey = toOwnerKey(owner.kind, owner.name);
if (ownersByKey.has(ownerKey)) {
warn(
`Duplicate node name "${owner.name}" found. First occurrence will be used for link resolution.`
`Duplicate ${owner.kind} name "${owner.name}" found. First occurrence will be used for link resolution.`
);
return;
}
ownersByName.set(owner.name, owner);
ownersByKey.set(ownerKey, owner);
};
let hostingEdgeCounter = 0;
@@ -163,6 +169,7 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe
}
addOwner({
kind: 'machine',
nodeId: machineNodeId,
name: machine.machineName,
ports: machinePorts
@@ -198,6 +205,7 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe
}
addOwner({
kind: 'device',
nodeId: deviceNodeId,
name: device.name,
ports: devicePortMap
@@ -207,12 +215,21 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe
const seenPhysicalConnections = new Set<string>();
let physicalEdgeCounter = 0;
for (const owner of ownersByName.values()) {
for (const owner of ownersByKey.values()) {
for (const port of owner.ports.values()) {
const connectedTo = parseConnectedTo(port.connectedTo);
if (!connectedTo) continue;
const targetOwner = ownersByName.get(connectedTo.device);
const machineTargetOwner = ownersByKey.get(toOwnerKey('machine', connectedTo.device));
const deviceTargetOwner = ownersByKey.get(toOwnerKey('device', connectedTo.device));
if (machineTargetOwner && deviceTargetOwner) {
warn(
`Link from "${owner.name}:${port.portName}" references ambiguous device "${connectedTo.device}" (matches both machine and device names).`
);
continue;
}
const targetOwner = machineTargetOwner ?? deviceTargetOwner;
if (!targetOwner) {
warn(
`Link from "${owner.name}:${port.portName}" references unknown device "${connectedTo.device}".`
+5 -5
View File
@@ -1,14 +1,14 @@
export interface ConnectedPortRef {
device: string;
port: string;
}
export interface Port {
portName: string;
speedGbps?: number; // default to 1 if not provided
connectedTo?: ConnectedPortRef;
}
export interface ConnectedPortRef {
device: string;
port: string;
}
export interface Hardware {
cpu: string;
ram: string;