From 15a8546b60bff6ab1e364229174d5b44633ae671 Mon Sep 17 00:00:00 2001 From: Robert Debug Date: Mon, 26 Apr 2021 10:05:36 +0200 Subject: [PATCH] reworked error handling --- xayn-ai-ffi-wasm/src/ai.rs | 103 ++++++++++++++++++++-------------- xayn-ai-ffi-wasm/src/error.rs | 71 +++++++++++++++++++++++ xayn-ai-ffi-wasm/src/lib.rs | 1 + xayn-ai-ffi-wasm/src/utils.rs | 13 ----- xayn-ai/src/analytics.rs | 3 +- 5 files changed, 136 insertions(+), 55 deletions(-) create mode 100644 xayn-ai-ffi-wasm/src/error.rs diff --git a/xayn-ai-ffi-wasm/src/ai.rs b/xayn-ai-ffi-wasm/src/ai.rs index 65ad9e11c..5431fd36c 100644 --- a/xayn-ai-ffi-wasm/src/ai.rs +++ b/xayn-ai-ffi-wasm/src/ai.rs @@ -1,17 +1,24 @@ -use std::collections::HashMap; - use js_sys::Uint8Array; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; use xayn_ai::{Builder, Document, DocumentHistory, Reranker}; -use super::utils::{ExternError, IntoJsResult}; +use super::{error::CCode, utils::IntoJsResult}; #[wasm_bindgen] +/// The Xayn AI. pub struct WXaynAi(Reranker); #[wasm_bindgen] impl WXaynAi { #[wasm_bindgen(constructor)] + /// Creates and initializes the Xayn AI. + /// + /// Requires the vocabulary and model of the tokenizer/embedder. Optionally accepts the serialized + /// reranker database, otherwise creates a new one. + /// + /// # Errors + /// - The `vocab` or `model` data are invalid. + /// - The `serialized` database is invalid. pub fn new( vocab: &[u8], model: &[u8], @@ -19,76 +26,90 @@ impl WXaynAi { ) -> Result { Builder::default() .with_serialized_database(&serialized.unwrap_or_default()) - .map_err(|cause| ExternError { - code: 1, - msg: format!("Failed to deserialize the reranker database: {}", cause), + .map_err(|cause| { + CCode::RerankerDeserialization.with_context(format!( + "Failed to deserialize the reranker database: {}", + cause + )) }) .into_js_result()? .with_bert_from_reader(vocab, model) .build() .map(WXaynAi) - .map_err(|cause| ExternError { - code: 2, - msg: format!("Failed to initialize the ai: {}", cause), + .map_err(|cause| { + CCode::InitAi.with_context(format!("Failed to initialize the ai: {}", cause)) }) .into_js_result() } + /// Reranks the documents with the Xayn AI. + /// + /// # Errors + /// Returns a null pointer if: + /// - The document `histories` are invalid. + /// - The `documents` are invalid. pub fn rerank( &mut self, - // Vec behaves like Box<[JsValue]> here - // https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/convert/trait.FromWasmAbi.html#impl-FromWasmAbi-for-Box%3C%5BJsValue%5D%3E history: Vec, documents: Vec, - ) -> Result, JsValue> { + ) -> Result, JsValue> { let history = history .iter() .map(JsValue::into_serde) .collect::, _>>() + .map_err(|cause| { + CCode::HistoriesDeserialization.with_context(format!( + "Failed to deserialize the collection of histories: {}", + cause + )) + }) .into_js_result()?; let documents = documents .iter() .map(JsValue::into_serde) .collect::, _>>() - .into_js_result()?; - let reranked = self.0.rerank(&history, &documents); - - let ranks = reranked - .into_iter() - .map(|(id, rank)| (id, rank as u32)) - .collect::>(); - documents - .iter() - .map(|document| ranks.get(&document.id).copied()) - .collect::>>() - .ok_or_else(|| { - JsValue::from_str( - "Failed to rerank the documents: The document ids are inconsistent", - ) + .map_err(|cause| { + CCode::DocumentsDeserialization.with_context(format!( + "Failed to deserialize the collection of documents: {}", + cause + )) }) + .into_js_result()?; + Ok(self.0.rerank(&history, &documents)) } + /// Serializes the database of the reranker. + /// + /// # Errors + /// - The serialization fails. pub fn serialize(&self) -> Result { self.0 .serialize() - .into_js_result() .map(|bytes| bytes.as_slice().into()) + .map_err(|cause| { + CCode::RerankerSerialization + .with_context(format!("Failed to serialize the reranker: {}", cause)) + }) + .into_js_result() } - // See [`xaynai_faults()`] for more. - // pub fn faults(&self) -> Faults { - - // self.0.errors().into() - // } - - // /// See [`xaynai_analytics()`] for more. - // unsafe fn analytics(xaynai: *const Self) -> Result { - // let xaynai = unsafe { xaynai.as_ref() }.ok_or_else(|| { - // CCode::AiPointer.with_context("Failed to get the analytics: The ai pointer is null") - // })?; + /// Retrieves faults which might occur during reranking. + /// + /// Faults can range from warnings to errors which are handled in some default way internally. + pub fn faults(&self) -> Vec { + self.0 + .errors() + .iter() + .map(|error| { + JsValue::from_serde(&CCode::Fault.with_context(error.to_string())).unwrap() + }) + .collect() + } - // Ok(CAnalytics(xaynai.0.analytics().cloned())) - // } + /// Retrieves the analytics which were collected in the penultimate reranking. + pub fn analytics(&self) -> JsValue { + JsValue::from_serde(&self.0.analytics()).unwrap() + } } #[cfg(target_arch = "wasm32")] diff --git a/xayn-ai-ffi-wasm/src/error.rs b/xayn-ai-ffi-wasm/src/error.rs new file mode 100644 index 000000000..e41c18a1a --- /dev/null +++ b/xayn-ai-ffi-wasm/src/error.rs @@ -0,0 +1,71 @@ +use serde::Serialize; +use wasm_bindgen::JsValue; + +use crate::utils::IntoJsResult; + +// just a placeholder +#[repr(i32)] +pub enum CCode { + /// A warning or uncritical error. + Fault = -2, + /// An irrecoverable error. + Panic = -1, + /// No error. + Success = 0, + /// A vocab null pointer error. + VocabPointer = 1, + /// A model null pointer error. + ModelPointer = 2, + /// A vocab or model file IO error. + ReadFile = 3, + /// A Xayn AI initialization error. + InitAi = 4, + /// A Xayn AI null pointer error. + AiPointer = 5, + /// A document histories null pointer error. + HistoriesPointer = 6, + /// A document history id null pointer error. + HistoryIdPointer = 7, + /// A documents null pointer error. + DocumentsPointer = 8, + /// A document id null pointer error. + DocumentIdPointer = 9, + /// A document snippet null pointer error. + DocumentSnippetPointer = 10, + /// Deserialization of reranker database error. + RerankerDeserialization = 11, + /// Serialization of reranker database error. + RerankerSerialization = 12, + /// Deserialization of history collection error. + HistoriesDeserialization = 13, + /// Deserialization of document collection error. + DocumentsDeserialization = 14, +} + +impl CCode { + /// Provides context for the error code. + pub fn with_context(self, message: impl Into) -> ExternError { + ExternError::new_error(self as i32, message) + } +} + +#[derive(Serialize)] +pub struct ExternError { + pub code: i32, + pub message: String, +} + +impl ExternError { + fn new_error(code: i32, message: impl Into) -> Self { + Self { + code, + message: message.into(), + } + } +} + +impl IntoJsResult for Result { + fn into_js_result(self) -> Result { + self.map_err(|e| JsValue::from_serde(&e).unwrap()) + } +} diff --git a/xayn-ai-ffi-wasm/src/lib.rs b/xayn-ai-ffi-wasm/src/lib.rs index 03dd5d74f..93de17a1d 100644 --- a/xayn-ai-ffi-wasm/src/lib.rs +++ b/xayn-ai-ffi-wasm/src/lib.rs @@ -1,2 +1,3 @@ pub mod ai; +pub mod error; pub mod utils; diff --git a/xayn-ai-ffi-wasm/src/utils.rs b/xayn-ai-ffi-wasm/src/utils.rs index 5b4b15445..7b81750b0 100644 --- a/xayn-ai-ffi-wasm/src/utils.rs +++ b/xayn-ai-ffi-wasm/src/utils.rs @@ -1,12 +1,5 @@ -use serde::Serialize; use wasm_bindgen::JsValue; -#[derive(Serialize)] -pub struct ExternError { - pub code: i32, - pub msg: String, -} - pub trait IntoJsResult { fn into_js_result(self) -> Result; } @@ -19,9 +12,3 @@ where self.map_err(|e| JsValue::from_str(&format!("{}", e))) } } - -impl IntoJsResult for Result { - fn into_js_result(self) -> Result { - self.map_err(|e| JsValue::from_serde(&e).unwrap()) - } -} diff --git a/xayn-ai/src/analytics.rs b/xayn-ai/src/analytics.rs index c6d201e43..662541f65 100644 --- a/xayn-ai/src/analytics.rs +++ b/xayn-ai/src/analytics.rs @@ -3,8 +3,9 @@ use crate::{ error::Error, reranker::systems, }; +use serde::Serialize; -#[derive(Clone)] +#[derive(Clone, Serialize)] pub struct Analytics; pub(crate) struct AnalyticsSystem;