use super::Decoded;
use crate::mmtf::schema::{Entity, File};
use molframe_core::column::Presence;
use molframe_core::diagnostic::{Code, Diagnostic};
use molframe_core::io::ReadOptions;
use molframe_core::structure::{StructureData, UnitCell};
use molframe_core::symbol::SymbolId;
use molframe_core::topology::EntityKind;
pub(super) fn validate_version(version: &str) -> Result<(), Diagnostic> {
let major = version
.split('.')
.next()
.and_then(|value| value.parse::<u32>().ok());
if matches!(major, Some(0 | 1)) {
Ok(())
} else {
Err(schema_error("unsupported MMTF major version").with_context("version", version))
}
}
pub(super) fn validate_declared_counts(
file: &File,
options: &ReadOptions,
) -> Result<(), Diagnostic> {
for (name, value) in [
("numAtoms", file.num_atoms),
("numGroups", file.num_groups),
("numChains", file.num_chains),
("numModels", file.num_models),
] {
validate_declared_count(name, value, options.limits.rows_per_category)?;
}
Ok(())
}
pub(super) fn validate_declared_count(
name: &'static str,
value: i32,
row_limit: u64,
) -> Result<(), Diagnostic> {
let value = usize_of(value)?;
if value as u64 > row_limit {
return Err(molframe_core::io::Limits::exceeded("MMTF rows", value));
}
if value == 0 {
return Err(schema_error("required MMTF count is zero").with_context("field", name));
}
Ok(())
}
pub(super) fn validate_decoded_lengths(decoded: &Decoded) -> Result<(), Diagnostic> {
let atoms = usize_of(decoded.file.num_atoms)?;
let groups = usize_of(decoded.file.num_groups)?;
let chains = usize_of(decoded.file.num_chains)?;
for (name, actual, expected) in [
("xCoordList", decoded.x.len(), atoms),
("yCoordList", decoded.y.len(), atoms),
("zCoordList", decoded.z.len(), atoms),
("groupIdList", decoded.group_id.len(), groups),
("groupTypeList", decoded.group_type.len(), groups),
("chainIdList", decoded.chain_id.len(), chains),
(
"groupsPerChain",
decoded.file.groups_per_chain.len(),
chains,
),
] {
validate_exact_length(name, actual, expected)?;
}
Ok(())
}
pub(super) fn validate_exact_length(
name: &'static str,
actual: usize,
expected: usize,
) -> Result<(), Diagnostic> {
if actual == expected {
return Ok(());
}
Err(
schema_error("MMTF field length disagrees with declared count")
.with_context("field", name)
.with_context("expected", expected.to_string())
.with_context("actual", actual.to_string()),
)
}
pub(super) fn position(decoded: &Decoded, atom: usize) -> Result<[f32; 3], Diagnostic> {
let x = decoded
.x
.get(atom)
.copied()
.ok_or_else(|| schema_error("x coordinate missing"))?;
let y = decoded
.y
.get(atom)
.copied()
.ok_or_else(|| schema_error("y coordinate missing"))?;
let z = decoded
.z
.get(atom)
.copied()
.ok_or_else(|| schema_error("z coordinate missing"))?;
Ok([x, y, z])
}
pub(super) fn optional_float(
values: Option<&Vec<f32>>,
atom: usize,
default: f32,
) -> (f32, Presence) {
match values.and_then(|values| values.get(atom)).copied() {
Some(value) => (value, Presence::Present),
None => (default, Presence::Inapplicable),
}
}
pub(super) fn unit_cell(values: Option<&[f32]>) -> Result<Option<UnitCell>, Diagnostic> {
let Some(values) = values else {
return Ok(None);
};
let [a, b, c, alpha, beta, gamma] = values else {
return Err(schema_error("unitCell must contain six values"));
};
Ok(Some(UnitCell {
lengths: [f64::from(*a), f64::from(*b), f64::from(*c)],
angles: [f64::from(*alpha), f64::from(*beta), f64::from(*gamma)],
}))
}
pub(super) fn entity_kind(entity: &Entity) -> Result<EntityKind, Diagnostic> {
match entity.kind.as_str() {
"polymer" => Ok(EntityKind::Polymer),
"non-polymer" | "macrolide" => Ok(EntityKind::NonPolymer),
"water" => Ok(EntityKind::Water),
"branched" => Ok(EntityKind::Branched),
value => Err(schema_error("unsupported MMTF entity type")
.with_context("entity_type", value.to_owned())),
}
}
pub(super) fn char_symbol(
data: &mut StructureData,
value: Option<u8>,
) -> Result<Option<SymbolId>, Diagnostic> {
match value {
Some(0) | None => Ok(None),
Some(value) if value.is_ascii() => {
let mut buffer = [0u8; 4];
let text = char::from(value).encode_utf8(&mut buffer);
intern(data, text).map(Some)
}
Some(_) => Err(schema_error("MMTF character is not ASCII")),
}
}
pub(super) fn intern(data: &mut StructureData, text: &str) -> Result<SymbolId, Diagnostic> {
data.dictionary
.intern(text)
.map_err(|_| molframe_core::io::Limits::exceeded("identifier dictionary", text))
}
pub(super) fn usize_of(value: i32) -> Result<usize, Diagnostic> {
usize::try_from(value).map_err(|_| schema_error("MMTF count or index is negative"))
}
pub(super) fn schema_error(message: &'static str) -> Diagnostic {
Diagnostic::new(Code::E1102).with_message(message)
}
pub(super) fn u32_of(value: usize, field: &'static str) -> Result<u32, Diagnostic> {
u32::try_from(value).map_err(|_| {
schema_error("MMTF index exceeds unsigned 32-bit range").with_context("field", field)
})
}