From 074bfae740d021b9a81174903ca3811327e80a63 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Thu, 19 Feb 2026 08:48:23 +0000 Subject: [PATCH 01/36] WIP on custom errors --- Cargo.toml | 23 +++++++------ crates/parser/Cargo.toml | 13 +++---- crates/parser/src/errors.rs | 43 ++++++++++++++++++++++++ src/components/user_input/file_upload.rs | 3 +- src/errors.rs | 6 ++++ 5 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 src/errors.rs diff --git a/Cargo.toml b/Cargo.toml index 0c0feb5..6d62891 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ "webgl", ], git="https://github.com/WebVOWL/WasmGrapher", branch="master"} horned-owl={git="https://github.com/phillord/horned-owl", rev="c4f7835ecae70b5e7281dc23b9b7fc6daa497f1d"} + leptos={version="0.8.8", features=["nightly", "multipart", "rkyv"]} log="0.4" rdf-fusion="0.1.0" rkyv="0.8.12" @@ -69,7 +70,7 @@ gloo-timers="0.2" grapher={workspace=true} http={version="1.3.1", optional=true} - leptos={version="0.8.8", features=["nightly", "multipart", "rkyv"]} + leptos={workspace=true} leptos_actix={version="0.8.5", optional=true} leptos_meta={version="0.8.5"} leptos_router={version="0.8.6", features=["nightly"]} @@ -326,13 +327,13 @@ server-fn-prefix="/api" [lints.clippy] -pedantic = { level = "deny", priority = -1 } -nursery = { level = "deny", priority = -1 } -unwrap_used = { level = "deny" } -expect_used = { level = "deny" } -too_many_lines = { level = "allow" } -missing_panics_doc = { level = "allow" } -# Triggers false positives on components, -# See https://github.com/leptos-rs/leptos/issues/3172 -# This should have been fixed around 0.8.3 but it seems not to be? -must_use_candidate = { level = "allow" } + expect_used={level="deny"} + missing_panics_doc={level="allow"} + nursery={level="deny", priority=-1} + pedantic={level="deny", priority=-1} + too_many_lines={level="allow"} + unwrap_used={level="deny"} + # Triggers false positives on components, + # See https://github.com/leptos-rs/leptos/issues/3172 + # This should have been fixed around 0.8.3 but it seems not to be? + must_use_candidate={level="allow"} diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 18ee286..c804482 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -14,14 +14,15 @@ [dependencies] - futures={workspace=true} - horned-owl={workspace=true} - log={workspace=true} - rdf-fusion={workspace=true} + futures.workspace=true + horned-owl.workspace=true + leptos.workspace=true + log.workspace=true + rdf-fusion.workspace=true serde="1.0" - tokio={workspace=true} + tokio.workspace=true tokio-stream="0.1.17" vowlr-util={path="../util"} [dev-dependencies] -env_logger.workspace = true + env_logger.workspace=true diff --git a/crates/parser/src/errors.rs b/crates/parser/src/errors.rs index 96ffc70..4d1aefd 100644 --- a/crates/parser/src/errors.rs +++ b/crates/parser/src/errors.rs @@ -1,6 +1,13 @@ use std::{io::Error, panic::Location}; use horned_owl::error::HornedError; +use leptos::{ + prelude::{FromServerFnError, ServerFnErrorErr}, + server_fn::{ + codec::{JsonEncoding, Rkyv}, + error::IntoAppError, + }, +}; use rdf_fusion::{ error::LoaderError, execution::sparql::error::QueryEvaluationError, @@ -26,6 +33,42 @@ pub struct VOWLRStoreError { location: &'static Location<'static>, } +impl FromServerFnError for VOWLRStoreError { + type Encoder = JsonEncoding; + + fn from_server_fn_error(value: ServerFnErrorErr) -> Self { + match value { + ServerFnErrorErr::Registration(_) => todo!(), + ServerFnErrorErr::UnsupportedRequestMethod(_) => todo!(), + ServerFnErrorErr::Request(_) => todo!(), + ServerFnErrorErr::ServerError(e) => todo!(), + ServerFnErrorErr::MiddlewareError(_) => todo!(), + ServerFnErrorErr::Deserialization(_) => todo!(), + ServerFnErrorErr::Serialization(_) => todo!(), + ServerFnErrorErr::Args(_) => todo!(), + ServerFnErrorErr::MissingArg(_) => todo!(), + ServerFnErrorErr::Response(_) => todo!(), + } + } + + fn ser(&self) -> leptos::server_fn::Bytes { + Self::Encoder::encode(self).unwrap_or_else(|e| { + Self::Encoder::encode(&Self::from_server_fn_error( + ServerFnErrorErr::Serialization(e.to_string()), + )) + .expect( + "error serializing should success at least with the \ + Serialization error", + ) + }) + } + + fn de(data: leptos::server_fn::Bytes) -> Self { + Self::Encoder::decode(data) + .unwrap_or_else(|e| ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()) + } +} + impl From for Error { fn from(val: VOWLRStoreError) -> Self { Error::other(val.to_string()) diff --git a/src/components/user_input/file_upload.rs b/src/components/user_input/file_upload.rs index e58ace8..475842f 100644 --- a/src/components/user_input/file_upload.rs +++ b/src/components/user_input/file_upload.rs @@ -248,8 +248,7 @@ pub async fn handle_internal_sparql( if let QueryResults::Solutions(solutions) = query_stream { solution_serializer .serialize_nodes_stream(&mut data_buffer, solutions) - .await - .map_err(|e| ServerFnError::ServerError(e.to_string()))?; + .await?; } else { return Err(ServerFnError::ServerError( "Query stream is not a solutions stream".to_string(), diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..3ff0c58 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,6 @@ +use leptos::prelude::RwSignal; + +#[derive(Clone)] +pub struct ErrorLogContext { + pub errors: RwSignal>, +} From d61b7066f555d52eea9f7823e211108b8570fdf6 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:26:43 +0000 Subject: [PATCH 02/36] WIP 2 on errors --- Cargo.toml | 1 + crates/parser/Cargo.toml | 1 + crates/parser/src/errors.rs | 2 +- crates/util/Cargo.toml | 4 +++- crates/util/src/error_handler.rs | 39 ++++++++++++++++++++++++++++++++ crates/util/src/lib.rs | 1 + 6 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 crates/util/src/error_handler.rs diff --git a/Cargo.toml b/Cargo.toml index 6d62891..3460579 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ rdf-fusion="0.1.0" rkyv="0.8.12" smallvec={version="1.15.1", features=["union"]} + strum={version="0.27", features=["derive"]} tokio="1.48.0" [dependencies] diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index c804482..058be2d 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -20,6 +20,7 @@ log.workspace=true rdf-fusion.workspace=true serde="1.0" + strum.workspace=true tokio.workspace=true tokio-stream="0.1.17" vowlr-util={path="../util"} diff --git a/crates/parser/src/errors.rs b/crates/parser/src/errors.rs index 4d1aefd..518cf8c 100644 --- a/crates/parser/src/errors.rs +++ b/crates/parser/src/errors.rs @@ -37,7 +37,7 @@ impl FromServerFnError for VOWLRStoreError { type Encoder = JsonEncoding; fn from_server_fn_error(value: ServerFnErrorErr) -> Self { - match value { + match value.inner { ServerFnErrorErr::Registration(_) => todo!(), ServerFnErrorErr::UnsupportedRequestMethod(_) => todo!(), ServerFnErrorErr::Request(_) => todo!(), diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index cb14979..e328b37 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -13,5 +13,7 @@ [dependencies] - rkyv={workspace=true} + leptos.workspace=true + log.workspace=true + rkyv.workspace=true serde={version="1.0", features=["derive"]} diff --git a/crates/util/src/error_handler.rs b/crates/util/src/error_handler.rs new file mode 100644 index 0000000..d7b3905 --- /dev/null +++ b/crates/util/src/error_handler.rs @@ -0,0 +1,39 @@ +/// Logs a server message at the error level. +/// +/// Please use the `target` argument to define the error type. See Examples. +/// +/// Returns a [`leptos::prelude::ServerFnError::ServerError`] +/// +/// # Examples +/// +/// ``` +/// use vowlr_util::error_s; +/// use leptos::prelude::ServerFnError; +/// +/// let (err_info, port) = ("No connection", 22); +/// +/// let s = error_s!("Error: {err_info} on port {port}"); +/// let s1 = error_s!(target: "serializer", "App Error: {err_info}, Port: {port}"); +/// +/// assert_eq!() +/// ``` +#[macro_export] +macro_rules! error_s { + // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // error!(target: "my_target", "a {} event", "log") + (target: $target:expr, $($arg:tt)+) => ({ + $log::error!($($arg)+) + $leptos::prelude::ServerFnError::ServerError( + $std::format_args!($target, $($arg)+) + ) + }); + + // error!(key1 = 42, key2 = true; "a {} event", "log") + // error!("a {} event", "log") + ($($arg:tt)+) => ({ + $log::error!($($arg)+) + $leptos::prelude::ServerFnError::ServerError( + $std::format_args!($($arg)+) + ) + }); +} diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 0d8681d..d720b9a 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -1 +1,2 @@ pub mod datatypes; +pub mod error_handler; From 87f5b2e68f1fbfab0ea944a25a26fda568c935ac Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:18:40 +0000 Subject: [PATCH 03/36] Add custom error types handling --- crates/database/src/errors.rs | 126 ++++++++++++++++ crates/database/src/lib.rs | 99 +----------- crates/database/src/serializers.rs | 7 +- crates/database/src/serializers/frontend.rs | 2 +- crates/database/src/serializers/util.rs | 13 ++ crates/parser/src/errors.rs | 97 ++++++------ crates/parser/src/parser_util.rs | 12 +- crates/util/src/error_handler.rs | 157 ++++++++++++++++++++ crates/util/src/lib.rs | 2 + 9 files changed, 362 insertions(+), 153 deletions(-) create mode 100644 crates/database/src/errors.rs diff --git a/crates/database/src/errors.rs b/crates/database/src/errors.rs new file mode 100644 index 0000000..0019d37 --- /dev/null +++ b/crates/database/src/errors.rs @@ -0,0 +1,126 @@ +use crate::serializers::Triple; +use oxrdf::{BlankNodeIdParseError, IriParseError}; +use vowlr_parser::errors::VOWLRStoreError; +use vowlr_util::prelude::{ErrorRecord, ErrorSeverity, ErrorType}; + +// pub trait SerializationErrorExt { +// fn triple(&self) -> Option<&Triple>; +// } + +// macro_rules! ser_err { +// ($variant:ident($triple:expr, $msg:expr)) => { +// $crate::SerializationErrorKind::$variant(($triple).map(Box::new), $msg) +// }; +// } +// pub(crate) use ser_err; + +// #[derive(Debug)] +// pub enum SerializationErrorKind { +// MissingObject(Option>, String), +// MissingSubject(Option>, String), +// SerializationFailed(Option>, String), +// IriParseError(Option>, Box), +// BlankNodeParseError(Option>, Box), +// } +// impl SerializationErrorExt for SerializationErrorKind { +// fn triple(&self) -> Option<&Triple> { +// match &self { +// SerializationErrorKind::MissingObject(triple, _) +// | SerializationErrorKind::MissingSubject(triple, _) +// | SerializationErrorKind::SerializationFailed(triple, _) +// | SerializationErrorKind::IriParseError(triple, _) +// | SerializationErrorKind::BlankNodeParseError(triple, _) => { +// triple.as_ref().map(|t| &**t) +// } +// } +// } +// } + +#[derive(Debug)] +pub enum SerializationErrorKind { + /// An error raised when the object of a triple is required but missing. + MissingObject(Triple, String), + /// An error raised when the subject of a triple is required but missing. + MissingSubject(Triple, String), + /// An error raised when the serializer encountered an unrecoverable problem. + SerializationFailed(Triple, String), + /// An error raised during Iri or IriRef validation. + IriParseError(Triple, IriParseError), + /// An error raised during BlankNode IDs validation + BlankNodeParseError(Triple, BlankNodeIdParseError), +} + +#[derive(Debug)] +pub struct SerializationError { + inner: SerializationErrorKind, +} +impl std::fmt::Display for SerializationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.inner) + } +} + +// impl SerializationErrorExt for SerializationError { +// fn triple(&self) -> Option<&Triple> { +// self.inner.triple() +// } +// } + +impl From for SerializationError { + fn from(error: SerializationErrorKind) -> Self { + SerializationError { inner: error } + } +} + +// impl From for SerializationError { +// fn from(error: IriParseError) -> Self { +// SerializationError { +// inner: SerializationErrorKind::IriParseError(None, Box::new(error)), +// } +// } +// } + +// impl From for VOWLRStoreError { +// fn from(error: SerializationError) -> Self { +// VOWLRStoreError::from(error.to_string()) +// } +// } + +// impl From for SerializationError { +// fn from(error: BlankNodeIdParseError) -> Self { +// SerializationError { +// inner: SerializationErrorKind::BlankNodeParseError(None, Box::new(error)), +// } +// } +// } + +impl From for ErrorRecord { + fn from(value: SerializationError) -> Self { + let (message, severity) = match value.inner { + SerializationErrorKind::MissingObject(triple, e) => { + (format!("{e}\n{triple}"), ErrorSeverity::Warning) + } + SerializationErrorKind::MissingSubject(triple, e) => { + (format!("{e}\n{triple}"), ErrorSeverity::Warning) + } + SerializationErrorKind::SerializationFailed(triple, e) => { + (format!("{e}\n{triple}"), ErrorSeverity::Critical) + } + SerializationErrorKind::IriParseError(triple, iri_parse_error) => ( + format!("{iri_parse_error}\n{triple}"), + ErrorSeverity::Severe, + ), + SerializationErrorKind::BlankNodeParseError(triple, blank_node_id_parse_error) => ( + format!("{blank_node_id_parse_error}\n{triple}"), + ErrorSeverity::Severe, + ), + }; + ErrorRecord::new( + severity, + ErrorType::Serializer, + message, + #[cfg(debug_assertions)] + "N/A".to_string(), + ) + } +} diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs index b2b1b9c..3e7f2a9 100644 --- a/crates/database/src/lib.rs +++ b/crates/database/src/lib.rs @@ -1,11 +1,4 @@ -use std::fmt::{Display, Formatter}; - -use grapher::prelude::{ElementType, OwlEdge, OwlType, RdfEdge, RdfType}; -use oxrdf::{BlankNodeIdParseError, IriParseError}; -use vowlr_parser::errors::VOWLRStoreError; - -use crate::serializers::Triple; - +mod errors; pub mod serializers; pub mod store; pub mod vocab; @@ -16,93 +9,3 @@ pub mod prelude { pub use crate::store::VOWLRStore; } - -pub const SYMMETRIC_EDGE_TYPES: [ElementType; 1] = - [ElementType::Owl(OwlType::Edge(OwlEdge::DisjointWith))]; - -pub const PROPERTY_EDGE_TYPES: [ElementType; 7] = [ - ElementType::Owl(OwlType::Edge(OwlEdge::ObjectProperty)), - ElementType::Owl(OwlType::Edge(OwlEdge::DatatypeProperty)), - ElementType::Owl(OwlType::Edge(OwlEdge::DeprecatedProperty)), - ElementType::Owl(OwlType::Edge(OwlEdge::ExternalProperty)), - ElementType::Owl(OwlType::Edge(OwlEdge::ValuesFrom)), - ElementType::Owl(OwlType::Edge(OwlEdge::InverseOf)), - ElementType::Rdf(RdfType::Edge(RdfEdge::RdfProperty)), -]; - -pub trait SerializationErrorExt { - fn triple(&self) -> Option<&Triple>; -} - -macro_rules! ser_err { - ($variant:ident($triple:expr, $msg:expr)) => { - $crate::SerializationErrorKind::$variant(($triple).map(Box::new), $msg) - }; -} -pub(crate) use ser_err; - -#[derive(Debug)] -pub enum SerializationErrorKind { - MissingObject(Option>, String), - MissingSubject(Option>, String), - SerializationFailed(Option>, String), - IriParseError(Option>, Box), - BlankNodeParseError(Option>, Box), -} -impl SerializationErrorExt for SerializationErrorKind { - fn triple(&self) -> Option<&Triple> { - match &self { - SerializationErrorKind::MissingObject(triple, _) - | SerializationErrorKind::MissingSubject(triple, _) - | SerializationErrorKind::SerializationFailed(triple, _) - | SerializationErrorKind::IriParseError(triple, _) - | SerializationErrorKind::BlankNodeParseError(triple, _) => { - triple.as_ref().map(|t| &**t) - } - } - } -} - -#[derive(Debug)] -pub struct SerializationError { - inner: SerializationErrorKind, -} -impl Display for SerializationError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "SerializationError: {:?}", self.inner) - } -} - -impl SerializationErrorExt for SerializationError { - fn triple(&self) -> Option<&Triple> { - self.inner.triple() - } -} - -impl From for SerializationError { - fn from(error: SerializationErrorKind) -> Self { - SerializationError { inner: error } - } -} - -impl From for SerializationError { - fn from(error: IriParseError) -> Self { - SerializationError { - inner: SerializationErrorKind::IriParseError(None, Box::new(error)), - } - } -} - -impl From for VOWLRStoreError { - fn from(error: SerializationError) -> Self { - VOWLRStoreError::from(error.to_string()) - } -} - -impl From for SerializationError { - fn from(error: BlankNodeIdParseError) -> Self { - SerializationError { - inner: SerializationErrorKind::BlankNodeParseError(None, Box::new(error)), - } - } -} diff --git a/crates/database/src/serializers.rs b/crates/database/src/serializers.rs index 1fa49c4..308c240 100644 --- a/crates/database/src/serializers.rs +++ b/crates/database/src/serializers.rs @@ -8,7 +8,10 @@ use grapher::prelude::{ElementType, GraphDisplayData, OwlEdge, OwlType}; use log::error; use oxrdf::Term; -use crate::{PROPERTY_EDGE_TYPES, SYMMETRIC_EDGE_TYPES}; +use crate::{ + errors::SerializationError, + serializers::util::{PROPERTY_EDGE_TYPES, SYMMETRIC_EDGE_TYPES}, +}; pub mod frontend; pub mod util; @@ -229,7 +232,7 @@ pub struct SerializationDataBuffer { /// Each element is a tuple of: /// - 0 = The triple (if any). /// - 1 = The reason it failed to serialize (or the message if no triple is available). - failed_buffer: Vec<(Option, String)>, + failed_buffer: Vec, /// The base IRI of the document. /// /// For instance: `http://purl.obolibrary.org/obo/envo.owl` diff --git a/crates/database/src/serializers/frontend.rs b/crates/database/src/serializers/frontend.rs index ddd4a4b..32bd795 100644 --- a/crates/database/src/serializers/frontend.rs +++ b/crates/database/src/serializers/frontend.rs @@ -6,7 +6,7 @@ use std::{ use super::{Edge, SerializationDataBuffer, Triple}; use crate::{ - SerializationError, SerializationErrorExt, ser_err, + SerializationError, SerializationErrorKind, /*SerializationErrorExt, ser_err*/ serializers::util::{get_reserved_iris, trim_tag_circumfix}, vocab::owl, }; diff --git a/crates/database/src/serializers/util.rs b/crates/database/src/serializers/util.rs index d8f5ccc..08e3c0b 100644 --- a/crates/database/src/serializers/util.rs +++ b/crates/database/src/serializers/util.rs @@ -3,6 +3,19 @@ use std::collections::HashSet; use crate::vocab::owl; use rdf_fusion::model::vocab::{rdf, rdfs}; +pub const SYMMETRIC_EDGE_TYPES: [ElementType; 1] = + [ElementType::Owl(OwlType::Edge(OwlEdge::DisjointWith))]; + +pub const PROPERTY_EDGE_TYPES: [ElementType; 7] = [ + ElementType::Owl(OwlType::Edge(OwlEdge::ObjectProperty)), + ElementType::Owl(OwlType::Edge(OwlEdge::DatatypeProperty)), + ElementType::Owl(OwlType::Edge(OwlEdge::DeprecatedProperty)), + ElementType::Owl(OwlType::Edge(OwlEdge::ExternalProperty)), + ElementType::Owl(OwlType::Edge(OwlEdge::ValuesFrom)), + ElementType::Owl(OwlType::Edge(OwlEdge::InverseOf)), + ElementType::Rdf(RdfType::Edge(RdfEdge::RdfProperty)), +]; + /// Reserved IRIs should not be overridden by e.g. "external class" ElementType. pub fn get_reserved_iris() -> HashSet { let rdf = vec![rdf::XML_LITERAL]; diff --git a/crates/parser/src/errors.rs b/crates/parser/src/errors.rs index 518cf8c..4ead104 100644 --- a/crates/parser/src/errors.rs +++ b/crates/parser/src/errors.rs @@ -1,74 +1,45 @@ use std::{io::Error, panic::Location}; use horned_owl::error::HornedError; -use leptos::{ - prelude::{FromServerFnError, ServerFnErrorErr}, - server_fn::{ - codec::{JsonEncoding, Rkyv}, - error::IntoAppError, - }, -}; + use rdf_fusion::{ error::LoaderError, execution::sparql::error::QueryEvaluationError, model::{IriParseError, StorageError}, }; use tokio::task::JoinError; +use vowlr_util::prelude::{ErrorRecord, ErrorSeverity, ErrorType}; #[derive(Debug)] pub enum VOWLRStoreErrorKind { - InvalidInput(String), + /// The file type is not supported by the server. + /// + /// Example: server only supports `.owl` and is given `.png` + InvalidFileType(String), + /// An error raised by Horned-OWL during parsing (of OWL files). HornedError(HornedError), + /// Generic IO error. IOError(std::io::Error), + /// An error raised while trying to parse an invalid IRI. IriParseError(IriParseError), + /// An error raised while loading a file into a Store (database). LoaderError(LoaderError), + /// A SPARQL evaluation error. QueryEvaluationError(QueryEvaluationError), + /// A Tokio task failed to execute to completion. JoinError(JoinError), + /// An error related to (database) storage operations (reads, writes...). StorageError(StorageError), } #[derive(Debug)] pub struct VOWLRStoreError { + /// The contained error type. inner: VOWLRStoreErrorKind, + /// The error's location in the source code. location: &'static Location<'static>, } -impl FromServerFnError for VOWLRStoreError { - type Encoder = JsonEncoding; - - fn from_server_fn_error(value: ServerFnErrorErr) -> Self { - match value.inner { - ServerFnErrorErr::Registration(_) => todo!(), - ServerFnErrorErr::UnsupportedRequestMethod(_) => todo!(), - ServerFnErrorErr::Request(_) => todo!(), - ServerFnErrorErr::ServerError(e) => todo!(), - ServerFnErrorErr::MiddlewareError(_) => todo!(), - ServerFnErrorErr::Deserialization(_) => todo!(), - ServerFnErrorErr::Serialization(_) => todo!(), - ServerFnErrorErr::Args(_) => todo!(), - ServerFnErrorErr::MissingArg(_) => todo!(), - ServerFnErrorErr::Response(_) => todo!(), - } - } - - fn ser(&self) -> leptos::server_fn::Bytes { - Self::Encoder::encode(self).unwrap_or_else(|e| { - Self::Encoder::encode(&Self::from_server_fn_error( - ServerFnErrorErr::Serialization(e.to_string()), - )) - .expect( - "error serializing should success at least with the \ - Serialization error", - ) - }) - } - - fn de(data: leptos::server_fn::Bytes) -> Self { - Self::Encoder::decode(data) - .unwrap_or_else(|e| ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()) - } -} - impl From for Error { fn from(val: VOWLRStoreError) -> Self { Error::other(val.to_string()) @@ -78,7 +49,7 @@ impl From for VOWLRStoreError { #[track_caller] fn from(error: String) -> Self { VOWLRStoreError { - inner: VOWLRStoreErrorKind::InvalidInput(error), + inner: VOWLRStoreErrorKind::InvalidFileType(error), location: Location::caller(), } } @@ -170,7 +141,7 @@ impl std::fmt::Display for VOWLRStoreError { impl std::error::Error for VOWLRStoreError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self.inner { - VOWLRStoreErrorKind::InvalidInput(_) => None, + VOWLRStoreErrorKind::InvalidFileType(_) => None, VOWLRStoreErrorKind::HornedError(e) => Some(e), VOWLRStoreErrorKind::IOError(e) => Some(e), VOWLRStoreErrorKind::IriParseError(e) => Some(e), @@ -181,3 +152,37 @@ impl std::error::Error for VOWLRStoreError { } } } + +impl From for ErrorRecord { + fn from(value: VOWLRStoreError) -> Self { + let (message, error_type) = match value.inner { + VOWLRStoreErrorKind::InvalidFileType(e) => (e, ErrorType::Parser), + VOWLRStoreErrorKind::HornedError(horned_error) => { + (horned_error.to_string(), ErrorType::Parser) + } + VOWLRStoreErrorKind::IOError(error) => (error.to_string(), ErrorType::Generic), + VOWLRStoreErrorKind::IriParseError(iri_parse_error) => { + (iri_parse_error.to_string(), ErrorType::Parser) + } + VOWLRStoreErrorKind::LoaderError(loader_error) => { + (loader_error.to_string(), ErrorType::Database) + } + VOWLRStoreErrorKind::QueryEvaluationError(query_evaluation_error) => { + (query_evaluation_error.to_string(), ErrorType::Database) + } + VOWLRStoreErrorKind::JoinError(join_error) => { + (join_error.to_string(), ErrorType::Generic) + } + VOWLRStoreErrorKind::StorageError(storage_error) => { + (storage_error.to_string(), ErrorType::Database) + } + }; + ErrorRecord::new( + ErrorSeverity::Critical, + error_type, + message, + #[cfg(debug_assertions)] + value.location.to_string(), + ) + } +} diff --git a/crates/parser/src/parser_util.rs b/crates/parser/src/parser_util.rs index 18b9877..68fee29 100644 --- a/crates/parser/src/parser_util.rs +++ b/crates/parser/src/parser_util.rs @@ -87,7 +87,7 @@ pub async fn parse_stream_to( let mut buf = Vec::new(); let mut serializer = RdfSerializer::from_format(format_from_resource_type(&DataType::OWL).ok_or( - VOWLRStoreErrorKind::InvalidInput(format!( + VOWLRStoreErrorKind::InvalidFileType(format!( "Unsupported output type: {:?}", output_type )), @@ -123,7 +123,7 @@ pub async fn parse_stream_to( writer.flush()?; Ok(writer) } - _ => Err(VOWLRStoreError::from(VOWLRStoreErrorKind::InvalidInput( + _ => Err(VOWLRStoreError::from(VOWLRStoreErrorKind::InvalidFileType( format!("Unsupported output type: {:?}", output_type), ))), })(); @@ -143,7 +143,7 @@ pub async fn parse_stream_to( let result = async { let mut serializer = RdfSerializer::from_format(format_from_resource_type(&output_type).ok_or( - VOWLRStoreErrorKind::InvalidInput(format!( + VOWLRStoreErrorKind::InvalidFileType(format!( "Unsupported output type: {:?}", output_type )), @@ -186,7 +186,7 @@ pub fn parser_from_reader( }; let Some(format) = path_type(path) else { - return Err(VOWLRStoreErrorKind::InvalidInput(format!( + return Err(VOWLRStoreErrorKind::InvalidFileType(format!( "Unsupported format {}", path.display() )) @@ -313,14 +313,14 @@ pub fn parser_from_reader( reader.read_to_end(&mut input)?; let input = ParserInput::File(input); let format = format_from_resource_type(&f).ok_or_else(|| { - VOWLRStoreErrorKind::InvalidInput(format!("could not convert {f:?} to format")) + VOWLRStoreErrorKind::InvalidFileType(format!("could not convert {f:?} to format")) })?; Ok(PreparedParser { parser: make_parser(format), input, }) } - _ => Err(VOWLRStoreErrorKind::InvalidInput(format!( + _ => Err(VOWLRStoreErrorKind::InvalidFileType(format!( "Unsupported parser: {}", format.mime_type() ))), diff --git a/crates/util/src/error_handler.rs b/crates/util/src/error_handler.rs index d7b3905..717e0cd 100644 --- a/crates/util/src/error_handler.rs +++ b/crates/util/src/error_handler.rs @@ -1,3 +1,13 @@ +// Use this to implement (de)encoding error types: +// Encode: https://github.com/leptos-rs/leptos/blob/6dc8ad4bfa71c33bf67aac514204af5dcaf7a112/server_fn/src/error.rs#L264 +// Decode: https://github.com/leptos-rs/leptos/blob/6dc8ad4bfa71c33bf67aac514204af5dcaf7a112/server_fn/src/error.rs#L306 + +use leptos::{ + prelude::{FromServerFnError, ServerFnError, ServerFnErrorErr}, + server_fn::{Decodes, Encodes, codec::RkyvEncoding, error::IntoAppError}, +}; +use serde::{Deserialize, Serialize}; + /// Logs a server message at the error level. /// /// Please use the `target` argument to define the error type. See Examples. @@ -37,3 +47,150 @@ macro_rules! error_s { ) }); } + +#[derive( + Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, +)] +pub enum ErrorSeverity { + Critical, + Severe, + Medium, + Low, + Warning, + Unset, +} + +impl std::fmt::Display for ErrorSeverity { + fn fmt(&self, f: &mut std::fmt::Formatter<'e>) -> std::fmt::Result { + match self { + Self::Critical => write!( + f, + "an unrecoverable error which makes VOWL-R unusable (do not use the output of VOWL-R!)" + ), + Self::Severe => write!( + f, + "an error which highly disrupts the user experience (the output of VOWL-R is likely incorrect)" + ), + Self::Medium => write!(f, "error desc goes here"), + Self::Low => write!( + f, + "error desc goes here (part of the output of VOWL-R could be incorrect, but should be \"insignificant\")" + ), + Self::Warning => write!( + f, + "something happened which may reduce the user experience (but can otherwise be ignored)" + ), + Self::Unset => write!(f, "unknown severity"), + } + } +} + +#[derive( + Debug, + Copy, + Clone, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, + Serialize, + Deserialize, + strum::Display, +)] +pub enum ErrorType { + /// Errors related to database operations. + Database, + /// Errors related to serializing data from backend to frontend (server -> client). + Serializer, + /// Errors related to parsing data (e.g. a `.owl` file). + Parser, + /// Errors related to the graph renderer (i.e. WasmGrapher) + Renderer, + #[strum(serialize = "GUI")] + /// Errors related to the frontend GUI. + Gui, + /// Errors without a type. Equivalent to a "500 Internal Server Error" + Generic, +} + +#[derive( + Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, +)] +pub struct ErrorRecord { + /// The severity of an error. + /// + /// Useful for grouping errors by severity and applying custom color schemes in the GUI. + pub severity: ErrorSeverity, + /// The type of an error. + /// + /// Useful for grouping errors by type and debugging for devs. + pub error_type: ErrorType, + /// The actual error message to show. + pub message: String, + + #[cfg(debug_assertions)] + /// The location in the source code where the error originated. + /// + /// Only enabled with [cfg.debug_assertions] + pub location: String, +} + +impl ErrorRecord { + pub fn new( + severity: ErrorSeverity, + error_type: ErrorType, + message: String, + #[cfg(debug_assertions)] location: String, + ) -> Self { + Self { + severity, + error_type, + message, + #[cfg(debug_assertions)] + location, + } + } +} + +#[derive( + Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, +)] +pub struct VOWLRServerError { + pub records: Vec, +} + +impl FromServerFnError for ServerFnError { + type Encoder = RkyvEncoding; + + fn from_server_fn_error(value: ServerFnErrorErr) -> Self { + let (severity, error_type, message, location) = match value { + ServerFnErrorErr::Registration(e) => todo!(), + ServerFnErrorErr::UnsupportedRequestMethod(e) => todo!(), + ServerFnErrorErr::Request(e) => todo!(), + ServerFnErrorErr::ServerError(e) => todo!(), + ServerFnErrorErr::MiddlewareError(e) => todo!(), + ServerFnErrorErr::Deserialization(e) => todo!(), + ServerFnErrorErr::Serialization(e) => todo!(), + ServerFnErrorErr::Args(e) => todo!(), + ServerFnErrorErr::MissingArg(e) => todo!(), + ServerFnErrorErr::Response(e) => todo!(), + } + + } + + fn ser(&self) -> leptos::server_fn::Bytes { + Self::Encoder::encode(self).unwrap_or_else(|e| { + Self::Encoder::encode(&Self::from_server_fn_error( + ServerFnErrorErr::Serialization(e.to_string()), + )) + .expect( + "error serializing should success at least with the \ + Serialization error", + ) + }) + } + + fn de(data: leptos::server_fn::Bytes) -> Self { + Self::Encoder::decode(data) + .unwrap_or_else(|e| ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()) + } +} diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index b698f65..f0920e0 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -1,5 +1,7 @@ mod datatypes; +mod error_handler; pub mod prelude { pub use crate::datatypes::DataType; + pub use crate::error_handler::{ErrorRecord, ErrorSeverity, ErrorType}; } From 8fd4d9ff0cc7423ca1e43b8edbb21513d3cc3e88 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:18:46 +0000 Subject: [PATCH 04/36] Add strum --- crates/util/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 5a3df5d..0f39188 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -17,3 +17,4 @@ log.workspace=true rkyv.workspace=true serde={version="1.0", features=["derive"]} + strum.workspace=true From 92852582f4fcb5cbdfb448708f0262026a99b236 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:18:53 +0000 Subject: [PATCH 05/36] Upate Cargo.lock --- Cargo.lock | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index e6d6ed7..0579415 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7521,9 +7521,11 @@ dependencies = [ "env_logger", "futures", "horned-owl", + "leptos", "log", "rdf-fusion", "serde", + "strum 0.27.2", "tokio", "tokio-stream", "vowlr-util", @@ -7542,8 +7544,11 @@ dependencies = [ name = "vowlr-util" version = "0.1.0" dependencies = [ + "leptos", + "log", "rkyv", "serde", + "strum 0.27.2", ] [[package]] From 4f39734f1cbe4afc2ddf0c76c7557a2f429c287c Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:49:45 +0100 Subject: [PATCH 06/36] Update Cargo.lock --- Cargo.lock | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0579415..dab2846 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -913,6 +913,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "bytemuck" version = "1.25.0" @@ -4906,6 +4912,17 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "papergrid" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6978128c8b51d8f4080631ceb2302ab51e32cc6e8615f735ee2f83fd269ae3f1" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + [[package]] name = "parking" version = "2.2.1" @@ -6796,6 +6813,30 @@ dependencies = [ "libc", ] +[[package]] +name = "tabled" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e39a2ee1fbcd360805a771e1b300f78cc88fec7b8d3e2f71cd37bbf23e725c7d" +dependencies = [ + "papergrid", + "tabled_derive", + "testing_table", +] + +[[package]] +name = "tabled_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea5d1b13ca6cff1f9231ffd62f15eefd72543dab5e468735f1a456728a02846" +dependencies = [ + "heck", + "proc-macro-error2", + "proc-macro2 1.0.106", + "quote 1.0.44", + "syn 2.0.117", +] + [[package]] name = "tachys" version = "0.2.13" @@ -6862,6 +6903,15 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "testing_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -7549,6 +7599,7 @@ dependencies = [ "rkyv", "serde", "strum 0.27.2", + "tabled", ] [[package]] From bc4bd1e42044e1eaaf3b50d0feb13e190c05d3c3 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:55:29 +0100 Subject: [PATCH 07/36] Implement conversion functionality between other error types, ErrorRecords, and ServerFnError --- crates/util/src/error_handler.rs | 284 ++++++++++++++++++++++--------- 1 file changed, 207 insertions(+), 77 deletions(-) diff --git a/crates/util/src/error_handler.rs b/crates/util/src/error_handler.rs index 717e0cd..3e3a5a7 100644 --- a/crates/util/src/error_handler.rs +++ b/crates/util/src/error_handler.rs @@ -1,56 +1,32 @@ -// Use this to implement (de)encoding error types: -// Encode: https://github.com/leptos-rs/leptos/blob/6dc8ad4bfa71c33bf67aac514204af5dcaf7a112/server_fn/src/error.rs#L264 -// Decode: https://github.com/leptos-rs/leptos/blob/6dc8ad4bfa71c33bf67aac514204af5dcaf7a112/server_fn/src/error.rs#L306 +#[cfg(not(feature = "server"))] +use std::fmt::Write; use leptos::{ - prelude::{FromServerFnError, ServerFnError, ServerFnErrorErr}, + prelude::{FromServerFnError, IntoView, ServerFnError, ServerFnErrorErr}, server_fn::{Decodes, Encodes, codec::RkyvEncoding, error::IntoAppError}, }; + +// use leptos_struct_table::*; use serde::{Deserialize, Serialize}; -/// Logs a server message at the error level. -/// -/// Please use the `target` argument to define the error type. See Examples. -/// -/// Returns a [`leptos::prelude::ServerFnError::ServerError`] -/// -/// # Examples -/// -/// ``` -/// use vowlr_util::error_s; -/// use leptos::prelude::ServerFnError; -/// -/// let (err_info, port) = ("No connection", 22); -/// -/// let s = error_s!("Error: {err_info} on port {port}"); -/// let s1 = error_s!(target: "serializer", "App Error: {err_info}, Port: {port}"); -/// -/// assert_eq!() -/// ``` -#[macro_export] -macro_rules! error_s { - // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") - // error!(target: "my_target", "a {} event", "log") - (target: $target:expr, $($arg:tt)+) => ({ - $log::error!($($arg)+) - $leptos::prelude::ServerFnError::ServerError( - $std::format_args!($target, $($arg)+) - ) - }); - - // error!(key1 = 42, key2 = true; "a {} event", "log") - // error!("a {} event", "log") - ($($arg:tt)+) => ({ - $log::error!($($arg)+) - $leptos::prelude::ServerFnError::ServerError( - $std::format_args!($($arg)+) - ) - }); -} +#[cfg(feature = "server")] +use tabled::{ + Table, Tabled, + settings::{Settings, Style}, +}; #[derive( - Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, + Debug, + Copy, + Clone, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, + Serialize, + Deserialize, + strum::Display, )] +#[strum(serialize_all = "title_case")] pub enum ErrorSeverity { Critical, Severe, @@ -60,31 +36,39 @@ pub enum ErrorSeverity { Unset, } -impl std::fmt::Display for ErrorSeverity { - fn fmt(&self, f: &mut std::fmt::Formatter<'e>) -> std::fmt::Result { +impl ErrorSeverity { + // TODO: Work in progress. Pls don't remove format! + pub fn description(&self) -> String { match self { - Self::Critical => write!( - f, + Self::Critical => format!( "an unrecoverable error which makes VOWL-R unusable (do not use the output of VOWL-R!)" ), - Self::Severe => write!( - f, + Self::Severe => format!( "an error which highly disrupts the user experience (the output of VOWL-R is likely incorrect)" ), - Self::Medium => write!(f, "error desc goes here"), - Self::Low => write!( - f, + // TODO + Self::Medium => format!("error desc goes here"), + // TODO + Self::Low => format!( "error desc goes here (part of the output of VOWL-R could be incorrect, but should be \"insignificant\")" ), - Self::Warning => write!( - f, + Self::Warning => format!( "something happened which may reduce the user experience (but can otherwise be ignored)" ), - Self::Unset => write!(f, "unknown severity"), + Self::Unset => format!("unknown severity"), } } } +// impl CellValue for ErrorSeverity { +// type RenderOptions = (); + +// // #[allow(unused)] +// fn render_value(self, _options: Self::RenderOptions) -> impl IntoView { +// self.to_string() +// } +// } + #[derive( Debug, Copy, @@ -108,13 +92,39 @@ pub enum ErrorType { #[strum(serialize = "GUI")] /// Errors related to the frontend GUI. Gui, - /// Errors without a type. Equivalent to a "500 Internal Server Error" - Generic, + /// Server errors without a type. + InternalServerError, + /// Client errors without a type. + ClientError, } +// impl CellValue for ErrorType { +// type RenderOptions = (); + +// fn render_value(self, _options: Self::RenderOptions) -> impl IntoView { +// self.to_string() +// } +// } + #[derive( - Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, + Debug, + Clone, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, + Serialize, + Deserialize, + // TableRow, )] +#[cfg_attr(feature = "server", derive(Tabled))] +// #[table(sortable, classes_provider = "TailwindClassesPreset")] +// TODO: implement a leptos struct table looking like: https://datatables.net/ +/// The fundamental building block of the error handling system. +/// +/// It stores the data of a single error event. +/// +/// # Note +/// Every error type in use should implement [`From for ErrorRecord`]. pub struct ErrorRecord { /// The severity of an error. /// @@ -149,32 +159,99 @@ impl ErrorRecord { location, } } + + #[cfg(feature = "server")] + /// Only available on the server. + pub fn format_records(records: &[ErrorRecord]) -> String { + let table_config = Settings::default().with(Style::modern_rounded()); + Table::new(records).with(table_config).to_string() + } +} + +impl From for ErrorRecord { + fn from(value: ServerFnError) -> Self { + let (error_type, message) = match value { + ServerFnError::WrappedServerError(_) => ( + // TODO: Remove in Leptos v0.9 + ErrorType::InternalServerError, + "deprecated WrappedServerError".to_string(), + ), + ServerFnError::Registration(e) => (ErrorType::InternalServerError, e), + ServerFnError::Request(e) => (ErrorType::ClientError, e), + ServerFnError::Response(e) => (ErrorType::InternalServerError, e), + ServerFnError::ServerError(e) => (ErrorType::InternalServerError, e), + ServerFnError::MiddlewareError(e) => (ErrorType::InternalServerError, e), + ServerFnError::Deserialization(e) => (ErrorType::ClientError, e), + ServerFnError::Serialization(e) => (ErrorType::ClientError, e), + ServerFnError::Args(e) => (ErrorType::InternalServerError, e), + ServerFnError::MissingArg(e) => (ErrorType::InternalServerError, e), + }; + + ErrorRecord::new( + ErrorSeverity::Unset, + error_type, + message, + #[cfg(debug_assertions)] + "N/A".to_string(), + ) + } +} + +impl From for ErrorRecord { + fn from(value: ServerFnErrorErr) -> Self { + let a: ServerFnError = value.into(); + a.into() + } +} + +#[cfg(feature = "server")] +impl std::fmt::Display for ErrorRecord { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let table_config = Settings::default().with(Style::modern_rounded()); + let table = Table::new([self]).with(table_config).to_string(); + write!(f, "{}", table) + } +} + +#[cfg(not(feature = "server"))] +impl std::fmt::Display for ErrorRecord { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[cfg(debug_assertions)] + { + write!( + f, + "{} | {} | {} | {}", + self.severity, self.error_type, self.message, self.location + ) + } + + #[cfg(not(debug_assertions))] + { + write!( + f, + "{} | {} | {}", + self.severity, self.error_type, self.message + ) + } + } } #[derive( Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, )] +/// The struct sent by the server when things go south. +/// +/// # Note +/// Every error type in use should implement [`From for VOWLRServerError`]. pub struct VOWLRServerError { pub records: Vec, } -impl FromServerFnError for ServerFnError { +impl FromServerFnError for VOWLRServerError { type Encoder = RkyvEncoding; fn from_server_fn_error(value: ServerFnErrorErr) -> Self { - let (severity, error_type, message, location) = match value { - ServerFnErrorErr::Registration(e) => todo!(), - ServerFnErrorErr::UnsupportedRequestMethod(e) => todo!(), - ServerFnErrorErr::Request(e) => todo!(), - ServerFnErrorErr::ServerError(e) => todo!(), - ServerFnErrorErr::MiddlewareError(e) => todo!(), - ServerFnErrorErr::Deserialization(e) => todo!(), - ServerFnErrorErr::Serialization(e) => todo!(), - ServerFnErrorErr::Args(e) => todo!(), - ServerFnErrorErr::MissingArg(e) => todo!(), - ServerFnErrorErr::Response(e) => todo!(), - } - + value.into() } fn ser(&self) -> leptos::server_fn::Bytes { @@ -182,10 +259,7 @@ impl FromServerFnError for ServerFnError { Self::Encoder::encode(&Self::from_server_fn_error( ServerFnErrorErr::Serialization(e.to_string()), )) - .expect( - "error serializing should success at least with the \ - Serialization error", - ) + .expect("serializing should at least succeed with the serialization error type") }) } @@ -194,3 +268,59 @@ impl FromServerFnError for ServerFnError { .unwrap_or_else(|e| ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()) } } + +impl From for VOWLRServerError { + fn from(value: ServerFnError) -> Self { + let record: ErrorRecord = value.into(); + record.into() + } +} + +impl From for VOWLRServerError { + fn from(value: ServerFnErrorErr) -> Self { + let record: ErrorRecord = value.into(); + record.into() + } +} + +impl From for VOWLRServerError { + fn from(value: ErrorRecord) -> Self { + VOWLRServerError { + records: vec![value], + } + } +} + +impl From> for VOWLRServerError { + fn from(value: Vec) -> Self { + VOWLRServerError { records: value } + } +} + +#[cfg(feature = "server")] +impl std::fmt::Display for VOWLRServerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", ErrorRecord::format_records(&self.records)) + } +} + +#[cfg(not(feature = "server"))] +impl std::fmt::Display for VOWLRServerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[cfg(debug_assertions)] + { + writeln!(f, "Severity | Error Type | Message | Location")?; + } + + #[cfg(not(debug_assertions))] + { + writeln!(f, "Severity | Error Type | Message")?; + } + + let mut buffer = String::new(); + for record in self.records.iter() { + writeln!(buffer, "{}", record)?; + } + write!(f, "{buffer}") + } +} From 854922927aad288537879194ba65bcc3950722c1 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:57:20 +0100 Subject: [PATCH 08/36] Fix Leptos version and test Leptos table gen --- Cargo.toml | 13 ++++++++----- crates/util/Cargo.toml | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 061aad9..64ff1c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,8 @@ "webgl", ], git="https://github.com/WebVOWL/WasmGrapher"} horned-owl={git="https://github.com/phillord/horned-owl", rev="c4f7835ecae70b5e7281dc23b9b7fc6daa497f1d"} - leptos={version="0.8.8", features=["nightly", "multipart", "rkyv"]} + leptos={version="0.8.16", features=["nightly", "multipart", "rkyv"]} + # leptos-struct-table={version="0.18.0"} log="0.4" rdf-fusion="0.1.0" rkyv="0.8.12" @@ -69,16 +70,17 @@ futures={workspace=true} getrandom={version="0.3", features=["wasm_js"]} gloo-timers="0.2" - grapher={workspace=true} + grapher.workspace=true http={version="1.3.1", optional=true} - leptos={version="0.8.16", features=["nightly", "multipart", "rkyv"]} + leptos.workspace=true + # leptos-struct-table.workspace=true leptos_actix={version="0.8.7", optional=true} leptos_meta={version="0.8.6"} leptos_router={version="0.8.12", features=["nightly"]} - log={workspace=true} + log.workspace=true rayon="1.10" reqwest={version="0.12.24", optional=true, features=["json", "stream"]} - rkyv={workspace=true} + rkyv.workspace=true tokio={workspace=true, optional=true} vowlr-database={path="crates/database", optional=true} vowlr-parser={path="crates/parser", optional=true} @@ -129,6 +131,7 @@ "dep:vowlr-database", "dep:vowlr-parser", "leptos-use/actix", + "vowlr-util/server", ] ssr=[ "dep:actix-files", diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 0f39188..6119334 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -14,6 +14,7 @@ [dependencies] leptos.workspace=true + # leptos-struct-table.workspace=true log.workspace=true rkyv.workspace=true serde={version="1.0", features=["derive"]} From 4162439614641082925e731afb4880b920a825d1 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:58:03 +0100 Subject: [PATCH 09/36] Fix missing imports --- crates/database/src/serializers/util.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/database/src/serializers/util.rs b/crates/database/src/serializers/util.rs index 08e3c0b..d940a92 100644 --- a/crates/database/src/serializers/util.rs +++ b/crates/database/src/serializers/util.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use crate::vocab::owl; +use grapher::prelude::{ElementType, OwlEdge, OwlType, RdfEdge, RdfType}; use rdf_fusion::model::vocab::{rdf, rdfs}; pub const SYMMETRIC_EDGE_TYPES: [ElementType; 1] = From 654b2db06a85417a7e2bf24360a0734c1ed225bd Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:59:44 +0100 Subject: [PATCH 10/36] Remove old error code --- crates/database/src/errors.rs | 61 --------------------- crates/database/src/serializers/frontend.rs | 2 +- 2 files changed, 1 insertion(+), 62 deletions(-) diff --git a/crates/database/src/errors.rs b/crates/database/src/errors.rs index 0019d37..1d669a6 100644 --- a/crates/database/src/errors.rs +++ b/crates/database/src/errors.rs @@ -3,39 +3,6 @@ use oxrdf::{BlankNodeIdParseError, IriParseError}; use vowlr_parser::errors::VOWLRStoreError; use vowlr_util::prelude::{ErrorRecord, ErrorSeverity, ErrorType}; -// pub trait SerializationErrorExt { -// fn triple(&self) -> Option<&Triple>; -// } - -// macro_rules! ser_err { -// ($variant:ident($triple:expr, $msg:expr)) => { -// $crate::SerializationErrorKind::$variant(($triple).map(Box::new), $msg) -// }; -// } -// pub(crate) use ser_err; - -// #[derive(Debug)] -// pub enum SerializationErrorKind { -// MissingObject(Option>, String), -// MissingSubject(Option>, String), -// SerializationFailed(Option>, String), -// IriParseError(Option>, Box), -// BlankNodeParseError(Option>, Box), -// } -// impl SerializationErrorExt for SerializationErrorKind { -// fn triple(&self) -> Option<&Triple> { -// match &self { -// SerializationErrorKind::MissingObject(triple, _) -// | SerializationErrorKind::MissingSubject(triple, _) -// | SerializationErrorKind::SerializationFailed(triple, _) -// | SerializationErrorKind::IriParseError(triple, _) -// | SerializationErrorKind::BlankNodeParseError(triple, _) => { -// triple.as_ref().map(|t| &**t) -// } -// } -// } -// } - #[derive(Debug)] pub enum SerializationErrorKind { /// An error raised when the object of a triple is required but missing. @@ -60,40 +27,12 @@ impl std::fmt::Display for SerializationError { } } -// impl SerializationErrorExt for SerializationError { -// fn triple(&self) -> Option<&Triple> { -// self.inner.triple() -// } -// } - impl From for SerializationError { fn from(error: SerializationErrorKind) -> Self { SerializationError { inner: error } } } -// impl From for SerializationError { -// fn from(error: IriParseError) -> Self { -// SerializationError { -// inner: SerializationErrorKind::IriParseError(None, Box::new(error)), -// } -// } -// } - -// impl From for VOWLRStoreError { -// fn from(error: SerializationError) -> Self { -// VOWLRStoreError::from(error.to_string()) -// } -// } - -// impl From for SerializationError { -// fn from(error: BlankNodeIdParseError) -> Self { -// SerializationError { -// inner: SerializationErrorKind::BlankNodeParseError(None, Box::new(error)), -// } -// } -// } - impl From for ErrorRecord { fn from(value: SerializationError) -> Self { let (message, severity) = match value.inner { diff --git a/crates/database/src/serializers/frontend.rs b/crates/database/src/serializers/frontend.rs index 32bd795..67d1898 100644 --- a/crates/database/src/serializers/frontend.rs +++ b/crates/database/src/serializers/frontend.rs @@ -6,7 +6,7 @@ use std::{ use super::{Edge, SerializationDataBuffer, Triple}; use crate::{ - SerializationError, SerializationErrorKind, /*SerializationErrorExt, ser_err*/ + errors::{SerializationError, SerializationErrorKind}, serializers::util::{get_reserved_iris, trim_tag_circumfix}, vocab::owl, }; From 40e921d3598bf036574709567d43a3946d3f51f1 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 02:00:29 +0100 Subject: [PATCH 11/36] Fix: parse errors do not have access to a triple --- crates/database/src/errors.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/database/src/errors.rs b/crates/database/src/errors.rs index 1d669a6..20ad39a 100644 --- a/crates/database/src/errors.rs +++ b/crates/database/src/errors.rs @@ -12,9 +12,9 @@ pub enum SerializationErrorKind { /// An error raised when the serializer encountered an unrecoverable problem. SerializationFailed(Triple, String), /// An error raised during Iri or IriRef validation. - IriParseError(Triple, IriParseError), + IriParseError(String, IriParseError), /// An error raised during BlankNode IDs validation - BlankNodeParseError(Triple, BlankNodeIdParseError), + BlankNodeParseError(String, BlankNodeIdParseError), } #[derive(Debug)] From be93538ddbe0496ea15217cdcc8e55ea56b7ffdd Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 02:01:40 +0100 Subject: [PATCH 12/36] Use new error handling backend --- crates/database/src/serializers.rs | 31 +- crates/database/src/serializers/frontend.rs | 374 +++++++++++--------- 2 files changed, 206 insertions(+), 199 deletions(-) diff --git a/crates/database/src/serializers.rs b/crates/database/src/serializers.rs index 308c240..a2aee08 100644 --- a/crates/database/src/serializers.rs +++ b/crates/database/src/serializers.rs @@ -7,11 +7,9 @@ use std::{ use grapher::prelude::{ElementType, GraphDisplayData, OwlEdge, OwlType}; use log::error; use oxrdf::Term; +use vowlr_util::prelude::ErrorRecord; -use crate::{ - errors::SerializationError, - serializers::util::{PROPERTY_EDGE_TYPES, SYMMETRIC_EDGE_TYPES}, -}; +use crate::serializers::util::{PROPERTY_EDGE_TYPES, SYMMETRIC_EDGE_TYPES}; pub mod frontend; pub mod util; @@ -224,15 +222,8 @@ pub struct SerializationDataBuffer { /// can be either the subject, object or both (in this case, subject is used) /// - Value = The unresolved triples. unknown_buffer: HashMap>, - /// Stores triples that are impossible to serialize. - /// - /// This could be caused by various reasons, such as - /// visualization of the triple is not supported. - /// - /// Each element is a tuple of: - /// - 0 = The triple (if any). - /// - 1 = The reason it failed to serialize (or the message if no triple is available). - failed_buffer: Vec, + /// Stores errors encountered during serialization. + failed_buffer: Vec, /// The base IRI of the document. /// /// For instance: `http://purl.obolibrary.org/obo/envo.owl` @@ -412,17 +403,9 @@ impl Display for SerializationDataBuffer { .join("\n") )?; } - writeln!(f, "\tfailed_buffer:")?; - for (triple, reason) in self.failed_buffer.iter() { - match triple { - Some(triple) => { - writeln!(f, "\t\t{} : {}", triple, reason)?; - } - None => { - writeln!(f, "\t\tNO TRIPLE : {}", reason)?; - } - } - } + // Not needed as it's displayed by the serializer + // writeln!(f, "\tfailed_buffer:")?; + // writeln!(f, "{}", ErrorRecord::format_records(&self.failed_buffer))?; writeln!(f, "}}") } } diff --git a/crates/database/src/serializers/frontend.rs b/crates/database/src/serializers/frontend.rs index 67d1898..052bf29 100644 --- a/crates/database/src/serializers/frontend.rs +++ b/crates/database/src/serializers/frontend.rs @@ -25,6 +25,7 @@ use rdf_fusion::{ }, }; use vowlr_parser::errors::VOWLRStoreError; +use vowlr_util::prelude::VOWLRServerError; pub struct GraphDisplayDataSolutionSerializer { pub resolvable_iris: HashSet, @@ -41,13 +42,20 @@ impl GraphDisplayDataSolutionSerializer { &self, data: &mut GraphDisplayData, mut solution_stream: QuerySolutionStream, - ) -> Result<(), VOWLRStoreError> { + ) -> Result<(), VOWLRServerError> { let mut count: u32 = 0; info!("Serializing query solution stream..."); let start_time = Instant::now(); let mut data_buffer = SerializationDataBuffer::new(); - while let Some(solution) = solution_stream.next().await { - let solution = solution?; + while let Some(maybe_solution) = solution_stream.next().await { + let solution = match maybe_solution { + Ok(solution) => solution, + Err(e) => { + let a: VOWLRStoreError = e.into(); + data_buffer.failed_buffer.push(a.into()); + continue; + } + }; let Some(id_term) = solution.get("id") else { continue; }; @@ -62,17 +70,18 @@ impl GraphDisplayDataSolutionSerializer { element_type: node_type_term.to_owned(), target: solution.get("target").map(|term| term.to_owned()), }; - match self.write_node_triple(&mut data_buffer, triple) { - Ok(_) => (), - Err(e) => { - data_buffer - .failed_buffer - .push((e.inner.triple().cloned(), e.to_string())); - } - } + + self.write_node_triple(&mut data_buffer, triple) + .or_else(|e| { + data_buffer.failed_buffer.push(e.into()); + Ok::<(), VOWLRServerError>(()) + })?; count += 1; } - self.check_all_unknowns(&mut data_buffer); + self.check_all_unknowns(&mut data_buffer).or_else(|e| { + data_buffer.failed_buffer.push(e.into()); + Ok::<(), VOWLRServerError>(()) + })?; let finish_time = Instant::now() .checked_duration_since(start_time) @@ -91,29 +100,16 @@ impl GraphDisplayDataSolutionSerializer { data_buffer.node_element_buffer.len(), data_buffer.edge_buffer.len(), data_buffer.label_buffer.len(), + 0, data_buffer.edge_characteristics.len() + data_buffer.node_characteristics.len(), - 0 ); + debug!("{}", data_buffer); if !data_buffer.failed_buffer.is_empty() { let total = data_buffer.failed_buffer.len(); - - let mut error_log = String::from("[\n"); - for (triple, reason) in data_buffer.failed_buffer.iter() { - match triple { - Some(t) => error_log.push_str(&format!("\t{} : {}\n", t, reason)), - None => error_log.push_str(&format!("\tNO TRIPLE : {}\n", reason)), - } - } - error_log.push(']'); - error!("Failed to serialize: {}", error_log); - - return Err(VOWLRStoreError::from(format!( - "Serialization failed ({} errors): {}", - total, error_log - ))); + let err = data_buffer.failed_buffer.into(); + error!("Failed to serialize {} triples:\n{}", total, err); + return Err(err); } - - debug!("{}", data_buffer); *data = data_buffer.into(); debug!("{}", data); Ok(()) @@ -487,31 +483,39 @@ impl GraphDisplayDataSolutionSerializer { } } - fn create_node( + fn create_named_node(&self, iri: String) -> Result { + Ok(NamedNode::new(&iri).map_err(|e| SerializationErrorKind::IriParseError(iri, e))?) + } + + fn create_blank_node(&self, id: String) -> Result { + Ok(BlankNode::new(&id).map_err(|e| SerializationErrorKind::BlankNodeParseError(id, e))?) + } + + fn create_triple( &self, id: String, - node_type: NamedNode, + element_type: NamedNode, object_iri: Option, ) -> Result { let subject = match NamedNode::new(id.clone()) { Ok(node) => Term::NamedNode(node), - Err(_) => Term::BlankNode(BlankNode::new(id)?), + Err(_) => Term::BlankNode(self.create_blank_node(id)?), }; let object = match object_iri { - Some(iri) => { - let obj = NamedNode::new(iri)?; - Some(Term::NamedNode(obj)) - } + Some(iri) => Some(Term::NamedNode(self.create_named_node(iri)?)), None => None, }; - let t = Triple::new(subject, Term::NamedNode(node_type), object); + let t = Triple::new(subject, Term::NamedNode(element_type), object); debug!("Created new triple: {}", t); Ok(t) } - fn check_all_unknowns(&self, data_buffer: &mut SerializationDataBuffer) { + fn check_all_unknowns( + &self, + data_buffer: &mut SerializationDataBuffer, + ) -> Result<(), SerializationError> { info!("Second pass: Resolving all possible unknowns"); let unknown_nodes = take(&mut data_buffer.unknown_buffer); @@ -524,33 +528,21 @@ impl GraphDisplayDataSolutionSerializer { // dummy triple, only subject matters. let external_triple = Triple::new( term, - Term::BlankNode(BlankNode::new("_:external_class").unwrap()), + Term::BlankNode(self.create_blank_node("_:external_class".to_string())?), None, ); - match self.insert_node( + + self.insert_node( data_buffer, &external_triple, ElementType::Owl(OwlType::Node(OwlNode::ExternalClass)), - ) { - Ok(_) => (), - Err(e) => { - data_buffer - .failed_buffer - .push((e.inner.triple().cloned(), e.to_string())); - } - } + )?; } for triple in triples { - match self.write_node_triple(data_buffer, triple) { - Ok(_) => (), - Err(e) => { - data_buffer - .failed_buffer - .push((e.inner.triple().cloned(), e.to_string())); - } - } + self.write_node_triple(data_buffer, triple)?; } } + Ok(()) } /// Serialize a triple to `data_buffer`. @@ -559,21 +551,19 @@ impl GraphDisplayDataSolutionSerializer { data_buffer: &mut SerializationDataBuffer, triple: Triple, ) -> Result<(), SerializationError> { - // TODO: Collect errors and show to frontend debug!("{}", triple); match &triple.element_type { Term::BlankNode(bnode) => { // The query must never put blank nodes in the ?nodeType variable let msg = format!("Illegal blank node during serialization: '{bnode}'"); - return Err(ser_err!(SerializationFailed(Some(triple), msg)).into()); + return Err(SerializationErrorKind::SerializationFailed(triple, msg).into()); } Term::Literal(literal) => { - // NOTE: Any string literal goes here, e.g. 'EquivalentClass'. - // That is, every BIND("someString" AS ?nodeType) + // NOTE: Any string literal goes here, e.g, every BIND("someString" AS ?nodeType) let value = literal.value(); match value { "blanknode" => { - info!("Visualizing blank node: {}", triple.id); + debug!("Visualizing blank node: {}", triple.id); self.insert_node( data_buffer, &triple, @@ -635,10 +625,11 @@ impl GraphDisplayDataSolutionSerializer { )?; } rdfs::DOMAIN => { - error!( - "sparql query should not have rdfs:domain triples: {}", - triple - ); + return Err(SerializationErrorKind::SerializationFailed( + triple, + "SPARQL query should not have rdfs:domain triples".to_string(), + ) + .into()); } // rdfs::IS_DEFINED_BY => {} @@ -653,10 +644,11 @@ impl GraphDisplayDataSolutionSerializer { } // rdfs::MEMBER => {} rdfs::RANGE => { - error!( - "sparql query should not have rdfs:range triples: {}", - triple - ); + return Err(SerializationErrorKind::SerializationFailed( + triple, + "SPARQL query should not have rdfs:range triples".to_string(), + ) + .into()); } rdfs::RESOURCE => { self.insert_node( @@ -739,7 +731,6 @@ impl GraphDisplayDataSolutionSerializer { &triple, e, ); - return Ok(()); } //TODO: OWL1 (deprecated in OWL2, replaced by rdfs:datatype) @@ -789,17 +780,17 @@ impl GraphDisplayDataSolutionSerializer { let (index_s, index_o) = self.resolve_so(data_buffer, &triple); match (index_s, index_o) { (Some(index_s), Some(index_o)) => { - match self.merge_nodes(data_buffer, &index_o, &index_s) { - Ok(_) => (), - Err(e) => { - data_buffer - .failed_buffer - .push((e.inner.triple().cloned(), e.to_string())); - } - } - - // SAFETY: If index_s is Some it exists in node_element_buffer. - if data_buffer.node_element_buffer[&index_s] + self.merge_nodes(data_buffer, &index_o, &index_s)?; + + let index_s_element = data_buffer + .node_element_buffer + .get(&index_s) + .ok_or_else(|| { + let msg = "subject not present in node_element_buffer" + .to_string(); + SerializationErrorKind::SerializationFailed(triple, msg) + })?; + if *index_s_element != ElementType::Owl(OwlType::Node(OwlNode::AnonymousClass)) { self.upgrade_node_type( @@ -822,7 +813,10 @@ impl GraphDisplayDataSolutionSerializer { self.add_to_unknown_buffer(data_buffer, target, triple.clone()) } None => { - data_buffer.failed_buffer.push((Some(triple), "Failed to merge object of equivalence relation into subject: object not found".to_string())); + let msg = "Failed to merge object of equivalence relation into subject: object not found".to_string(); + return Err( + SerializationErrorKind::MissingObject(triple, msg).into() + ); } }, (None, Some(index_o)) => { @@ -977,10 +971,13 @@ impl GraphDisplayDataSolutionSerializer { // owl::VERSION_INFO => {} // owl::VERSION_IRI => {} // owl::WITH_RESTRICTIONS => {} - _ => match triple.target.clone() { - Some(target) => { - let (node_triple, edge_triple): (Option>, Option) = - match ( + _ => { + match triple.target.clone() { + Some(target) => { + let (node_triple, edge_triple): ( + Option>, + Option, + ) = match ( self.resolve(data_buffer, triple.id.clone()), self.resolve(data_buffer, triple.element_type.clone()), self.resolve(data_buffer, target.clone()), @@ -990,7 +987,7 @@ impl GraphDisplayDataSolutionSerializer { "Resolving object property: range: {}, property: {}, domain: {}", range, property, domain ); - (None, Some(triple)) + (None, Some(triple.clone())) } (Some(domain), Some(property), None) => { trace!("Missing range: {}", triple); @@ -999,7 +996,7 @@ impl GraphDisplayDataSolutionSerializer { trim_tag_circumfix(domain.to_string().as_str()) + "_thing"; info!("Creating thing node: {}", target_iri); - Some(self.create_node( + Some(self.create_triple( target_iri.clone(), owl::THING.into(), None, @@ -1009,7 +1006,7 @@ impl GraphDisplayDataSolutionSerializer { trim_tag_circumfix(property.to_string().as_str()) + "_literal"; info!("Creating literal node: {}", target_iri); - Some(self.create_node( + Some(self.create_triple( target_iri.clone(), rdfs::LITERAL.into(), None, @@ -1021,8 +1018,8 @@ impl GraphDisplayDataSolutionSerializer { Some(node) => ( Some(vec![node.clone()]), Some(Triple { - id: triple.id, - element_type: triple.element_type, + id: triple.id.clone(), + element_type: triple.element_type.clone(), target: Some(node.id), }), ), @@ -1034,7 +1031,7 @@ impl GraphDisplayDataSolutionSerializer { self.add_to_unknown_buffer( data_buffer, target, - triple, + triple.clone(), ); (None, None) } @@ -1047,7 +1044,7 @@ impl GraphDisplayDataSolutionSerializer { trim_tag_circumfix(range.to_string().as_str()) + "_thing"; info!("Creating thing node: {}", target_iri); - Some(self.create_node( + Some(self.create_triple( target_iri.clone(), owl::THING.into(), None, @@ -1056,7 +1053,7 @@ impl GraphDisplayDataSolutionSerializer { let target_iri = trim_tag_circumfix(range.to_string().as_str()) + "_literal"; - Some(self.create_node( + Some(self.create_triple( target_iri.clone(), rdfs::LITERAL.into(), None, @@ -1069,8 +1066,8 @@ impl GraphDisplayDataSolutionSerializer { Some(vec![node.clone()]), Some(Triple { id: node.id, - element_type: triple.element_type, - target: triple.target, + element_type: triple.element_type.clone(), + target: triple.target.clone(), }), ), None => { @@ -1081,7 +1078,7 @@ impl GraphDisplayDataSolutionSerializer { self.add_to_unknown_buffer( data_buffer, target, - triple, + triple.clone(), ); (None, None) } @@ -1090,19 +1087,19 @@ impl GraphDisplayDataSolutionSerializer { (None, Some(property), None) => { trace!("Missing domain and range: {}", triple); if triple.element_type == owl::DATATYPE_PROPERTY.into() { - let local_literal = NamedNode::new( + let local_literal = self.create_named_node( property.to_string() + "_locallitral", )?; - let literal_triple = self.create_node( + let literal_triple = self.create_triple( local_literal.to_string(), rdfs::LITERAL.into(), None, )?; info!("Creating literal node: {}", local_literal); - let local_thing = NamedNode::new( + let local_thing = self.create_named_node( property.to_string() + "_localthing", )?; - let thing_triple = self.create_node( + let thing_triple = self.create_triple( local_thing.to_string(), owl::THING.into(), None, @@ -1115,15 +1112,17 @@ impl GraphDisplayDataSolutionSerializer { ]), Some(Triple { id: thing_triple.id.clone(), - element_type: triple.element_type, + element_type: triple.element_type.clone(), target: Some(literal_triple.id), }), ) } else if triple.element_type == owl::OBJECT_PROPERTY.into() { - let global_thing = - NamedNode::new(owl::THING.to_string() + "_thing")?; - let node = self.create_node( + let global_thing = self.create_named_node( + owl::THING.to_string() + "_thing", + )?; + + let node = self.create_triple( global_thing.to_string(), global_thing, None, @@ -1132,16 +1131,18 @@ impl GraphDisplayDataSolutionSerializer { Some(vec![node.clone()]), Some(Triple { id: node.id.clone(), - element_type: triple.element_type, + element_type: triple.element_type.clone(), target: Some(node.id), }), ) } else { - return Err(ser_err!(SerializationFailed( - Some(triple), - "Illegal property triple".to_string() - )) - .into()); + return Err( + SerializationErrorKind::SerializationFailed( + triple, + "Illegal property triple".to_string(), + ) + .into(), + ); } } @@ -1153,7 +1154,7 @@ impl GraphDisplayDataSolutionSerializer { self.add_to_unknown_buffer( data_buffer, triple.element_type.clone(), - triple, + triple.clone(), ); (None, None) } @@ -1162,77 +1163,100 @@ impl GraphDisplayDataSolutionSerializer { self.add_to_unknown_buffer( data_buffer, triple.id.clone(), - triple, + triple.clone(), ); (None, None) } }; - match node_triple { - Some(node_triples) => { - for node_triple in node_triples { - if node_triple.element_type == owl::THING.into() { - self.insert_node( - data_buffer, - &node_triple, - ElementType::Owl(OwlType::Node(OwlNode::Thing)), - )?; - } else if node_triple.element_type == rdfs::LITERAL.into() { - self.insert_node( - data_buffer, - &node_triple, - ElementType::Rdfs(RdfsType::Node( - RdfsNode::Literal, - )), - )?; + match node_triple { + Some(node_triples) => { + for node_triple in node_triples { + if node_triple.element_type == owl::THING.into() { + self.insert_node( + data_buffer, + &node_triple, + ElementType::Owl(OwlType::Node(OwlNode::Thing)), + )?; + } else if node_triple.element_type + == rdfs::LITERAL.into() + { + self.insert_node( + data_buffer, + &node_triple, + ElementType::Rdfs(RdfsType::Node( + RdfsNode::Literal, + )), + )?; + } } } + None => { + return Err(SerializationErrorKind::SerializationFailed( + triple.clone(), + "Error creating node".to_string(), + ) + .into()); + } } - None => error!("Error creating node {:?}", node_triple), - } - match edge_triple { - Some(edge_triple) => { - // unwrap safe, edge_triple will always be Some if property can be resolved. - let property = data_buffer + match edge_triple { + Some(edge_triple) => { + // Dummy variable + // TODO: Refactor clones away in all of serializer + let dummy = || edge_triple.clone(); + + let property= data_buffer .edge_element_buffer - .get(&edge_triple.element_type) - .unwrap(); - let edge = self.insert_edge( - data_buffer, - &edge_triple, - *property, - data_buffer - .label_buffer - .get(&edge_triple.element_type) - .cloned(), - ); - if let Some(edge) = edge { - data_buffer.add_property_edge( - edge_triple.element_type.clone(), - edge, - ); - data_buffer.add_property_domain( - edge_triple.element_type.clone(), - edge_triple - .target - .clone() - .expect("target should be a string"), - ); - data_buffer.add_property_range( - edge_triple.element_type, - edge_triple.id, + .get(&edge_triple.element_type).ok_or_else(|| { + let msg = "Edge triple not present in edge_element_buffer".to_string(); + SerializationErrorKind::SerializationFailed(dummy(), msg)})?; + let edge = self.insert_edge( + data_buffer, + &edge_triple, + *property, + data_buffer + .label_buffer + .get(&edge_triple.element_type) + .cloned(), ); + if let Some(edge) = edge { + data_buffer.add_property_edge( + edge_triple.element_type.clone(), + edge, + ); + data_buffer.add_property_domain( + edge_triple.element_type.clone(), + edge_triple.target.clone().ok_or_else(|| { + SerializationErrorKind::SerializationFailed( + dummy(), + "target should be a string".to_string(), + ) + })?, + ); + data_buffer.add_property_range( + edge_triple.element_type, + edge_triple.id, + ); + } + } + None => { + return Err(SerializationErrorKind::SerializationFailed( + triple, + "Error creating edge".to_string(), + ) + .into()); } - } - None => { - error!("Error creating edge: "); } } + None => { + return Err(SerializationErrorKind::SerializationFailed( + triple, + "Object property triples should have a target".to_string(), + ) + .into()); + } } - None => { - error!("object property triples should have a target: {}", triple); - } - }, + } } } } @@ -1250,16 +1274,16 @@ impl GraphDisplayDataSolutionSerializer { Some(s) => match data_buffer.node_characteristics.get_mut(&s) { Some(char) => { for (k, v) in data_buffer.property_edge_map.iter() { - info!("{} -> {}", k, v); + trace!("{} -> {}", k, v); } - info!("Inserting characteristic: {} -> {}", s, arg); + debug!("Inserting characteristic: {} -> {}", s, arg); char.push(arg); } None => { for (k, v) in data_buffer.property_edge_map.iter() { - info!("{} -> {}", k, v); + trace!("{} -> {}", k, v); } - info!("Inserting characteristic: {} -> {}", s, arg); + debug!("Inserting characteristic: {} -> {}", s, arg); let e = data_buffer.property_edge_map.get(&s); match e { Some(e) => { @@ -1274,7 +1298,7 @@ impl GraphDisplayDataSolutionSerializer { } }, None => { - info!("Adding characteristic to unknown buffer: {}", triple); + debug!("Adding characteristic to unknown buffer: {}", triple); self.add_to_unknown_buffer(data_buffer, triple.id.clone(), triple); } } From 68a7ccb7c48b9424270eeab64150daa6cea82b85 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 02:02:42 +0100 Subject: [PATCH 13/36] Minor format! changes --- crates/database/src/errors.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/database/src/errors.rs b/crates/database/src/errors.rs index 20ad39a..0ff1024 100644 --- a/crates/database/src/errors.rs +++ b/crates/database/src/errors.rs @@ -1,6 +1,5 @@ use crate::serializers::Triple; use oxrdf::{BlankNodeIdParseError, IriParseError}; -use vowlr_parser::errors::VOWLRStoreError; use vowlr_util::prelude::{ErrorRecord, ErrorSeverity, ErrorType}; #[derive(Debug)] @@ -37,20 +36,20 @@ impl From for ErrorRecord { fn from(value: SerializationError) -> Self { let (message, severity) = match value.inner { SerializationErrorKind::MissingObject(triple, e) => { - (format!("{e}\n{triple}"), ErrorSeverity::Warning) + (format!("{e}:\n{triple}"), ErrorSeverity::Warning) } SerializationErrorKind::MissingSubject(triple, e) => { - (format!("{e}\n{triple}"), ErrorSeverity::Warning) + (format!("{e}:\n{triple}"), ErrorSeverity::Warning) } SerializationErrorKind::SerializationFailed(triple, e) => { - (format!("{e}\n{triple}"), ErrorSeverity::Critical) + (format!("{e}:\n{triple}"), ErrorSeverity::Critical) } - SerializationErrorKind::IriParseError(triple, iri_parse_error) => ( - format!("{iri_parse_error}\n{triple}"), + SerializationErrorKind::IriParseError(iri, iri_parse_error) => ( + format!("{iri_parse_error} (IRI: {iri})"), ErrorSeverity::Severe, ), - SerializationErrorKind::BlankNodeParseError(triple, blank_node_id_parse_error) => ( - format!("{blank_node_id_parse_error}\n{triple}"), + SerializationErrorKind::BlankNodeParseError(id, blank_node_id_parse_error) => ( + format!("{blank_node_id_parse_error} (ID: {id})"), ErrorSeverity::Severe, ), }; From 4f880684d4139a54e14974db8ae7321839f4a832 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 02:03:42 +0100 Subject: [PATCH 14/36] Impl for our custom server error --- crates/parser/src/errors.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/parser/src/errors.rs b/crates/parser/src/errors.rs index 4ead104..a7fa453 100644 --- a/crates/parser/src/errors.rs +++ b/crates/parser/src/errors.rs @@ -8,7 +8,7 @@ use rdf_fusion::{ model::{IriParseError, StorageError}, }; use tokio::task::JoinError; -use vowlr_util::prelude::{ErrorRecord, ErrorSeverity, ErrorType}; +use vowlr_util::prelude::{ErrorRecord, ErrorSeverity, ErrorType, VOWLRServerError}; #[derive(Debug)] pub enum VOWLRStoreErrorKind { @@ -160,7 +160,9 @@ impl From for ErrorRecord { VOWLRStoreErrorKind::HornedError(horned_error) => { (horned_error.to_string(), ErrorType::Parser) } - VOWLRStoreErrorKind::IOError(error) => (error.to_string(), ErrorType::Generic), + VOWLRStoreErrorKind::IOError(error) => { + (error.to_string(), ErrorType::InternalServerError) + } VOWLRStoreErrorKind::IriParseError(iri_parse_error) => { (iri_parse_error.to_string(), ErrorType::Parser) } @@ -171,7 +173,7 @@ impl From for ErrorRecord { (query_evaluation_error.to_string(), ErrorType::Database) } VOWLRStoreErrorKind::JoinError(join_error) => { - (join_error.to_string(), ErrorType::Generic) + (join_error.to_string(), ErrorType::InternalServerError) } VOWLRStoreErrorKind::StorageError(storage_error) => { (storage_error.to_string(), ErrorType::Database) @@ -186,3 +188,10 @@ impl From for ErrorRecord { ) } } + +impl From for VOWLRServerError { + fn from(value: VOWLRStoreError) -> Self { + let record: ErrorRecord = value.into(); + record.into() + } +} From 9fff2ab6ae935473c5ecb25a68a41789cf23fb7b Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 02:03:53 +0100 Subject: [PATCH 15/36] Export our custom server error --- crates/util/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index f0920e0..f29811f 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -3,5 +3,5 @@ mod error_handler; pub mod prelude { pub use crate::datatypes::DataType; - pub use crate::error_handler::{ErrorRecord, ErrorSeverity, ErrorType}; + pub use crate::error_handler::{ErrorRecord, ErrorSeverity, ErrorType, VOWLRServerError}; } From f6ae37e5dd302f606b6f5be8e0a7b81ec6721b4e Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 02:04:05 +0100 Subject: [PATCH 16/36] Add table gen on server --- crates/util/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 6119334..7276ae8 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -19,3 +19,7 @@ rkyv.workspace=true serde={version="1.0", features=["derive"]} strum.workspace=true + tabled={version="0.20", optional=true} + +[features] + server=["dep:tabled"] From 5df30a3f99fe4bd5d90a31c11ebec69d2b4c8663 Mon Sep 17 00:00:00 2001 From: TheRealMorgenfrue <33068980+TheRealMorgenfrue@users.noreply.github.com> Date: Sat, 28 Feb 2026 02:04:56 +0100 Subject: [PATCH 17/36] Expand error handling frontend with the new error handling backend --- src/blocks/workbench.rs | 9 +-- src/blocks/workbench/error_log.rs | 112 ++++++++++++++++++++++++++---- 2 files changed, 102 insertions(+), 19 deletions(-) diff --git a/src/blocks/workbench.rs b/src/blocks/workbench.rs index 8e4abbf..0a12651 100644 --- a/src/blocks/workbench.rs +++ b/src/blocks/workbench.rs @@ -46,14 +46,11 @@ pub fn NewWorkbench() -> impl IntoView { total_graph_data, }); - let all_errors = RwSignal::new(Vec::::new()); - - let error_context = ErrorLogContext { errors: all_errors }; - - provide_context(error_context.clone()); + let error_context = ErrorLogContext::default(); + provide_context(error_context); let error_title = Signal::derive(move || { - let count = error_context.errors.get().len(); + let count = error_context.len(); if count > 0 { format!("Error Log ({count})") } else { diff --git a/src/blocks/workbench/error_log.rs b/src/blocks/workbench/error_log.rs index 991ccad..93c2fed 100644 --- a/src/blocks/workbench/error_log.rs +++ b/src/blocks/workbench/error_log.rs @@ -1,35 +1,121 @@ +// use std::ops::Range; + use super::WorkbenchMenuItems; use leptos::prelude::*; +// use leptos_struct_table::{ColumnSort, TableContent, TableDataProvider}; +use vowlr_util::prelude::{ErrorRecord, VOWLRServerError}; -#[derive(Clone)] +#[derive(Debug, Copy, Clone)] pub struct ErrorLogContext { - pub errors: RwSignal>, + pub records: RwSignal>, } -pub fn ErrorLog() -> impl IntoView { - fn unescape_log(s: &str) -> String { - s.replace("\\n", "\n").replace("\\t", "\t") +impl ErrorLogContext { + pub fn new(records: Vec) -> Self { + Self { + records: RwSignal::new(records), + } + } + + /// Appends an element to the back of a collection. + /// + /// # Panics + /// Panics if you update the value of the signal of `self` before this function returns. + pub fn push(&self, record: ErrorRecord) { + self.records.update(|records| records.push(record)); + } + + /// Extends a collection with the contents of an iterator. + /// + /// # Panics + /// Panics if you update the value of the signal of `self` before this function returns. + pub fn extend(&self, records: Vec) { + self.records.update(|records_| records_.extend(records)); + } + + /// Clears the collection, removing all values. + /// + /// Note that this method has no effect on the allocated capacity of the vector. + /// + /// # Panics + /// Panics if you update the value of the signal of `self` before this function returns. + pub fn clear(&self) { + // self.records.update(|records| records.clear()); + self.records.update(std::vec::Vec::clear); + } + + /// Returns the number of elements in the collection, also referred to as its 'length' + /// + /// # Panics + /// Panics if you try to access the signal of `self` when it has been disposed. + pub fn len(&self) -> usize { + self.records.read().len() + } +} + +impl Default for ErrorLogContext { + fn default() -> Self { + Self { + records: RwSignal::new(Vec::new()), + } + } +} + +// impl TableDataProvider for ErrorLogContext { +// async fn get_rows( +// &self, +// range: Range, +// ) -> Result<(Vec, Range), String> { +// let records = self.records.read(); +// if records.is_empty() { +// return Ok((vec![], 0..0)); +// } + +// let start = range.start.min(records.len() - 1); +// let end = range.end.min(records.len()); + +// let return_range = start..end; + +// Ok((records[return_range.clone()].to_vec(), return_range)) +// } + +// async fn row_count(&self) -> Option { +// Some(self.len()) +// } + +// // fn set_sorting(&mut self, sorting: &std::collections::VecDeque<(usize, ColumnSort)>) {} +// } + +impl From for ErrorLogContext { + fn from(value: VOWLRServerError) -> Self { + Self::new(value.records) } +} - let error_log = expect_context::(); +pub fn ErrorLog() -> impl IntoView { + let error_context = expect_context::(); + // view! { + // + // + //
+ // } view! { {move || { - let errors = error_log.errors.get(); + let records = error_context.records.read(); view! {
- {if errors.is_empty() { + {if records.is_empty() { view! {

"No errors"

} .into_any() } else { view! {