///|
fn integrity_diagnostic(
message : String,
source_index? : Int = 0,
) -> Diagnostic {
Diagnostic::new(
IntegrityInvalid,
Error,
message,
SourceRef::sentence(source_index),
)
}
///|
fn string_entry_total(entries : Array[StringCountEntry]) -> Int {
let mut total = 0
for entry in entries {
total += entry.count_value
}
total
}
///|
fn checksum_entry_total(entries : Array[ChecksumCountEntry]) -> Int {
let mut total = 0
for entry in entries {
total += entry.count_value
}
total
}
///|
pub fn validate_batch_integrity(batch : NmeaBatch) -> Array[Diagnostic] {
let findings : Array[Diagnostic] = []
let summary = batch.summary_value
if summary.total_value != batch.items_value.length() {
findings.push(integrity_diagnostic("batch total does not match item count"))
}
let mut successes = 0
let mut failures = 0
for index = 0; index < batch.items_value.length(); index = index + 1 {
let item = batch.items_value[index]
if item.record_index_value != index {
findings.push(
integrity_diagnostic(
"batch item index is not contiguous",
source_index=index,
),
)
}
match (item.value_value, item.error_value) {
(Some(_), None) => successes += 1
(None, Some(_)) => failures += 1
_ =>
findings.push(
integrity_diagnostic(
"batch item must contain exactly one value or error",
source_index=index,
),
)
}
}
if summary.successes_value != successes || summary.failures_value != failures {
findings.push(
integrity_diagnostic("batch outcome totals do not match items"),
)
}
if summary.successes_value + summary.failures_value != summary.total_value {
findings.push(
integrity_diagnostic("batch success and failure totals do not reconcile"),
)
}
if string_entry_total(summary.formatter_entries_value) != successes ||
string_entry_total(summary.talker_entries_value) != successes {
findings.push(
integrity_diagnostic("batch successful record counts do not reconcile"),
)
}
if string_entry_total(summary.error_entries_value) != failures {
findings.push(integrity_diagnostic("batch error counts do not reconcile"))
}
if checksum_entry_total(summary.checksum_entries_value) != summary.total_value {
findings.push(
integrity_diagnostic("batch checksum counts do not reconcile"),
)
}
findings
}
///|
pub fn validate_gsv_integrity(assembly : GsvAssembly) -> Array[Diagnostic] {
let findings : Array[Diagnostic] = []
let source = if assembly.source_record_indexes_value.length() > 0 {
assembly.source_record_indexes_value[0]
} else {
0
}
if assembly.total_messages_value <= 0 ||
assembly.source_record_indexes_value.length() !=
assembly.total_messages_value {
findings.push(
integrity_diagnostic(
"GSV source fragments do not match announced message total",
source_index=source,
),
)
}
if assembly.observations_value.length() != assembly.total_satellites_value {
findings.push(
integrity_diagnostic(
"GSV observations do not match announced satellite total",
source_index=source,
),
)
}
findings
}
///|
fn source_contains(sources : Array[Int], record_index : Int) -> Bool {
for source in sources {
if source == record_index {
return true
}
}
false
}
///|
pub fn validate_fused_fix_integrity(fix : FusedFix) -> Array[Diagnostic] {
let findings : Array[Diagnostic] = []
let source = if fix.source_record_indexes_value.length() > 0 {
fix.source_record_indexes_value[0]
} else {
0
}
if (fix.latitude_value is Some(_)) != (fix.longitude_value is Some(_)) {
findings.push(
integrity_diagnostic(
"fused position has only one coordinate",
source_index=source,
),
)
}
if fix.usable_value &&
(fix.latitude_value is None || fix.longitude_value is None) {
findings.push(
integrity_diagnostic(
"usable fused fix has no complete position",
source_index=source,
),
)
}
if fix.source_record_indexes_value.length() > 0 &&
fix.provenance_value.length() == 0 {
findings.push(
integrity_diagnostic(
"fused fix has sources but no field provenance",
source_index=source,
),
)
}
for provenance in fix.provenance_value {
if !source_contains(
fix.source_record_indexes_value,
provenance.record_index_value,
) {
findings.push(
integrity_diagnostic(
"field provenance references an absent source record",
source_index=provenance.record_index_value,
),
)
}
}
findings
}
///|
pub fn validate_track_integrity(track : Track) -> Array[Diagnostic] {
let findings : Array[Diagnostic] = []
let expected_segments = if track.points_value.length() == 0 {
0
} else {
track.points_value.length() - 1
}
if track.summary_value.point_count_value != track.points_value.length() {
findings.push(
integrity_diagnostic("track summary point count is inconsistent"),
)
}
if track.segments_value.length() != expected_segments ||
track.summary_value.segment_count_value != track.segments_value.length() {
findings.push(integrity_diagnostic("track segment counts are inconsistent"))
}
let mut distance = 0.0
for index = 0; index < track.segments_value.length(); index = index + 1 {
let segment = track.segments_value[index]
distance = distance + segment.distance_meters_value
if segment.start_index_value != index ||
segment.end_index_value != index + 1 {
findings.push(
integrity_diagnostic("track segment indexes are inconsistent"),
)
}
}
if (distance - track.summary_value.total_distance_meters_value).abs() >
0.000001 {
findings.push(integrity_diagnostic("track total distance is inconsistent"))
}
if (track.points_value.length() == 0) !=
(track.summary_value.bounds_value is None) {
findings.push(integrity_diagnostic("track bounds presence is inconsistent"))
}
findings
}