///|
/// Public topology of the input molecule.
pub(all) enum Topology {
Linear
Circular
} derive(Eq, Debug, ToJson, @json.FromJson)
///|
/// Stable domain failures; details never contain source sequences.
pub(all) suberror RestrictError {
InvalidInput(String)
LimitExceeded(String)
CutConflict(String)
} derive(Eq, Debug)
///|
/// Validated uppercase IUPAC DNA. Coordinates are zero-based base boundaries.
pub struct Dna {
bases : String
topology : Topology
} derive(Eq, Debug, ToJson)
///|
/// Validate before using upstream permissive IUPAC conversion.
pub fn Dna::new(
raw : String,
topology? : Topology = Linear,
) -> Dna raise RestrictError {
if raw.length() == 0 {
raise InvalidInput("DNA_EMPTY")
}
if raw.length() > 100000 {
raise LimitExceeded("DNA_MAX_100000")
}
let s = raw.to_upper()
for c in s.iter() {
match c {
'A'
| 'C'
| 'G'
| 'T'
| 'R'
| 'Y'
| 'S'
| 'W'
| 'K'
| 'M'
| 'B'
| 'D'
| 'H'
| 'V'
| 'N' => ()
_ => raise InvalidInput("DNA_ALPHABET")
}
}
{ bases: s, topology }
}
///|
pub fn Dna::sequence(self : Dna) -> String {
self.bases
}
///|
pub fn Dna::topology(self : Dna) -> Topology {
self.topology
}
///|
pub fn Dna::length(self : Dna) -> Int {
self.bases.length()
}
///|
/// Base-set expansion is owned by genetic_code, not reimplemented here.
pub fn Dna::base_options(
self : Dna,
index : Int,
) -> Array[Char] raise RestrictError {
if index < 0 || index >= self.length() {
raise InvalidInput("BASE_INDEX")
}
let c = self.bases[index].to_int().unsafe_to_char()
@base.IupacBase::from_char(c).expand().map(n => n.to_char())
}