///|
/// Encode a graph as stable, human-readable JSON using MoonBit's standard
/// JSON representation. The result can be stored or exchanged with tools.
pub fn graph_to_json(graph : Graph) -> String {
ToJson::to_json(graph).stringify(indent=2)
}
///|
/// Decode a graph from JSON. Parse and schema errors are propagated to callers.
pub fn graph_from_json(source : String) -> Graph raise {
@json.from_json(@json.parse(source))
}
///|
/// Return all validation findings instead of only a boolean result.
pub fn validate_graph(graph : Graph) -> Array[String] {
let findings : Array[String] = []
if graph.nodes.length() == 0 {
findings.push("graph must contain at least one node")
}
let mut node_index = 0
while node_index < graph.nodes.length() {
let node = graph.nodes[node_index]
if node.id != node_index {
findings.push("node ids must be contiguous and match array indexes")
}
if node.label.length() == 0 {
findings.push("node labels must not be empty")
}
node_index = node_index + 1
}
let mut edge_index = 0
while edge_index < graph.edges.length() {
let edge = graph.edges[edge_index]
if edge.from < 0 || edge.from >= graph.nodes.length() {
findings.push("edge source is outside the node array")
}
if edge.to < 0 || edge.to >= graph.nodes.length() {
findings.push("edge destination is outside the node array")
}
if edge.meters <= 0 {
findings.push("edge distance must be positive")
}
if edge.width_cm <= 0 {
findings.push("edge width must be positive")
}
if edge.lighting < 0 || edge.lighting > 5 {
findings.push("edge lighting must be between 0 and 5")
}
if edge.slope_percent < 0 {
findings.push("edge slope must not be negative")
}
if edge.steps < 0 || edge.crossing < 0 {
findings.push("edge steps and crossing counts must not be negative")
}
edge_index = edge_index + 1
}
findings
}
///|
pub fn graph_json_is_valid(source : String) -> Bool {
try {
let graph = graph_from_json(source)
validate_graph(graph).length() == 0
} catch {
_ => false
}
}