///|
/// Audit structural invariants that can be checked without network access.
pub fn Record::validate(self : Record) -> Array[ValidationIssue] {
let issues : Array[ValidationIssue] = []
let stats = self.sequence_stats()
if self.locus.length != stats.length {
issues.push({
code: "sequence_length_mismatch",
message: "LOCUS length does not match ORIGIN sequence length",
severity: ValidationSeverity::Error,
})
}
if self.features_by_key("source").is_empty() {
issues.push({
code: "missing_source_feature",
message: "record has no source feature",
severity: ValidationSeverity::Warning,
})
}
for feature in self.features {
for segment in feature.location.segments {
match segment.remote {
Some(accession) =>
issues.push({
code: "remote_location",
message: "location references remote accession: " + accession,
severity: ValidationSeverity::Warning,
})
None =>
if segment.range.end > stats.length {
issues.push({
code: "feature_out_of_bounds",
message: "feature location extends beyond ORIGIN",
severity: ValidationSeverity::Error,
})
}
}
if segment.range.between {
issues.push({
code: "between_base_location",
message: "feature uses a between-base location",
severity: ValidationSeverity::Warning,
})
}
}
}
for check in self.check_cds_translations() {
if !check.ok {
issues.push({
code: "cds_translation_mismatch",
message: "CDS translation does not match the ORIGIN sequence",
severity: ValidationSeverity::Error,
})
}
}
issues
}
///|
/// True when an audit finding is an error rather than an advisory warning.
pub fn ValidationIssue::is_error(self : ValidationIssue) -> Bool {
self.severity is ValidationSeverity::Error
}
///|
/// Render an audit finding for CLI and JSON consumers.
pub fn ValidationIssue::to_json_string(self : ValidationIssue) -> String {
let severity = match self.severity {
ValidationSeverity::Error => "error"
ValidationSeverity::Warning => "warning"
}
"{\"code\":" +
json_string(self.code) +
",\"message\":" +
json_string(self.message) +
",\"severity\":" +
json_string(severity) +
"}"
}