///|
pub(all) enum TopologyIssueKind {
OrphanNode
MissingTrackEndpoint
InvalidTrackGeometry
MissingTrackEdge
InvalidTurnoutReference
InvalidSignalBinding
} derive(Eq, Debug)
///|
pub fn TopologyIssueKind::to_string(self : TopologyIssueKind) -> String {
match self {
OrphanNode => "ORPHAN_NODE"
MissingTrackEndpoint => "MISSING_TRACK_ENDPOINT"
InvalidTrackGeometry => "INVALID_TRACK_GEOMETRY"
MissingTrackEdge => "MISSING_TRACK_EDGE"
InvalidTurnoutReference => "INVALID_TURNOUT_REFERENCE"
InvalidSignalBinding => "INVALID_SIGNAL_BINDING"
}
}
///|
pub(all) struct TopologyIssue {
kind : TopologyIssueKind
resource : String
detail : String
} derive(Debug)
///|
pub fn TopologyIssue::new(
kind : TopologyIssueKind,
resource : String,
detail : String,
) -> TopologyIssue {
{ kind, resource, detail }
}
///|
pub fn TopologyIssue::to_string(self : TopologyIssue) -> String {
self.kind.to_string() + ": " + self.resource + " - " + self.detail
}
///|
pub(all) struct TopologyValidationReport {
issues : Array[TopologyIssue]
mut error_count_value : Int
} derive(Debug)
///|
pub fn TopologyValidationReport::new() -> TopologyValidationReport {
{ issues: [], error_count_value: 0 }
}
///|
fn TopologyValidationReport::add_issue(
self : TopologyValidationReport,
issue : TopologyIssue,
) -> Unit {
self.issues.push(issue)
self.error_count_value = self.error_count_value + 1
}
///|
pub fn TopologyValidationReport::is_valid(
self : TopologyValidationReport,
) -> Bool {
self.error_count_value == 0
}
///|
pub fn TopologyValidationReport::error_count(
self : TopologyValidationReport,
) -> Int {
self.error_count_value
}
///|
pub fn TopologyValidationReport::issues(
self : TopologyValidationReport,
) -> Array[TopologyIssue] {
self.issues
}
///|
pub fn Station::validate_operational_topology(
self : Station,
) -> TopologyValidationReport {
let report = TopologyValidationReport::new()
for _, node in self.graph.nodes {
let has_incoming = match self.graph.reverse_adjacency.get(node.id.id) {
Some(edges) => edges.length() > 0
None => false
}
let has_outgoing = match self.graph.adjacency.get(node.id.id) {
Some(edges) => edges.length() > 0
None => false
}
if !has_incoming && !has_outgoing && node.node_type != SignalNode {
report.add_issue(
TopologyIssue::new(
OrphanNode,
node.name,
"node has no incoming or outgoing track edge",
),
)
}
}
for _, track in self.graph.tracks {
if track.length_meters <= 0.0 || track.speed_limit_kmh <= 0.0 {
report.add_issue(
TopologyIssue::new(
InvalidTrackGeometry,
track.name,
"length and speed limit must both be positive",
),
)
}
if !self.graph.nodes.contains(track.start_node.id) ||
!self.graph.nodes.contains(track.end_node.id) {
report.add_issue(
TopologyIssue::new(
MissingTrackEndpoint,
track.name,
"track references a node that is not in the station graph",
),
)
}
let mut edge_count = 0
for _, edge in self.graph.edges {
if edge.track_id.id == track.id.id {
edge_count = edge_count + 1
}
}
if edge_count == 0 {
report.add_issue(
TopologyIssue::new(
MissingTrackEdge,
track.name,
"track has no graph edge",
),
)
}
}
for _, turnout in self.graph.turnouts {
let refs_exist = self.graph.nodes.contains(turnout.base_node.id) &&
self.graph.nodes.contains(turnout.normal_next_node.id) &&
self.graph.nodes.contains(turnout.reverse_next_node.id)
if !refs_exist {
report.add_issue(
TopologyIssue::new(
InvalidTurnoutReference,
turnout.name,
"turnout references a node that is not in the station graph",
),
)
}
}
for _, signal in self.graph.signals {
if !self.graph.nodes.contains(signal.bound_node.id) {
report.add_issue(
TopologyIssue::new(
InvalidSignalBinding,
signal.name,
"signal references a node that is not in the station graph",
),
)
}
}
report
}