use super::codes::{CodeCategory, CodeInfo, ModeBehavior};
use std::collections::HashMap;
use std::sync::OnceLock;
static CODE_REGISTRY: OnceLock<HashMap<&'static str, CodeInfo>> = OnceLock::new();
fn get_registry() -> &'static HashMap<&'static str, CodeInfo> {
CODE_REGISTRY.get_or_init(build_registry)
}
fn build_registry() -> HashMap<&'static str, CodeInfo> {
let mut map = HashMap::new();
map.insert(
"E1001",
CodeInfo {
code: "E1001",
name: "InvalidAccession",
summary: "The accession identifier does not match any recognized format.",
explanation: "HGVS expressions require a valid reference sequence accession number. \
Valid prefixes include NM_ (mRNA), NP_ (protein), NC_ (chromosome), \
NG_ (genomic region), NR_ (non-coding RNA), and LRG_ (Locus Reference Genomic).",
category: CodeCategory::Parse,
bad_examples: &["XY_000088.3:c.459A>G", "000088.3:c.459A>G"],
good_examples: &["NM_000088.3:c.459A>G", "NC_000001.11:g.12345A>G"],
mode_behavior: None,
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/general/"),
related_codes: &["E1002", "W1003"],
},
);
map.insert(
"E1002",
CodeInfo {
code: "E1002",
name: "UnknownVariantType",
summary: "Unknown variant type prefix.",
explanation: "HGVS expressions must specify a coordinate type prefix: \
g. (genomic), c. (coding DNA), n. (non-coding DNA), r. (RNA), \
p. (protein), m. (mitochondrial), or o. (circular).",
category: CodeCategory::Parse,
bad_examples: &["NM_000088.3:x.459A>G", "NM_000088.3:459A>G"],
good_examples: &["NM_000088.3:c.459A>G", "NC_000001.11:g.12345A>G"],
mode_behavior: None,
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/general/"),
related_codes: &["E1001", "W3006"],
},
);
map.insert(
"E1003",
CodeInfo {
code: "E1003",
name: "InvalidPosition",
summary: "The position format is invalid.",
explanation: "Positions must be valid integers or offset positions (for intronic variants). \
Position 0 is never valid in HGVS. Negative positions indicate upstream of the start codon, \
and *N positions indicate downstream of the stop codon.",
category: CodeCategory::Parse,
bad_examples: &["NM_000088.3:c.0A>G", "NM_000088.3:c.A>G"],
good_examples: &["NM_000088.3:c.1A>G", "NM_000088.3:c.-10A>G"],
mode_behavior: None,
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/DNA/numbering/"),
related_codes: &["E3001", "W4001", "W4002"],
},
);
map.insert(
"E1004",
CodeInfo {
code: "E1004",
name: "InvalidEdit",
summary: "The edit type or format is invalid.",
explanation: "Valid edit types include substitutions (A>G), deletions (del), \
insertions (ins), duplications (dup), inversions (inv), and \
deletion-insertions (delins). The edit format must match HGVS syntax.",
category: CodeCategory::Parse,
bad_examples: &["NM_000088.3:c.459A->G", "NM_000088.3:c.459mutation"],
good_examples: &["NM_000088.3:c.459A>G", "NM_000088.3:c.459del"],
mode_behavior: None,
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/DNA/variant/",
),
related_codes: &["E1005", "E1007"],
},
);
map.insert(
"E1005",
CodeInfo {
code: "E1005",
name: "UnexpectedEnd",
summary: "Unexpected end of input.",
explanation: "The HGVS expression ended before it was complete. \
This usually means a required component is missing.",
category: CodeCategory::Parse,
bad_examples: &["NM_000088.3:c.", "NM_000088.3:c.459"],
good_examples: &["NM_000088.3:c.459A>G", "NM_000088.3:c.459del"],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E1004", "E1006"],
},
);
map.insert(
"E1006",
CodeInfo {
code: "E1006",
name: "UnexpectedChar",
summary: "Unexpected character in input.",
explanation: "The parser encountered a character that is not valid at this position. \
Check for typos, extra characters, or incorrect syntax.",
category: CodeCategory::Parse,
bad_examples: &["NM_000088.3:c.459A>>G", "NM_000088.3:c.459A>G;extra"],
good_examples: &["NM_000088.3:c.459A>G"],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E1005", "W2004"],
},
);
map.insert(
"E1007",
CodeInfo {
code: "E1007",
name: "InvalidBase",
summary: "Invalid nucleotide base.",
explanation: "Nucleotide bases must be one of A, C, G, T (or U for RNA). \
For protein variants, use three-letter amino acid codes.",
category: CodeCategory::Parse,
bad_examples: &["NM_000088.3:c.459X>G", "NM_000088.3:c.459A>X"],
good_examples: &["NM_000088.3:c.459A>G", "NM_000088.3:c.459A>T"],
mode_behavior: None,
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/DNA/variant/substitution/",
),
related_codes: &["E1008"],
},
);
map.insert(
"E1008",
CodeInfo {
code: "E1008",
name: "InvalidAminoAcid",
summary: "Invalid amino acid code.",
explanation:
"Amino acids must be specified using three-letter codes (e.g., Val, Glu, Ala). \
Single-letter codes may be accepted in lenient mode.",
category: CodeCategory::Parse,
bad_examples: &["NP_000079.2:p.Xxx600Yyy", "NP_000079.2:p.Val600Xxx"],
good_examples: &["NP_000079.2:p.Val600Glu", "NP_000079.2:p.Arg342Ter"],
mode_behavior: None,
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/protein/variant/",
),
related_codes: &["W1001", "W1002"],
},
);
map.insert(
"E2001",
CodeInfo {
code: "E2001",
name: "ReferenceNotFound",
summary: "Reference sequence or transcript not found.",
explanation: "The specified accession could not be found in the reference data. \
Ensure the accession is correct and that reference data has been loaded.",
category: CodeCategory::Reference,
bad_examples: &["NM_999999.1:c.100A>G"],
good_examples: &["NM_000088.3:c.100A>G"],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E2002", "E2003"],
},
);
map.insert(
"E2002",
CodeInfo {
code: "E2002",
name: "SequenceNotFound",
summary: "Sequence data not available.",
explanation: "The sequence for this accession is not available. \
This may occur when transcript metadata exists but sequence data is missing.",
category: CodeCategory::Reference,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E2001"],
},
);
map.insert(
"E2003",
CodeInfo {
code: "E2003",
name: "ChromosomeNotFound",
summary: "Chromosome or contig not found.",
explanation:
"The specified chromosome or contig is not available in the reference data. \
Check that the genome build matches your reference data.",
category: CodeCategory::Reference,
bad_examples: &["NC_999999.1:g.100A>G"],
good_examples: &["NC_000001.11:g.100A>G"],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E2001"],
},
);
map.insert(
"E3001",
CodeInfo {
code: "E3001",
name: "PositionOutOfBounds",
summary: "Position is outside the valid range.",
explanation: "The specified position is beyond the length of the reference sequence. \
Check that the position is correct for this transcript or chromosome.",
category: CodeCategory::Validation,
bad_examples: &["NM_000088.3:c.99999999A>G"],
good_examples: &["NM_000088.3:c.459A>G"],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E1003", "E3003"],
},
);
map.insert(
"E3002",
CodeInfo {
code: "E3002",
name: "ReferenceMismatch",
summary: "Reference sequence mismatch.",
explanation: "The reference base(s) stated in the HGVS expression do not match \
the actual reference sequence at that position. This may indicate a wrong \
transcript version or a typo in the variant description.",
category: CodeCategory::Validation,
bad_examples: &["NM_000088.3:c.459G>A (when ref is actually A)"],
good_examples: &["NM_000088.3:c.459A>G"],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W5001", "W3001"],
},
);
map.insert(
"E3003",
CodeInfo {
code: "E3003",
name: "InvalidRange",
summary: "Invalid coordinate range.",
explanation: "The start position is greater than the end position, \
or the range is otherwise invalid.",
category: CodeCategory::Validation,
bad_examples: &["NM_000088.3:c.200_100del"],
good_examples: &["NM_000088.3:c.100_200del"],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W4001"],
},
);
map.insert(
"E3004",
CodeInfo {
code: "E3004",
name: "ExonIntronBoundary",
summary: "Variant spans exon-intron boundary.",
explanation:
"The variant spans an exon-intron junction, which cannot be properly normalized. \
Split into separate exonic and intronic components if needed.",
category: CodeCategory::Validation,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E3005", "E4001"],
},
);
map.insert(
"E3005",
CodeInfo {
code: "E3005",
name: "UtrCdsBoundary",
summary: "Variant spans UTR-CDS boundary.",
explanation: "The variant spans the boundary between UTR and coding sequence. \
This requires special handling for normalization.",
category: CodeCategory::Validation,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E3004"],
},
);
map.insert(
"E3006",
CodeInfo {
code: "E3006",
name: "SelfCancellingAllele",
summary: "Self-cancelling allele (overlapping del + dup or equivalent).",
explanation: "HGVS does not allow descriptions that remove part of a reference \
sequence and re-insert (part of) the same sequence in the same allele. \
For example, `c.[762_768del;767_774dup]` deletes [762..=768] and re-introduces \
bases [767..=774] from the reference, partially undoing the deletion. \
Drop the redundant edit (or rewrite as a single `delins`).",
category: CodeCategory::Validation,
bad_examples: &["NM_004006.2:c.[762_768del;767_774dup]"],
good_examples: &["NM_004006.2:c.[100_110del;200_210dup]"],
mode_behavior: None,
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/general/"),
related_codes: &["E3003", "W3003"],
},
);
map.insert(
"E4001",
CodeInfo {
code: "E4001",
name: "IntronicVariant",
summary: "Intronic variant cannot be normalized.",
explanation: "Intronic variants (those with +/- offset positions) cannot be normalized \
without genomic context. Use genomic coordinates for intronic variant normalization.",
category: CodeCategory::Normalization,
bad_examples: &["NM_000088.3:c.459+10del"],
good_examples: &["NC_000017.11:g.12345del"],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E3004"],
},
);
map.insert(
"E4002",
CodeInfo {
code: "E4002",
name: "UnsupportedVariant",
summary: "Unsupported variant type for this operation.",
explanation: "This variant type is not supported for the requested operation. \
Not all HGVS variant types can be normalized or converted.",
category: CodeCategory::Normalization,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &[],
},
);
map.insert(
"E5001",
CodeInfo {
code: "E5001",
name: "ConversionFailed",
summary: "Coordinate conversion failed.",
explanation: "The coordinate conversion between reference sequences failed. \
This may occur when converting between coding and genomic coordinates.",
category: CodeCategory::Conversion,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E5002"],
},
);
map.insert(
"E5002",
CodeInfo {
code: "E5002",
name: "NoOverlappingTranscript",
summary: "No overlapping transcript found.",
explanation:
"No transcript was found that overlaps with the specified genomic position. \
The position may be intergenic or the transcript data may be incomplete.",
category: CodeCategory::Conversion,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E5001", "E2001"],
},
);
map.insert(
"E9001",
CodeInfo {
code: "E9001",
name: "IoError",
summary: "File I/O error.",
explanation: "An error occurred while reading or writing a file. \
Check that the file exists and you have the necessary permissions.",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E9002"],
},
);
map.insert(
"E9002",
CodeInfo {
code: "E9002",
name: "JsonError",
summary: "JSON parsing error.",
explanation: "An error occurred while parsing JSON data. \
Check that the JSON is valid and matches the expected format.",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E9001"],
},
);
map.insert(
"W1001",
CodeInfo {
code: "W1001",
name: "LowercaseAminoAcid",
summary: "Lowercase amino acid code.",
explanation: "Three-letter amino acid codes should use standard capitalization \
with the first letter uppercase (e.g., Val, Glu, Ala). Lowercase codes like \
'val' or 'glu' are auto-corrected in lenient/silent modes.",
category: CodeCategory::Case,
bad_examples: &["p.val600glu", "p.VAL600GLU"],
good_examples: &["p.Val600Glu"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/protein/variant/",
),
related_codes: &["W1002", "E1008"],
},
);
map.insert(
"W1002",
CodeInfo {
code: "W1002",
name: "SingleLetterAminoAcid",
summary: "Single-letter amino acid code.",
explanation: "HGVS recommends three-letter amino acid codes. Single-letter codes \
like 'V' for Valine may be auto-expanded in lenient/silent modes.",
category: CodeCategory::Case,
bad_examples: &["p.V600E"],
good_examples: &["p.Val600Glu"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/protein/variant/",
),
related_codes: &["W1001"],
},
);
map.insert(
"W1003",
CodeInfo {
code: "W1003",
name: "LowercaseAccessionPrefix",
summary: "Lowercase accession prefix.",
explanation: "Accession prefixes should be uppercase (NM_, NC_, NP_, etc.). \
Lowercase prefixes like 'nm_' are auto-corrected in lenient/silent modes.",
category: CodeCategory::Case,
bad_examples: &["nm_000088.3:c.459A>G", "nc_000001.11:g.12345A>G"],
good_examples: &["NM_000088.3:c.459A>G", "NC_000001.11:g.12345A>G"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &["E1001"],
},
);
map.insert(
"W1004",
CodeInfo {
code: "W1004",
name: "MixedCaseEditType",
summary: "Mixed case in edit type keyword.",
explanation: "Edit type keywords should be lowercase (del, ins, dup, etc.). \
Mixed case like 'Del' or 'INS' is auto-corrected in lenient/silent modes.",
category: CodeCategory::Case,
bad_examples: &["c.100Del", "c.100_101INS"],
good_examples: &["c.100del", "c.100_101ins"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &[],
},
);
map.insert(
"W2001",
CodeInfo {
code: "W2001",
name: "WrongDashCharacter",
summary: "Wrong dash character (en-dash or em-dash).",
explanation: "HGVS requires ASCII hyphen-minus (-). Word processors often \
substitute en-dash (–) or em-dash (—). These are auto-corrected in \
lenient/silent modes.",
category: CodeCategory::Character,
bad_examples: &["c.100–200del", "c.100—200del"],
good_examples: &["c.100-200del"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &["W2002", "W2004"],
},
);
map.insert(
"W2002",
CodeInfo {
code: "W2002",
name: "WrongQuoteCharacter",
summary: "Smart quotes instead of ASCII quotes.",
explanation: "HGVS requires ASCII quotes. Word processors often substitute \
curly/smart quotes. These are auto-corrected in lenient/silent modes.",
category: CodeCategory::Character,
bad_examples: &["c.100_101ins\u{201C}ATG\u{201D}"],
good_examples: &["c.100_101ins\"ATG\""],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &["W2001", "W2004"],
},
);
map.insert(
"W2003",
CodeInfo {
code: "W2003",
name: "ExtraWhitespace",
summary: "Extra whitespace in description.",
explanation: "HGVS expressions should not contain spaces (the spec is explicit \
that the format `reference:description` has spaces \"added for clarity only\"). \
Leading, trailing, and embedded whitespace — and zero-width invisible \
characters (U+200B ZERO WIDTH SPACE, U+200C ZERO WIDTH NON-JOINER, \
U+200D ZERO WIDTH JOINER, U+FEFF BOM/ZWNBSP) — are stripped in \
lenient/silent modes.",
category: CodeCategory::Character,
bad_examples: &[
"c.100 A>G",
"NM_000088.3 : c.459A>G",
"c.[100A>G ; 200T>C]",
"NM_000088.3\u{200B}:c.100A>G",
],
good_examples: &["c.100A>G", "NM_000088.3:c.459A>G"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/general/"),
related_codes: &["W2001", "W2004"],
},
);
map.insert(
"W2004",
CodeInfo {
code: "W2004",
name: "InvalidUnicodeCharacter",
summary: "Non-ASCII Unicode character.",
explanation: "HGVS expressions should use ASCII characters only. \
Common Unicode look-alikes are auto-corrected in lenient/silent modes.",
category: CodeCategory::Character,
bad_examples: &[],
good_examples: &[],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &["W2001", "W2002"],
},
);
map.insert(
"W3001",
CodeInfo {
code: "W3001",
name: "MissingVersion",
summary: "Missing transcript version.",
explanation: "Reference sequence accessions should include a version number \
(e.g., NM_000088.3 not NM_000088). Without a version, the correct reference \
sequence cannot be determined. In lenient/silent modes, parsing succeeds but \
the variant is flagged for downstream handling.",
category: CodeCategory::Format,
bad_examples: &["NM_000088:c.459A>G"],
good_examples: &["NM_000088.3:c.459A>G"],
mode_behavior: Some(ModeBehavior::warn_accept()),
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/general/"),
related_codes: &["E3002"],
},
);
map.insert(
"W3002",
CodeInfo {
code: "W3002",
name: "ProteinSubstitutionArrow",
summary: "Arrow in protein substitution.",
explanation: "Protein substitutions should not use '>' between amino acids. \
Use the format 'p.Val600Glu' not 'p.Val600>Glu'.",
category: CodeCategory::Format,
bad_examples: &["p.Val600>Glu"],
good_examples: &["p.Val600Glu"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/protein/variant/substitution/"),
related_codes: &[],
},
);
map.insert(
"W3003",
CodeInfo {
code: "W3003",
name: "OldSubstitutionSyntax",
summary: "Old-style multi-base substitution syntax.",
explanation: "Multi-base substitutions should use 'delins' not '>'. \
For example, 'c.100_102delinsATG' not 'c.100_102>ATG'.",
category: CodeCategory::Format,
bad_examples: &["c.100_102>ATG"],
good_examples: &["c.100_102delinsATG"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &[],
},
);
map.insert(
"W3004",
CodeInfo {
code: "W3004",
name: "OldAlleleFormat",
summary: "Old/deprecated allele bracket format.",
explanation: "In compound allele notation, the coordinate type should appear before \
the brackets. Use 'NM_000088.3:c.[100A>G;200C>T]' not 'NM_000088.3:[c.100A>G;c.200C>T]'.",
category: CodeCategory::Format,
bad_examples: &["NM_000088.3:[c.100A>G;c.200C>T]"],
good_examples: &["NM_000088.3:c.[100A>G;200C>T]"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &[],
},
);
map.insert(
"W3005",
CodeInfo {
code: "W3005",
name: "TrailingAnnotation",
summary: "Trailing protein annotation.",
explanation: "ClinVar and other databases often append protein consequence annotations \
to HGVS expressions (e.g., '(p.Lys236=)'). These are stripped in lenient/silent modes.",
category: CodeCategory::Format,
bad_examples: &["NM_000088.3:c.459A>G (p.Lys153=)", "NM_000088.3:c.100del (p.Val34fs)"],
good_examples: &["NM_000088.3:c.459A>G", "NM_000088.3:c.100del"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &[],
},
);
map.insert(
"W3006",
CodeInfo {
code: "W3006",
name: "MissingCoordinatePrefix",
summary: "Missing coordinate type prefix.",
explanation: "HGVS expressions require a coordinate type prefix (g., c., p., etc.). \
For chromosomal accessions (NC_), 'g.' can be inferred in lenient/silent modes.",
category: CodeCategory::Format,
bad_examples: &["NC_000001.11:12345A>G"],
good_examples: &["NC_000001.11:g.12345A>G"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &["E1002"],
},
);
map.insert(
"W3007",
CodeInfo {
code: "W3007",
name: "DeprecatedStopCodonStar",
summary: "Deprecated '*' for stop codon in protein substitution.",
explanation: "The HGVS checklist permits both 'Ter' and '*' to indicate a \
translation stop codon, but the three-letter 'Ter' is the preferred form. \
In lenient/silent modes 'p.Arg97*' is rewritten to 'p.Arg97Ter'.",
category: CodeCategory::Format,
bad_examples: &["NP_000079.2:p.Arg97*", "NP_000079.2:p.Trp10*"],
good_examples: &["NP_000079.2:p.Arg97Ter", "NP_000079.2:p.Trp10Ter"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/checklist/"),
related_codes: &["W3008", "W3009", "W3010"],
},
);
map.insert(
"W3008",
CodeInfo {
code: "W3008",
name: "DeprecatedStopCodonX",
summary: "Deprecated 'X' for stop codon in protein substitution.",
explanation: "Per the HGVS checklist, 'the X should not be used' to indicate a \
translation stop codon. The single-letter 'X' is also reserved for the \
'any amino acid' symbol Xaa, making 'p.Arg97X' ambiguous. In lenient/silent \
modes the input is rewritten to 'p.Arg97Ter'.",
category: CodeCategory::Format,
bad_examples: &["NP_000079.2:p.Arg97X", "NP_000079.2:p.Trp10X"],
good_examples: &["NP_000079.2:p.Arg97Ter", "NP_000079.2:p.Trp10Ter"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/protein/variant/substitution/",
),
related_codes: &["W3007", "W3009", "W3010"],
},
);
map.insert(
"W3009",
CodeInfo {
code: "W3009",
name: "DeprecatedFrameshiftStar",
summary: "Deprecated 'fs*N' frameshift termination notation.",
explanation: "The canonical HGVS frameshift form uses 'fsTerN', e.g. \
'p.Arg123LysfsTer34'. The 'fs*N' alternative is permitted but discouraged. \
In lenient/silent modes 'p.Arg97fs*23' is rewritten to 'p.Arg97fsTer23'.",
category: CodeCategory::Format,
bad_examples: &["NP_000079.2:p.Arg97fs*23", "NP_000079.2:p.Ser539Alafs*110"],
good_examples: &[
"NP_000079.2:p.Arg97fsTer23",
"NP_000079.2:p.Ser539AlafsTer110",
],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/protein/variant/frameshift/",
),
related_codes: &["W3007", "W3008", "W3010"],
},
);
map.insert(
"W3010",
CodeInfo {
code: "W3010",
name: "DeprecatedFrameshiftX",
summary: "Deprecated 'fsXN' frameshift termination notation.",
explanation: "Neither 'X' nor 'Xaa' is used in HGVS frameshift termination; the \
canonical form is 'fsTerN'. The strict parser rejects 'fsXN' outright; in \
lenient/silent modes the preprocessor rewrites 'p.Arg97fsX23' to \
'p.Arg97fsTer23' and emits this warning.",
category: CodeCategory::Format,
bad_examples: &["NP_000079.2:p.Arg97fsX23"],
good_examples: &["NP_000079.2:p.Arg97fsTer23"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/protein/variant/frameshift/",
),
related_codes: &["W3007", "W3008", "W3009"],
},
);
map.insert(
"W3011",
CodeInfo {
code: "W3011",
name: "DelSizeSuffix",
summary: "Deletion described with a size-count suffix instead of a position range.",
explanation:
"HGVS recommends naming both endpoints of a multi-residue deletion. The legacy \
form `g.123del6` (size 6 starting at position 123) is non-canonical; the canonical \
form lists the first and last residue deleted, e.g. `g.123_128del`. ferro cannot \
auto-synthesize the end position safely (it depends on offset/intronic semantics), \
so this warning is emitted but not auto-corrected.",
category: CodeCategory::Format,
bad_examples: &["NG_012232.1:g.123del6"],
good_examples: &["NG_012232.1:g.123_128del"],
mode_behavior: Some(ModeBehavior::warn_accept()),
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/DNA/deletion/",
),
related_codes: &["W4003"],
},
);
map.insert(
"W3012",
CodeInfo {
code: "W3012",
name: "EmptyDelinsInsert",
summary: "Deletion-insertion with empty inserted sequence.",
explanation:
"A `delins` with no inserted sequence is semantically equivalent to a plain \
deletion. Per HGVS spec the canonical form is `del`. In lenient/silent modes, \
ferro rewrites `delins` to `del` and warns once.",
category: CodeCategory::Format,
bad_examples: &["NC_000001.11:g.100_102delins"],
good_examples: &["NC_000001.11:g.100_102del"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/DNA/delins/"),
related_codes: &[],
},
);
map.insert(
"W3013",
CodeInfo {
code: "W3013",
name: "RedundantRepeatLabel",
summary: "Repeat description with redundant base label.",
explanation:
"HGVS spec discourages including the repeat-unit base label when the positions \
already define the unit, e.g. `r.-125_-123cug[4]` should be written as \
`r.-125_-123[4]`. In lenient/silent modes, ferro strips the redundant base \
segment and warns once.",
category: CodeCategory::Format,
bad_examples: &["NM_000088.3:r.100_102cug[4]"],
good_examples: &["NM_000088.3:r.100_102[4]"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/RNA/repeated/",
),
related_codes: &[],
},
);
map.insert(
"W3014",
CodeInfo {
code: "W3014",
name: "DeprecatedIvsNotation",
summary: "Retracted c.IVS intronic notation.",
explanation: "The c.IVSn+offset / c.IVSn-offset notation has been retracted by \
HGVS because the IVS number is not unique across transcripts and cannot be \
resolved to a unique genomic position without metadata. Use the canonical \
intronic-offset form, e.g. c.88+2T>G instead of c.IVS2+2T>G. ferro cannot \
auto-rewrite this form without genomic context, so all modes reject the input \
and emit an actionable hint.",
category: CodeCategory::Format,
bad_examples: &["c.IVS2+2T>G", "c.IVS5-1G>T"],
good_examples: &["c.88+2T>G", "c.123-1G>T"],
mode_behavior: Some(ModeBehavior::always_reject()),
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/background/numbering/"),
related_codes: &["E1006", "E4001"],
},
);
map.insert(
"W3015",
CodeInfo {
code: "W3015",
name: "DeprecatedConSyntax",
summary: "Deprecated 'con' (sequence conversion) edit syntax.",
explanation:
"The HGVS spec retired the `con` edit type in favour of `delins`. Conversions \
(a range of nucleotides replaced by a sequence from elsewhere) should be \
described as deletion-insertions: `c.100_200conNM_001.1:c.5_105` becomes \
`c.100_200delinsNM_001.1:c.5_105`. In lenient/silent modes ferro auto-rewrites; \
in strict mode the input is rejected.",
category: CodeCategory::Format,
bad_examples: &["c.100_200conNM_001.1:c.5_105"],
good_examples: &["c.100_200delinsNM_001.1:c.5_105"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/DNA/delins/"),
related_codes: &["W3003"],
},
);
map.insert(
"W4001",
CodeInfo {
code: "W4001",
name: "SwappedPositions",
summary: "Swapped/inverted range positions.",
explanation: "In a range, the start position should be less than the end position. \
Ranges like 'c.200_100del' are corrected to 'c.100_200del' in lenient/silent modes.",
category: CodeCategory::Position,
bad_examples: &["c.200_100del", "g.50000_10000dup"],
good_examples: &["c.100_200del", "g.10000_50000dup"],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: None,
related_codes: &["E3003"],
},
);
map.insert(
"W4002",
CodeInfo {
code: "W4002",
name: "PositionZero",
summary: "Invalid position zero.",
explanation: "Position 0 does not exist in HGVS notation. Coding sequences start at \
position 1 (ATG start codon), with upstream positions numbered as -1, -2, etc. \
This error cannot be auto-corrected.",
category: CodeCategory::Position,
bad_examples: &["c.0A>G", "g.0del"],
good_examples: &["c.1A>G", "c.-1A>G"],
mode_behavior: Some(ModeBehavior::always_reject()),
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/DNA/numbering/",
),
related_codes: &["E1003"],
},
);
map.insert(
"W4003",
CodeInfo {
code: "W4003",
name: "SinglePositionRange",
summary: "Single-position range used where a single position is canonical.",
explanation: "HGVS spec requires `position(s)_deleted` (and the analogous \
`positions_duplicated` / `positions_inverted`) to contain two different positions. \
Forms like `c.123_123del`, `c.123_123dup`, and `c.100_100inv` should be written \
with the single-position form `c.123del` / `c.123dup` / `c.100inv` respectively. \
In lenient/silent modes, ferro collapses the redundant range and warns once.",
category: CodeCategory::Position,
bad_examples: &[
"NM_000088.3:c.123_123del",
"NM_000088.3:c.123_123dup",
"NM_000088.3:c.100_100inv",
],
good_examples: &[
"NM_000088.3:c.123del",
"NM_000088.3:c.123dup",
"NM_000088.3:c.100inv",
],
mode_behavior: Some(ModeBehavior::standard_correctable()),
hgvs_spec_url: Some(
"https://hgvs-nomenclature.org/stable/recommendations/DNA/deletion/",
),
related_codes: &["W3011"],
},
);
map.insert(
"W5001",
CodeInfo {
code: "W5001",
name: "RefSeqMismatch",
summary: "Reference sequence mismatch.",
explanation: "The reference base(s) stated in the HGVS expression do not match \
the actual reference sequence. In lenient/silent modes, normalization proceeds \
using the actual reference sequence, but a warning is always emitted.",
category: CodeCategory::Semantic,
bad_examples: &["c.100G>A (when reference is T at position 100)"],
good_examples: &["c.100T>A (matches actual reference)"],
mode_behavior: Some(ModeBehavior::always_warn_if_not_rejected()),
hgvs_spec_url: None,
related_codes: &["E3002", "W3001"],
},
);
map.insert(
"W5002",
CodeInfo {
code: "W5002",
name: "OverlapConflictingEdits",
summary: "Two or more cis-allele edits share identical reference bounds.",
explanation: "Two or more edits in a cis allele point at the same single \
base or range with identical (start, end) bounds. The HGVS spec does \
not define a canonical form for this case; ferro preserves the input \
verbatim and emits this warning so callers can decide whether to flag \
or reject. If the HGVS Variant Working Group rules these inputs invalid, \
change the mode_behavior here to promote this code to an error in Strict.",
category: CodeCategory::Semantic,
bad_examples: &[
"g.[100G>A;100A>C]",
"g.[100del;100A>C]",
"c.[100_103del;100_103inv]",
],
good_examples: &["g.[100A>C;101A>C]", "c.[762_768del;767_774dup]"],
mode_behavior: Some(ModeBehavior::always_warn_if_not_rejected()),
hgvs_spec_url: Some("https://hgvs-nomenclature.org/stable/recommendations/general/"),
related_codes: &["W5001"],
},
);
map.insert(
"E-LOAD-001",
CodeInfo {
code: "E-LOAD-001",
name: "MalformedRecord",
summary: "A required GFF/GTF column failed to parse and the offending row was dropped.",
explanation: "During GFF3 or GTF loading, a record could not be interpreted because a \
mandatory column was malformed: for example, a non-integer coordinate, a missing \
tab delimiter, an invalid strand character, or an out-of-range phase value (must \
be 0, 1, or 2). The row is silently discarded and the loader continues. \
See the GFF/GTF loader design (§6 Stage 2 — record parsing).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E-LOAD-002", "W-LOAD-010"],
},
);
map.insert(
"E-LOAD-002",
CodeInfo {
code: "E-LOAD-002",
name: "UnsupportedFormat",
summary: "The input file could not be classified as GFF3 or GTF after extension and content sniffing.",
explanation: "ferro attempts to detect the annotation format by examining the file \
extension and scanning for format-specific header directives (e.g. `##gff-version 3` \
for GFF3, `gene_id` attributes for GTF). When neither heuristic succeeds the file \
is treated as unsupported. Currently reserved — Phase 1 falls back to GFF3 when \
detection is ambiguous (see W-LOAD-001). \
See the GFF/GTF loader design (§6 Stage 1 — format detection).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &[],
},
);
map.insert(
"E-LOAD-103",
CodeInfo {
code: "E-LOAD-103",
name: "StrandRequired",
summary: "A transcript has an unspecified or unknown strand and was dropped.",
explanation: "HGVS coordinate arithmetic requires a defined strand (+ or −) for every \
transcript. GFF3 allows `.` (unspecified) and `?` (unknown) as strand values; \
transcripts carrying either of these values cannot be represented in ferro's \
data model and are discarded. If the strand is determinable from context, \
fix the source annotation before loading. \
See the GFF/GTF loader design (§6 Stage 4 — transcript assembly).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W-LOAD-100"],
},
);
map.insert(
"W-LOAD-001",
CodeInfo {
code: "W-LOAD-001",
name: "UnknownFormat",
summary: "Format could not be determined with high confidence; defaulted to GFF3.",
explanation: "ferro examines the file extension and the presence of `##gff-version` \
or GTF-style attribute syntax to decide which parser to invoke. When neither \
indicator is present or they conflict, ferro falls back to GFF3 and emits this \
warning. Inspect the file extension and ensure a `##gff-version 3` (or `2`) \
directive is present on the first line. \
See the GFF/GTF loader design (§6 Stage 1 — format detection).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &[],
},
);
map.insert(
"W-LOAD-002",
CodeInfo {
code: "W-LOAD-002",
name: "InlineFastaIgnored",
summary: "A `##FASTA` directive was encountered; inline sequences are not parsed.",
explanation: "GFF3 files may embed reference sequences after a `##FASTA` line. \
ferro's loader does not parse inline FASTA sections — if reference sequences are \
needed for CDS validation, supply them via `--fasta` separately. \
Currently reserved — Phase 1 does not yet detect the `##FASTA` directive. \
See the GFF/GTF loader design (§6 Stage 2 — record parsing).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &[],
},
);
map.insert(
"W-LOAD-010",
CodeInfo {
code: "W-LOAD-010",
name: "OrphanFeature",
summary: "A feature references a parent ID that is not present in the file; the orphan is dropped.",
explanation: "In GFF3, child features reference their parent via the `Parent=` \
attribute; in GTF, children reference `transcript_id`. When the referenced \
parent record is absent — common with truncated or region-filtered input files — \
the child feature cannot be assembled into a transcript and is discarded. \
Check that the source file is complete and that all parent features are present. \
See the GFF/GTF loader design (§6 Stage 3 — parent-child linking).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["E-LOAD-001"],
},
);
map.insert(
"W-LOAD-100",
CodeInfo {
code: "W-LOAD-100",
name: "TranscriptWithoutExons",
summary: "A transcript-like feature has no exon, UTR, or CDS children and was dropped.",
explanation: "After parent-child linking, a transcript record (mRNA, transcript, \
ncRNA, lincRNA, etc.) was found with no child features from which an exon \
structure could be derived. Without at least one exon, UTR, or CDS interval \
there is nothing to build the transcript model from, so the transcript is \
discarded. Verify that the source file contains matching child records with the \
correct `Parent=` or `transcript_id` attributes. \
See the GFF/GTF loader design (§6 Stage 4 — transcript assembly).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W-LOAD-101", "E-LOAD-103"],
},
);
map.insert(
"W-LOAD-101",
CodeInfo {
code: "W-LOAD-101",
name: "GeneAsTranscript",
summary: "A `gene` feature with direct CDS children (no mRNA/transcript) is treated as a transcript.",
explanation: "Some prokaryotic GFF3 files attach CDS records directly to a `gene` \
feature without an intervening mRNA or transcript record. ferro detects this \
pattern and promotes the gene to act as its own transcript, preserving the CDS \
coordinates. If this was unintentional, restructure the annotation to include an \
explicit transcript-level feature. \
See the GFF/GTF loader design (§6 Stage 4 — transcript assembly).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W-LOAD-100"],
},
);
map.insert(
"W-LOAD-110",
CodeInfo {
code: "W-LOAD-110",
name: "PhaseAppliedToCdsStart",
summary: "The leading CDS feature has a non-zero phase; `cds_start_genomic` was shifted by the phase value.",
explanation: "GFF3 phase values indicate how many bases at the start of a CDS feature \
should be skipped to reach the first complete in-frame codon. When the 5'-most CDS \
record on a transcript carries a phase of 1 or 2, the loader advances \
`cds_start_genomic` by that number of bases so that ferro's CDS coordinates \
begin at a codon boundary. This is expected for fragmented or partial CDS \
annotations but may indicate a truncated transcript. \
See the GFF/GTF loader design (§6 Stage 4 — CDS start adjustment).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W-LOAD-111"],
},
);
map.insert(
"W-LOAD-111",
CodeInfo {
code: "W-LOAD-111",
name: "PhaseUnavailable",
summary:
"The leading CDS feature has no phase recorded; the unshifted CDS start is used.",
explanation: "When the 5'-most CDS record on a transcript has a missing or `.` phase \
field, ferro cannot determine whether the genomic start is offset from a codon \
boundary. The loader falls back to using the unshifted coordinate, which may be \
incorrect if the annotation was intended to encode a non-zero phase. Inspect the \
source annotation and add an explicit phase value where possible. \
See the GFF/GTF loader design (§6 Stage 4 — CDS start adjustment).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W-LOAD-110"],
},
);
map.insert(
"W-LOAD-112",
CodeInfo {
code: "W-LOAD-112",
name: "StopCodonAssumed",
summary: "No explicit `stop_codon` record was found; `cds_end_genomic` was extended by 3 bp on the 3' side.",
explanation: "ferro's CDS model is stop-codon-inclusive: the 3' boundary of the CDS \
encompasses the stop codon. GFF3 files often encode the stop codon as a separate \
`stop_codon` feature rather than including it in the CDS interval. When no \
`stop_codon` record is present, the loader extends `cds_end_genomic` by 3 bp \
toward the 3' end, clipped at the transcript boundary. If the extension would \
overshoot the last exon, the CDS end may be slightly incorrect. Verify that the \
transcript end is consistent with the annotated stop codon. \
See the GFF/GTF loader design (§6 Stage 4 — stop codon handling).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W-LOAD-110"],
},
);
map.insert(
"W-LOAD-200",
CodeInfo {
code: "W-LOAD-200",
name: "CdsLengthNotMod3",
summary: "FASTA validation found a CDS whose length is not divisible by 3.",
explanation: "When FASTA-based validation is enabled (Phase 4), ferro checks that the \
assembled CDS length (3' end inclusive of the stop codon) is a multiple of 3. A \
non-multiple-of-3 length suggests a frameshift, an incomplete CDS record, or an \
annotation error in the source file. Currently reserved — FASTA validation is not \
yet shipped (Phase 4). \
See the GFF/GTF loader design (§6 Stage 5 — FASTA validation).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W-LOAD-201"],
},
);
map.insert(
"W-LOAD-201",
CodeInfo {
code: "W-LOAD-201",
name: "NonCanonicalStartCodon",
summary:
"FASTA validation found that the first CDS codon is not in {ATG, CTG, GTG, TTG}.",
explanation: "When FASTA-based validation is enabled (Phase 4), ferro reads the first \
three bases of the CDS from the reference FASTA and checks for a canonical start \
codon (ATG) or one of the known alternative initiators (CTG, GTG, TTG). Any other \
triplet suggests a mis-annotated CDS start or a sequencing artefact. Currently \
reserved — FASTA validation is not yet shipped (Phase 4). \
See the GFF/GTF loader design (§6 Stage 5 — FASTA validation).",
category: CodeCategory::Io,
bad_examples: &[],
good_examples: &[],
mode_behavior: None,
hgvs_spec_url: None,
related_codes: &["W-LOAD-200"],
},
);
map
}
pub fn get_code_info(code: &str) -> Option<&'static CodeInfo> {
let uppercase = code.to_uppercase();
get_registry().get(uppercase.as_str())
}
pub fn list_all_codes() -> Vec<&'static CodeInfo> {
let mut codes: Vec<_> = get_registry().values().collect();
codes.sort_by_key(|c| c.code);
codes
}
pub fn list_error_codes() -> Vec<&'static CodeInfo> {
let mut codes: Vec<_> = get_registry().values().filter(|c| c.is_error()).collect();
codes.sort_by_key(|c| c.code);
codes
}
pub fn list_warning_codes() -> Vec<&'static CodeInfo> {
let mut codes: Vec<_> = get_registry().values().filter(|c| c.is_warning()).collect();
codes.sort_by_key(|c| c.code);
codes
}
pub fn list_codes_by_category(category: CodeCategory) -> Vec<&'static CodeInfo> {
let mut codes: Vec<_> = get_registry()
.values()
.filter(|c| c.category == category)
.collect();
codes.sort_by_key(|c| c.code);
codes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_has_codes() {
assert!(!get_registry().is_empty());
}
#[test]
fn test_get_code_info() {
let e1001 = get_code_info("E1001");
assert!(e1001.is_some());
assert_eq!(e1001.unwrap().name, "InvalidAccession");
let w1001 = get_code_info("W1001");
assert!(w1001.is_some());
assert_eq!(w1001.unwrap().name, "LowercaseAminoAcid");
assert!(get_code_info("NOTEXIST").is_none());
}
#[test]
fn test_list_error_codes() {
let errors = list_error_codes();
assert!(!errors.is_empty());
for code in errors {
assert!(code.code.starts_with('E'));
}
}
#[test]
fn test_list_warning_codes() {
let warnings = list_warning_codes();
assert!(!warnings.is_empty());
for code in warnings {
assert!(code.code.starts_with('W'));
}
}
#[test]
fn test_list_all_codes_sorted() {
let codes = list_all_codes();
let sorted: Vec<_> = codes.iter().map(|c| c.code).collect();
let mut check = sorted.clone();
check.sort();
assert_eq!(sorted, check);
}
#[test]
fn test_warning_codes_have_mode_behavior() {
let warnings = list_warning_codes();
for code in warnings {
if code.code.starts_with("W-LOAD-") {
continue;
}
assert!(
code.mode_behavior.is_some(),
"Warning {} should have mode_behavior",
code.code
);
}
}
#[test]
fn test_error_codes_no_mode_behavior() {
let errors = list_error_codes();
for code in errors {
assert!(
code.mode_behavior.is_none(),
"Error {} should not have mode_behavior",
code.code
);
}
}
#[test]
fn test_format_terminal() {
let code = get_code_info("W1001").unwrap();
let output = code.format_terminal(false);
assert!(output.contains("W1001"));
assert!(output.contains("LowercaseAminoAcid"));
assert!(output.contains("ferro parse --ignore"));
}
#[test]
fn test_format_json() {
let code = get_code_info("E1001").unwrap();
let json = code.format_json();
assert!(json.contains("\"code\":\"E1001\""));
assert!(json.contains("\"name\":\"InvalidAccession\""));
}
#[test]
fn test_format_markdown() {
let code = get_code_info("W1001").unwrap();
let md = code.format_markdown();
assert!(md.contains("## W1001:"));
assert!(md.contains("| Mode | Action |"));
}
#[test]
fn test_list_codes_by_category() {
let parse_codes = list_codes_by_category(CodeCategory::Parse);
assert!(!parse_codes.is_empty());
for code in parse_codes {
assert_eq!(code.category, CodeCategory::Parse);
}
}
#[test]
fn loader_error_codes_are_registered() {
assert!(get_code_info("E-LOAD-001").is_some());
assert!(get_code_info("E-LOAD-002").is_some());
assert!(get_code_info("E-LOAD-103").is_some());
}
#[test]
fn loader_warning_codes_are_registered() {
for code in &[
"W-LOAD-001",
"W-LOAD-002",
"W-LOAD-010",
"W-LOAD-100",
"W-LOAD-101",
"W-LOAD-110",
"W-LOAD-111",
"W-LOAD-112",
"W-LOAD-200",
"W-LOAD-201",
] {
assert!(
get_code_info(code).is_some(),
"loader warning code {} not registered",
code
);
}
}
#[test]
fn loader_codes_have_consistent_metadata() {
let info = get_code_info("W-LOAD-100").unwrap();
assert_eq!(info.name, "TranscriptWithoutExons");
assert!(!info.summary.is_empty());
assert!(!info.explanation.is_empty());
}
}