use wasm_bindgen::prelude::*;
use hs_predict::pipeline::HsPipeline;
use hs_predict::session::{Answer, ClassificationSession};
use hs_predict::types::ProductDescription;
fn to_js<T: serde::Serialize>(val: &T) -> JsValue {
serde_wasm_bindgen::to_value(val).unwrap_or(JsValue::NULL)
}
fn err_js(e: impl std::fmt::Display) -> JsValue {
JsValue::from_str(&e.to_string())
}
#[wasm_bindgen]
pub fn classify_smiles(smiles: &str) -> JsValue {
match hs_predict::smiles::classify_smiles(smiles) {
Some(r) => to_js(&r),
None => JsValue::NULL,
}
}
#[wasm_bindgen]
pub fn classify_product(product_json: &str) -> Result<JsValue, JsValue> {
let product: ProductDescription =
serde_json::from_str(product_json).map_err(|e| err_js(e))?;
let pipeline = HsPipeline::new();
pipeline
.classify(&product)
.map(|pred| to_js(&pred))
.map_err(|e| err_js(e))
}
#[wasm_bindgen]
pub struct WasmSession {
session: ClassificationSession,
pipeline: HsPipeline,
}
#[wasm_bindgen]
impl WasmSession {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmSession {
WasmSession {
session: ClassificationSession::new(),
pipeline: HsPipeline::new(),
}
}
pub fn new_ja() -> WasmSession {
WasmSession {
session: ClassificationSession::new_ja(),
pipeline: HsPipeline::new(),
}
}
pub fn start(&mut self) -> JsValue {
let q = self.session.start();
to_js(&q)
}
pub fn answer(&mut self, answer_json: &str) -> Result<JsValue, JsValue> {
let answer: Answer = serde_json::from_str(answer_json).map_err(|e| err_js(e))?;
self.session
.answer(answer)
.map(|result| to_js(&result))
.map_err(|e| err_js(e))
}
pub fn classify(&self) -> Result<JsValue, JsValue> {
let product = self.session.to_product_description();
self.pipeline
.classify(&product)
.map(|pred| to_js(&pred))
.map_err(|e| err_js(e))
}
}
#[cfg(all(test, target_arch = "wasm32"))]
mod tests {
use super::*;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn classify_smiles_acetic_acid_returns_non_null() {
let r = classify_smiles("CC(O)=O");
assert!(!r.is_null(), "acetic acid SMILES should return a classification");
}
#[wasm_bindgen_test]
fn classify_smiles_empty_returns_null() {
let r = classify_smiles("");
assert!(r.is_null(), "empty SMILES should return null");
}
#[wasm_bindgen_test]
fn classify_product_naoh_solid_ok() {
let json = r#"{
"identifier": { "cas": "1310-73-2", "smiles": null, "iupac_name": null,
"inchi": null, "inchi_key": null, "cid": null },
"physical_form": "Solid",
"purity_pct": null,
"purity_type": null,
"mixture_components": null,
"intended_use": null,
"additional_context": null
}"#;
let r = classify_product(json);
assert!(r.is_ok(), "NaOH solid should classify successfully");
}
#[wasm_bindgen_test]
fn classify_product_invalid_json_returns_err() {
let r = classify_product("not json at all");
assert!(r.is_err());
}
#[wasm_bindgen_test]
fn wasm_session_start_returns_question() {
let mut s = WasmSession::new();
let q = s.start();
assert!(!q.is_null(), "start() should return a question");
}
#[wasm_bindgen_test]
fn wasm_session_answer_valid_cas() {
let mut s = WasmSession::new();
s.start();
let r = s.answer(r#"{"Text":"1310-73-2"}"#);
assert!(r.is_ok(), "valid CAS should be accepted");
}
#[wasm_bindgen_test]
fn wasm_session_answer_invalid_json_returns_err() {
let mut s = WasmSession::new();
s.start();
let r = s.answer("not json");
assert!(r.is_err());
}
}