Skip to content
This repository has been archived by the owner on May 9, 2022. It is now read-only.

Commit

Permalink
reworked error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert-Steiner committed Apr 26, 2021
1 parent c507a5f commit 15a8546
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 55 deletions.
103 changes: 62 additions & 41 deletions xayn-ai-ffi-wasm/src/ai.rs
Original file line number Diff line number Diff line change
@@ -1,94 +1,115 @@
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],
serialized: Option<Box<[u8]>>,
) -> Result<WXaynAi, JsValue> {
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<JsValue> 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<JsValue>,
documents: Vec<JsValue>,
) -> Result<Vec<u32>, JsValue> {
) -> Result<Vec<usize>, JsValue> {
let history = history
.iter()
.map(JsValue::into_serde)
.collect::<Result<Vec<DocumentHistory>, _>>()
.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::<Result<Vec<Document>, _>>()
.into_js_result()?;
let reranked = self.0.rerank(&history, &documents);

let ranks = reranked
.into_iter()
.map(|(id, rank)| (id, rank as u32))
.collect::<HashMap<_, _>>();
documents
.iter()
.map(|document| ranks.get(&document.id).copied())
.collect::<Option<Vec<_>>>()
.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<Uint8Array, JsValue> {
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<CAnalytics, ExternError> {
// 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<JsValue> {
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")]
Expand Down
71 changes: 71 additions & 0 deletions xayn-ai-ffi-wasm/src/error.rs
Original file line number Diff line number Diff line change
@@ -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<String>) -> 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<String>) -> Self {
Self {
code,
message: message.into(),
}
}
}

impl<T> IntoJsResult<T> for Result<T, ExternError> {
fn into_js_result(self) -> Result<T, JsValue> {
self.map_err(|e| JsValue::from_serde(&e).unwrap())
}
}
1 change: 1 addition & 0 deletions xayn-ai-ffi-wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod ai;
pub mod error;
pub mod utils;
13 changes: 0 additions & 13 deletions xayn-ai-ffi-wasm/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
fn into_js_result(self) -> Result<T, JsValue>;
}
Expand All @@ -19,9 +12,3 @@ where
self.map_err(|e| JsValue::from_str(&format!("{}", e)))
}
}

impl<T> IntoJsResult<T> for Result<T, ExternError> {
fn into_js_result(self) -> Result<T, JsValue> {
self.map_err(|e| JsValue::from_serde(&e).unwrap())
}
}
3 changes: 2 additions & 1 deletion xayn-ai/src/analytics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 15a8546

Please sign in to comment.