mod convert;
pub(crate) mod ffi;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use chematic_core::Molecule;
use convert::ConvertError;
use ffi::{FreeStdINCHI, GetStdINCHI, GetStdINCHIKeyFromStdINCHI, InchiInput, InchiOutput};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InchiError {
NullOutput,
LibError(String),
InvalidInput(String),
KekulizationFailed(String),
}
impl std::fmt::Display for InchiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NullOutput => write!(f, "InChI library returned null output"),
Self::LibError(msg) => write!(f, "InChI library error: {msg}"),
Self::InvalidInput(msg) => write!(f, "invalid InChI input: {msg}"),
Self::KekulizationFailed(msg) => write!(f, "Kekulization failed: {msg}"),
}
}
}
impl std::error::Error for InchiError {}
pub fn standard_inchi(mol: &Molecule) -> Result<String, InchiError> {
let (mut atoms, mut stereo) = convert::mol_to_inchi_atoms(mol).map_err(|e| match e {
ConvertError::KekulizationFailed(msg) => InchiError::KekulizationFailed(msg),
})?;
if atoms.len() > 32766 {
return Err(InchiError::InvalidInput(format!(
"molecule has {} heavy atoms; InChI C library maximum is 32766",
atoms.len()
)));
}
let mut input = InchiInput {
atom: if atoms.is_empty() {
std::ptr::null_mut()
} else {
atoms.as_mut_ptr()
},
stereo0d: if stereo.is_empty() {
std::ptr::null_mut()
} else {
stereo.as_mut_ptr()
},
options: std::ptr::null(),
num_atoms: atoms.len() as i16,
num_stereo0d: stereo.len() as i16,
};
let mut output = InchiOutput {
sz_inchi: std::ptr::null_mut(),
sz_aux_info: std::ptr::null_mut(),
sz_message: std::ptr::null_mut(),
sz_log: std::ptr::null_mut(),
};
let ret = unsafe { GetStdINCHI(&mut input, &mut output) };
if ret > 1 {
let msg = if output.sz_message.is_null() {
String::from("unknown error")
} else {
unsafe { CStr::from_ptr(output.sz_message).to_string_lossy().into_owned() }
};
unsafe { FreeStdINCHI(&mut output) };
return Err(InchiError::LibError(msg));
}
if output.sz_inchi.is_null() {
unsafe { FreeStdINCHI(&mut output) };
return Err(InchiError::NullOutput);
}
let inchi = unsafe { CStr::from_ptr(output.sz_inchi).to_string_lossy().into_owned() };
unsafe { FreeStdINCHI(&mut output) };
Ok(inchi)
}
pub fn standard_inchi_key(inchi_str: &str) -> Result<String, InchiError> {
let c_inchi = CString::new(inchi_str)
.map_err(|e| InchiError::InvalidInput(e.to_string()))?;
let mut key_buf = vec![0 as c_char; 28];
let ret = unsafe { GetStdINCHIKeyFromStdINCHI(c_inchi.as_ptr(), key_buf.as_mut_ptr()) };
if ret != 0 {
return Err(InchiError::LibError(format!(
"GetStdINCHIKeyFromStdINCHI returned error code {ret}"
)));
}
let key = unsafe {
CStr::from_ptr(key_buf.as_ptr())
.to_string_lossy()
.into_owned()
};
Ok(key)
}