molframe-pdb 0.2.0

Legacy PDB reading and writing for molframe
Documentation
fn encoded_order(order: BondOrder) -> (i32, i32) {
    match order {
        BondOrder::Single | BondOrder::Polymeric => (1, 0),
        BondOrder::Double => (2, 0),
        BondOrder::Triple => (3, 0),
        BondOrder::Quadruple => (4, 0),
        BondOrder::Aromatic => (1, 1),
        BondOrder::Unknown => (-1, -1),
    }
}

type OptionalEncodedBonds = (
    Option<serde_bytes::ByteBuf>,
    Option<serde_bytes::ByteBuf>,
    Option<serde_bytes::ByteBuf>,
);

fn encode_bond_columns(
    atoms: &[i32],
    orders: &[i32],
    resonance: &[i32],
) -> Result<OptionalEncodedBonds, Diagnostic> {
    Ok((
        (!atoms.is_empty()).then(|| encode_i32(atoms)).transpose()?,
        (!orders.is_empty())
            .then(|| encode_i32(orders))
            .transpose()?,
        (!resonance.is_empty())
            .then(|| encode_i32(resonance))
            .transpose()?,
    ))
}

fn required_text(value: Option<&str>, field: &'static str) -> Result<String, Diagnostic> {
    let value = value.ok_or_else(|| refusal("MMTF required identifier is absent"))?;
    if !value.is_ascii() {
        return Err(refusal("MMTF identifiers must be ASCII").with_context("field", field));
    }
    Ok(value.to_string())
}

fn single_ascii(value: Option<&str>, field: &'static str) -> Result<u8, Diagnostic> {
    let Some(value) = value else { return Ok(0) };
    let [byte] = value.as_bytes() else {
        return Err(refusal("MMTF character field must contain one ASCII byte")
            .with_context("field", field));
    };
    if !byte.is_ascii() {
        return Err(refusal("MMTF character field must be ASCII").with_context("field", field));
    }
    Ok(*byte)
}

fn i32_of(value: usize, field: &'static str) -> Result<i32, Diagnostic> {
    i32::try_from(value)
        .map_err(|_| refusal("MMTF count exceeds signed 32-bit range").with_context("field", field))
}

fn source_metadata(structure: &Structure) -> Result<&MmtfMetadata, Diagnostic> {
    structure
        .extensions()
        .get::<MmtfMetadata>(MMTF_METADATA_EXTENSION)
        .ok_or_else(|| refusal("MMTF chemistry metadata is absent"))
}

fn encoded_cell(structure: &Structure) -> Result<Option<Vec<f32>>, Diagnostic> {
    structure.data().cell.map_or(Ok(None), |cell| {
        cell.lengths
            .into_iter()
            .chain(cell.angles)
            .map(|value| {
                value.to_f32().ok_or_else(|| {
                    refusal("unit-cell value exceeds the MMTF floating-point range")
                })
            })
            .collect::<Result<Vec<_>, _>>()
            .map(Some)
    })
}

fn experimental_methods(structure: &Structure) -> Option<Vec<String>> {
    structure
        .data()
        .entry
        .method
        .as_deref()
        .map(|method| vec![method.to_string()])
}

fn required_presence<'a, T>(
    values: Option<&'a [T]>,
    expected: bool,
    field: &'static str,
) -> Result<Option<&'a [T]>, Diagnostic> {
    match (values, expected) {
        (Some(values), true) => Ok(Some(values)),
        (None, false) => Ok(None),
        _ => Err(refusal("MMTF optional field presence changed").with_context("field", field)),
    }
}

fn encode_optional_f32(
    values: Option<&[f32]>,
    expected: bool,
    field: &'static str,
) -> Result<Option<serde_bytes::ByteBuf>, Diagnostic> {
    required_presence(values, expected, field)?
        .map(encode_f32)
        .transpose()
}

fn encode_optional_i32(
    values: Option<&[i32]>,
    expected: bool,
    field: &'static str,
) -> Result<Option<serde_bytes::ByteBuf>, Diagnostic> {
    required_presence(values, expected, field)?
        .map(encode_i32)
        .transpose()
}

fn chars_with_presence(
    values: &[u8],
    expected: bool,
    field: &'static str,
) -> Result<Option<serde_bytes::ByteBuf>, Diagnostic> {
    if expected {
        Ok(Some(encode_chars(values)?))
    } else if values.iter().all(|value| *value == 0) {
        Ok(None)
    } else {
        Err(refusal("MMTF optional field presence changed").with_context("field", field))
    }
}

fn validate_elements(
    atoms: &[molframe_core::structure::AtomRef<'_>],
    elements: &[Box<str>],
) -> Result<Vec<String>, Diagnostic> {
    if atoms.len() != elements.len()
        || atoms.iter().zip(elements).any(|(atom, deposited)| {
            atom.element()
                .is_some_and(|element| element.symbol() != deposited.as_ref())
        })
    {
        return Err(refusal(
            "MMTF deposited elements no longer match the structure",
        ));
    }
    Ok(elements.iter().map(ToString::to_string).collect())
}

fn refusal(message: &'static str) -> Diagnostic {
    Diagnostic::new(Code::E4105).with_message(message)
}