use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HgvsVersion {
pub year: u16,
pub month: u8,
}
impl HgvsVersion {
pub const fn new(year: u16, month: u8) -> Self {
Self { year, month }
}
pub const V15_11: Self = Self::new(15, 11);
pub const V19_01: Self = Self::new(19, 1);
pub const V20_05: Self = Self::new(20, 5);
pub const CURRENT: Self = Self::V20_05;
pub fn at_least(&self, other: Self) -> bool {
*self >= other
}
}
impl Default for HgvsVersion {
fn default() -> Self {
Self::CURRENT
}
}
impl fmt::Display for HgvsVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02}.{:02}", self.year, self.month)
}
}
#[derive(Debug, Clone)]
pub struct VersionedFeature {
pub name: &'static str,
pub description: &'static str,
pub introduced: HgvsVersion,
pub deprecated: Option<HgvsVersion>,
}
impl VersionedFeature {
pub const fn new(
name: &'static str,
description: &'static str,
introduced: HgvsVersion,
) -> Self {
Self {
name,
description,
introduced,
deprecated: None,
}
}
pub const fn deprecated(
name: &'static str,
description: &'static str,
introduced: HgvsVersion,
deprecated: HgvsVersion,
) -> Self {
Self {
name,
description,
introduced,
deprecated: Some(deprecated),
}
}
pub fn is_supported(&self, version: HgvsVersion) -> bool {
version.at_least(self.introduced)
}
pub fn is_deprecated(&self, version: HgvsVersion) -> bool {
self.deprecated.is_some_and(|dep| version.at_least(dep))
}
}
pub mod features {
use super::*;
pub const PROTEIN_EXTENSION: VersionedFeature = VersionedFeature::new(
"protein_extension",
"Protein N-terminal and C-terminal extensions",
HgvsVersion::V15_11,
);
pub const REPEAT_NOTATION: VersionedFeature = VersionedFeature::new(
"repeat_notation",
"Repeated sequence notation with [n] syntax",
HgvsVersion::V15_11,
);
pub const ALLELE_NOTATION: VersionedFeature = VersionedFeature::new(
"allele_notation",
"Compound heterozygous notation [a];[b]",
HgvsVersion::V15_11,
);
pub const MOSAIC_NOTATION: VersionedFeature = VersionedFeature::new(
"mosaic_notation",
"Mosaic variant notation with /",
HgvsVersion::V15_11,
);
pub const CHIMERIC_NOTATION: VersionedFeature = VersionedFeature::new(
"chimeric_notation",
"Chimeric variant notation with //",
HgvsVersion::V15_11,
);
pub const UNCERTAIN_POSITIONS: VersionedFeature = VersionedFeature::new(
"uncertain_positions",
"Uncertain positions with (pos) notation",
HgvsVersion::V15_11,
);
pub const REFERENCE_REQUIRED: VersionedFeature = VersionedFeature::new(
"reference_required",
"Reference sequence mandatory in all descriptions",
HgvsVersion::V15_11,
);
pub const GENE_CONVERSION: VersionedFeature = VersionedFeature::new(
"gene_conversion",
"Gene conversion notation with con prefix",
HgvsVersion::V15_11,
);
}
pub mod deprecated {
use super::*;
pub const STOP_AS_X: VersionedFeature = VersionedFeature::deprecated(
"stop_as_x",
"Using X for stop codon - use Ter instead",
HgvsVersion::V15_11,
HgvsVersion::V15_11,
);
pub const STOP_AS_STAR: VersionedFeature = VersionedFeature::deprecated(
"stop_as_star",
"Using * for stop codon - use Ter instead",
HgvsVersion::V15_11,
HgvsVersion::V15_11,
);
pub const IVS_NOTATION: VersionedFeature = VersionedFeature::deprecated(
"ivs_notation",
"IVS notation for introns - use intronic offsets instead",
HgvsVersion::V15_11,
HgvsVersion::V15_11,
);
pub const FS_X_NOTATION: VersionedFeature = VersionedFeature::deprecated(
"fs_x_notation",
"fsX notation - use fsTer instead",
HgvsVersion::V15_11,
HgvsVersion::V15_11,
);
}
pub fn current_version() -> HgvsVersion {
HgvsVersion::CURRENT
}
pub fn is_feature_supported(feature: &VersionedFeature) -> bool {
feature.is_supported(HgvsVersion::CURRENT)
}
pub fn known_features() -> Vec<&'static VersionedFeature> {
vec![
&features::PROTEIN_EXTENSION,
&features::REPEAT_NOTATION,
&features::ALLELE_NOTATION,
&features::MOSAIC_NOTATION,
&features::CHIMERIC_NOTATION,
&features::UNCERTAIN_POSITIONS,
&features::REFERENCE_REQUIRED,
&features::GENE_CONVERSION,
]
}
pub fn deprecated_features() -> Vec<&'static VersionedFeature> {
vec![
&deprecated::STOP_AS_X,
&deprecated::STOP_AS_STAR,
&deprecated::IVS_NOTATION,
&deprecated::FS_X_NOTATION,
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_comparison() {
assert!(HgvsVersion::V20_05 > HgvsVersion::V19_01);
assert!(HgvsVersion::V19_01 > HgvsVersion::V15_11);
assert!(HgvsVersion::V20_05.at_least(HgvsVersion::V15_11));
}
#[test]
fn test_version_display() {
assert_eq!(HgvsVersion::V20_05.to_string(), "20.05");
assert_eq!(HgvsVersion::V15_11.to_string(), "15.11");
}
#[test]
fn test_current_version() {
assert_eq!(current_version(), HgvsVersion::V20_05);
}
#[test]
fn test_feature_supported() {
assert!(features::PROTEIN_EXTENSION.is_supported(HgvsVersion::V20_05));
assert!(features::PROTEIN_EXTENSION.is_supported(HgvsVersion::V15_11));
assert!(!features::PROTEIN_EXTENSION.is_supported(HgvsVersion::new(14, 1)));
}
#[test]
fn test_deprecated_feature() {
assert!(deprecated::STOP_AS_X.is_deprecated(HgvsVersion::V20_05));
assert!(deprecated::IVS_NOTATION.is_deprecated(HgvsVersion::CURRENT));
}
#[test]
fn test_known_features() {
let features = known_features();
assert!(!features.is_empty());
assert!(features.iter().all(|f| is_feature_supported(f)));
}
#[test]
fn test_deprecated_features() {
let deps = deprecated_features();
assert!(!deps.is_empty());
}
}