From 1a064ede53884eaf0eb52d18875121162f59e527 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 8 Feb 2024 16:41:41 -0500 Subject: [PATCH 01/15] Convert error to thiserror This converts just one `SerializerError` to use `thiserror` crate, removing some code. --- Cargo.lock | 21 +++++++++++++ Cargo.toml | 1 + lib/oxigraph/Cargo.toml | 1 + lib/oxigraph/src/storage/error.rs | 49 +++++-------------------------- 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5245a514..d729862c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1068,6 +1068,7 @@ dependencies = [ "sparesults", "spargebra", "sparopt", + "thiserror", "zstd", ] @@ -1847,6 +1848,26 @@ dependencies = [ "term", ] +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.34" diff --git a/Cargo.toml b/Cargo.toml index 7cbc711b..1c0cc9e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ sha1 = "0.10" sha2 = "0.10" siphasher = ">=0.3, <2.0" text-diff = "0.4" +thiserror = "1" time = "0.3" tokio = "1.29" url = "2.4" diff --git a/lib/oxigraph/Cargo.toml b/lib/oxigraph/Cargo.toml index 13f14476..cefb5755 100644 --- a/lib/oxigraph/Cargo.toml +++ b/lib/oxigraph/Cargo.toml @@ -43,6 +43,7 @@ siphasher.workspace = true sparesults = { workspace = true, features = ["rdf-star"] } spargebra = { workspace = true, features = ["rdf-star", "sep-0002", "sep-0006"] } sparopt = { workspace = true, features = ["rdf-star", "sep-0002", "sep-0006"] } +thiserror.workspace = true [target.'cfg(not(target_family = "wasm"))'.dependencies] libc.workspace = true diff --git a/lib/oxigraph/src/storage/error.rs b/lib/oxigraph/src/storage/error.rs index 05076e6e..d0a67522 100644 --- a/lib/oxigraph/src/storage/error.rs +++ b/lib/oxigraph/src/storage/error.rs @@ -2,6 +2,7 @@ use crate::io::{ParseError, RdfFormat}; use oxiri::IriParseError; use std::error::Error; use std::{fmt, io}; +use thiserror::Error; /// An error related to storage operations (reads, writes...). #[derive(Debug)] @@ -185,55 +186,19 @@ impl From for io::Error { } /// An error raised while writing a file from a [`Store`](crate::store::Store). -#[derive(Debug)] +#[derive(Debug, Error)] pub enum SerializerError { /// An error raised while writing the content. - Io(io::Error), + #[error(transparent)] + Io(#[from] io::Error), /// An error raised during the lookup in the store. - Storage(StorageError), + #[error(transparent)] + Storage(#[from] StorageError), /// A format compatible with [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) is required. + #[error("A RDF format supporting datasets was expected, {0} found")] DatasetFormatExpected(RdfFormat), } -impl fmt::Display for SerializerError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io(e) => e.fmt(f), - Self::Storage(e) => e.fmt(f), - Self::DatasetFormatExpected(format) => write!( - f, - "A RDF format supporting datasets was expected, {format} found" - ), - } - } -} - -impl Error for SerializerError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Io(e) => Some(e), - Self::Storage(e) => Some(e), - Self::DatasetFormatExpected(_) => None, - } - } -} - -impl From for SerializerError { - #[inline] - fn from(error: io::Error) -> Self { - Self::Io(error) - } -} - -impl From for SerializerError { - #[inline] - fn from(error: StorageError) -> Self { - Self::Storage(error) - } -} - impl From for io::Error { #[inline] fn from(error: SerializerError) -> Self { From 65eed84256a69d5f4f1d28185690b9315dbea8d7 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 8 Feb 2024 17:11:49 -0500 Subject: [PATCH 02/15] Many other errors --- Cargo.lock | 4 + lib/oxigraph/src/sparql/error.rs | 111 +++++------------------ lib/oxigraph/src/storage/error.rs | 91 +++---------------- lib/oxigraph/src/storage/small_string.rs | 31 +------ lib/oxrdfio/Cargo.toml | 1 + lib/oxrdfio/src/error.rs | 43 ++------- lib/oxrdfxml/Cargo.toml | 1 + lib/oxrdfxml/src/error.rs | 43 ++------- lib/oxttl/Cargo.toml | 1 + lib/oxttl/src/toolkit/error.rs | 43 ++------- lib/sparesults/Cargo.toml | 1 + lib/sparesults/src/error.rs | 43 ++------- 12 files changed, 72 insertions(+), 341 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d729862c..2eaeff63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1145,6 +1145,7 @@ dependencies = [ "oxrdf", "oxrdfxml", "oxttl", + "thiserror", "tokio", ] @@ -1156,6 +1157,7 @@ dependencies = [ "oxiri", "oxrdf", "quick-xml", + "thiserror", "tokio", ] @@ -1184,6 +1186,7 @@ dependencies = [ "oxilangtag", "oxiri", "oxrdf", + "thiserror", "tokio", ] @@ -1745,6 +1748,7 @@ dependencies = [ "memchr", "oxrdf", "quick-xml", + "thiserror", "tokio", ] diff --git a/lib/oxigraph/src/sparql/error.rs b/lib/oxigraph/src/sparql/error.rs index b3516d8e..35eba714 100644 --- a/lib/oxigraph/src/sparql/error.rs +++ b/lib/oxigraph/src/sparql/error.rs @@ -5,93 +5,54 @@ use crate::sparql::ParseError; use crate::storage::StorageError; use std::convert::Infallible; use std::error::Error; -use std::{fmt, io}; +use std::io; +use thiserror::Error; /// A SPARQL evaluation error. -#[derive(Debug)] +#[derive(Debug, Error)] #[non_exhaustive] pub enum EvaluationError { /// An error in SPARQL parsing. - Parsing(ParseError), + #[error(transparent)] + Parsing(#[from] ParseError), /// An error from the storage. - Storage(StorageError), + #[error(transparent)] + Storage(#[from] StorageError), /// An error while parsing an external RDF file. - GraphParsing(RdfParseError), + #[error(transparent)] + GraphParsing(#[from] RdfParseError), /// An error while parsing an external result file (likely from a federated query). - ResultsParsing(ResultsParseError), + #[error(transparent)] + ResultsParsing(#[from] ResultsParseError), /// An error returned during results serialization. - ResultsSerialization(io::Error), + #[error(transparent)] + ResultsSerialization(#[from] io::Error), /// Error during `SERVICE` evaluation - Service(Box), + #[error("{0}")] + Service(#[source] Box), /// Error when `CREATE` tries to create an already existing graph + #[error("The graph {0} already exists")] GraphAlreadyExists(NamedNode), /// Error when `DROP` or `CLEAR` tries to remove a not existing graph + #[error("The graph {0} does not exist")] GraphDoesNotExist(NamedNode), /// The variable storing the `SERVICE` name is unbound + #[error("The variable encoding the service name is unbound")] UnboundService, /// The given `SERVICE` is not supported + #[error("The service {0} is not supported")] UnsupportedService(NamedNode), /// The given content media type returned from an HTTP response is not supported (`SERVICE` and `LOAD`) + #[error("The content media type {0} is not supported")] UnsupportedContentType(String), /// The `SERVICE` call has not returns solutions + #[error("The service is not returning solutions but a boolean or a graph")] ServiceDoesNotReturnSolutions, /// The results are not a RDF graph + #[error("The query results are not a RDF graph")] NotAGraph, } -impl fmt::Display for EvaluationError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Parsing(error) => error.fmt(f), - Self::Storage(error) => error.fmt(f), - Self::GraphParsing(error) => error.fmt(f), - Self::ResultsParsing(error) => error.fmt(f), - Self::ResultsSerialization(error) => error.fmt(f), - Self::Service(error) => error.fmt(f), - Self::GraphAlreadyExists(graph) => write!(f, "The graph {graph} already exists"), - Self::GraphDoesNotExist(graph) => write!(f, "The graph {graph} does not exist"), - Self::UnboundService => { - f.write_str("The variable encoding the service name is unbound") - } - Self::UnsupportedService(service) => { - write!(f, "The service {service} is not supported") - } - Self::UnsupportedContentType(content_type) => { - write!(f, "The content media type {content_type} is not supported") - } - Self::ServiceDoesNotReturnSolutions => { - f.write_str("The service is not returning solutions but a boolean or a graph") - } - Self::NotAGraph => f.write_str("The query results are not a RDF graph"), - } - } -} - -impl Error for EvaluationError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Parsing(e) => Some(e), - Self::Storage(e) => Some(e), - Self::GraphParsing(e) => Some(e), - Self::ResultsParsing(e) => Some(e), - Self::ResultsSerialization(e) => Some(e), - Self::Service(e) => { - let e = Box::as_ref(e); - Some(e) - } - Self::GraphAlreadyExists(_) - | Self::GraphDoesNotExist(_) - | Self::UnboundService - | Self::UnsupportedService(_) - | Self::UnsupportedContentType(_) - | Self::ServiceDoesNotReturnSolutions - | Self::NotAGraph => None, - } - } -} - impl From for EvaluationError { #[inline] fn from(error: Infallible) -> Self { @@ -99,34 +60,6 @@ impl From for EvaluationError { } } -impl From for EvaluationError { - #[inline] - fn from(error: ParseError) -> Self { - Self::Parsing(error) - } -} - -impl From for EvaluationError { - #[inline] - fn from(error: StorageError) -> Self { - Self::Storage(error) - } -} - -impl From for EvaluationError { - #[inline] - fn from(error: RdfParseError) -> Self { - Self::GraphParsing(error) - } -} - -impl From for EvaluationError { - #[inline] - fn from(error: ResultsParseError) -> Self { - Self::ResultsParsing(error) - } -} - impl From for io::Error { #[inline] fn from(error: EvaluationError) -> Self { diff --git a/lib/oxigraph/src/storage/error.rs b/lib/oxigraph/src/storage/error.rs index d0a67522..72f8b5e2 100644 --- a/lib/oxigraph/src/storage/error.rs +++ b/lib/oxigraph/src/storage/error.rs @@ -5,46 +5,20 @@ use std::{fmt, io}; use thiserror::Error; /// An error related to storage operations (reads, writes...). -#[derive(Debug)] +#[derive(Debug, Error)] #[non_exhaustive] pub enum StorageError { /// Error from the OS I/O layer. - Io(io::Error), + #[error(transparent)] + Io(#[from] io::Error), /// Error related to data corruption. - Corruption(CorruptionError), + #[error(transparent)] + Corruption(#[from] CorruptionError), #[doc(hidden)] + #[error(transparent)] Other(Box), } -impl fmt::Display for StorageError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io(e) => e.fmt(f), - Self::Corruption(e) => e.fmt(f), - Self::Other(e) => e.fmt(f), - } - } -} - -impl Error for StorageError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Io(e) => Some(e), - Self::Corruption(e) => Some(e), - Self::Other(e) => Some(e.as_ref()), - } - } -} - -impl From for StorageError { - #[inline] - fn from(error: io::Error) -> Self { - Self::Io(error) - } -} - impl From for io::Error { #[inline] fn from(error: StorageError) -> Self { @@ -106,13 +80,6 @@ impl Error for CorruptionError { } } -impl From for StorageError { - #[inline] - fn from(error: CorruptionError) -> Self { - Self::Corruption(error) - } -} - impl From for io::Error { #[inline] fn from(error: CorruptionError) -> Self { @@ -121,57 +88,25 @@ impl From for io::Error { } /// An error raised while loading a file into a [`Store`](crate::store::Store). -#[derive(Debug)] +#[derive(Debug, Error)] pub enum LoaderError { /// An error raised while reading the file. - Parsing(ParseError), + #[error(transparent)] + Parsing(#[from] ParseError), /// An error raised during the insertion in the store. - Storage(StorageError), + #[error(transparent)] + Storage(#[from] StorageError), /// The base IRI is invalid. + #[error("Invalid base IRI '{iri}': {error}")] InvalidBaseIri { /// The IRI itself. iri: String, /// The parsing error. + #[source] error: IriParseError, }, } -impl fmt::Display for LoaderError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Parsing(e) => e.fmt(f), - Self::Storage(e) => e.fmt(f), - Self::InvalidBaseIri { iri, error } => write!(f, "Invalid base IRI '{iri}': {error}"), - } - } -} - -impl Error for LoaderError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Parsing(e) => Some(e), - Self::Storage(e) => Some(e), - Self::InvalidBaseIri { error, .. } => Some(error), - } - } -} - -impl From for LoaderError { - #[inline] - fn from(error: ParseError) -> Self { - Self::Parsing(error) - } -} - -impl From for LoaderError { - #[inline] - fn from(error: StorageError) -> Self { - Self::Storage(error) - } -} - impl From for io::Error { #[inline] fn from(error: LoaderError) -> Self { diff --git a/lib/oxigraph/src/storage/small_string.rs b/lib/oxigraph/src/storage/small_string.rs index fcd9b227..0ffa3f3a 100644 --- a/lib/oxigraph/src/storage/small_string.rs +++ b/lib/oxigraph/src/storage/small_string.rs @@ -1,10 +1,10 @@ use std::borrow::Borrow; use std::cmp::Ordering; -use std::error::Error; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::str::{FromStr, Utf8Error}; use std::{fmt, str}; +use thiserror::Error; /// A small inline string #[derive(Clone, Copy, Default)] @@ -169,31 +169,10 @@ impl<'a> TryFrom<&'a str> for SmallString { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Error)] pub enum BadSmallStringError { + #[error("small strings could only contain at most 15 characters, found {0}")] TooLong(usize), - BadUtf8(Utf8Error), -} - -impl fmt::Display for BadSmallStringError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::TooLong(v) => write!( - f, - "small strings could only contain at most 15 characters, found {v}" - ), - Self::BadUtf8(e) => e.fmt(f), - } - } -} - -impl Error for BadSmallStringError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::TooLong(_) => None, - Self::BadUtf8(e) => Some(e), - } - } + #[error(transparent)] + BadUtf8(#[from] Utf8Error), } diff --git a/lib/oxrdfio/Cargo.toml b/lib/oxrdfio/Cargo.toml index 84e398f1..05069df3 100644 --- a/lib/oxrdfio/Cargo.toml +++ b/lib/oxrdfio/Cargo.toml @@ -22,6 +22,7 @@ rdf-star = ["oxrdf/rdf-star", "oxttl/rdf-star"] oxrdf.workspace = true oxrdfxml.workspace = true oxttl.workspace = true +thiserror.workspace = true tokio = { workspace = true, optional = true, features = ["io-util"] } [dev-dependencies] diff --git a/lib/oxrdfio/src/error.rs b/lib/oxrdfio/src/error.rs index 78f9b998..86534d46 100644 --- a/lib/oxrdfio/src/error.rs +++ b/lib/oxrdfio/src/error.rs @@ -1,14 +1,17 @@ use std::error::Error; use std::ops::Range; use std::{fmt, io}; +use thiserror::Error; /// Error returned during RDF format parsing. -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ParseError { /// I/O error during parsing (file not found...). - Io(io::Error), + #[error(transparent)] + Io(#[from] io::Error), /// An error in the file syntax. - Syntax(SyntaxError), + #[error(transparent)] + Syntax(#[from] SyntaxError), } impl ParseError { @@ -19,26 +22,6 @@ impl ParseError { } } -impl fmt::Display for ParseError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io(e) => e.fmt(f), - Self::Syntax(e) => e.fmt(f), - } - } -} - -impl Error for ParseError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Io(e) => Some(e), - Self::Syntax(e) => Some(e), - } - } -} - impl From for SyntaxError { #[inline] fn from(error: oxttl::SyntaxError) -> Self { @@ -77,20 +60,6 @@ impl From for ParseError { } } -impl From for ParseError { - #[inline] - fn from(error: io::Error) -> Self { - Self::Io(error) - } -} - -impl From for ParseError { - #[inline] - fn from(error: SyntaxError) -> Self { - Self::Syntax(error) - } -} - impl From for io::Error { #[inline] fn from(error: ParseError) -> Self { diff --git a/lib/oxrdfxml/Cargo.toml b/lib/oxrdfxml/Cargo.toml index 2ed9b248..efc71e62 100644 --- a/lib/oxrdfxml/Cargo.toml +++ b/lib/oxrdfxml/Cargo.toml @@ -22,6 +22,7 @@ oxilangtag.workspace = true oxiri.workspace = true oxrdf.workspace = true quick-xml.workspace = true +thiserror.workspace = true tokio = { workspace = true, optional = true, features = ["io-util"] } [dev-dependencies] diff --git a/lib/oxrdfxml/src/error.rs b/lib/oxrdfxml/src/error.rs index be2e161d..798a2f5d 100644 --- a/lib/oxrdfxml/src/error.rs +++ b/lib/oxrdfxml/src/error.rs @@ -3,48 +3,17 @@ use oxiri::IriParseError; use std::error::Error; use std::sync::Arc; use std::{fmt, io}; +use thiserror::Error; /// Error returned during RDF/XML parsing. -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ParseError { /// I/O error during parsing (file not found...). - Io(io::Error), + #[error(transparent)] + Io(#[from] io::Error), /// An error in the file syntax. - Syntax(SyntaxError), -} - -impl fmt::Display for ParseError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io(e) => e.fmt(f), - Self::Syntax(e) => e.fmt(f), - } - } -} - -impl Error for ParseError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Io(e) => Some(e), - Self::Syntax(e) => Some(e), - } - } -} - -impl From for ParseError { - #[inline] - fn from(error: io::Error) -> Self { - Self::Io(error) - } -} - -impl From for ParseError { - #[inline] - fn from(error: SyntaxError) -> Self { - Self::Syntax(error) - } + #[error(transparent)] + Syntax(#[from] SyntaxError), } impl From for io::Error { diff --git a/lib/oxttl/Cargo.toml b/lib/oxttl/Cargo.toml index 6f4f48ff..0a4bc3ab 100644 --- a/lib/oxttl/Cargo.toml +++ b/lib/oxttl/Cargo.toml @@ -23,6 +23,7 @@ memchr.workspace = true oxrdf.workspace = true oxiri.workspace = true oxilangtag.workspace = true +thiserror.workspace = true tokio = { workspace = true, optional = true, features = ["io-util"] } [dev-dependencies] diff --git a/lib/oxttl/src/toolkit/error.rs b/lib/oxttl/src/toolkit/error.rs index 2f632352..a90de168 100644 --- a/lib/oxttl/src/toolkit/error.rs +++ b/lib/oxttl/src/toolkit/error.rs @@ -1,6 +1,7 @@ use std::error::Error; use std::ops::Range; use std::{fmt, io}; +use thiserror::Error; /// A position in a text i.e. a `line` number starting from 0, a `column` number starting from 0 (in number of code points) and a global file `offset` starting from 0 (in number of bytes). #[derive(Eq, PartialEq, Debug, Clone, Copy)] @@ -79,46 +80,14 @@ impl From for io::Error { /// A parsing error. /// /// It is the union of [`SyntaxError`] and [`io::Error`]. -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ParseError { /// I/O error during parsing (file not found...). - Io(io::Error), + #[error(transparent)] + Io(#[from] io::Error), /// An error in the file syntax. - Syntax(SyntaxError), -} - -impl fmt::Display for ParseError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io(e) => e.fmt(f), - Self::Syntax(e) => e.fmt(f), - } - } -} - -impl Error for ParseError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(match self { - Self::Io(e) => e, - Self::Syntax(e) => e, - }) - } -} - -impl From for ParseError { - #[inline] - fn from(error: SyntaxError) -> Self { - Self::Syntax(error) - } -} - -impl From for ParseError { - #[inline] - fn from(error: io::Error) -> Self { - Self::Io(error) - } + #[error(transparent)] + Syntax(#[from] SyntaxError), } impl From for io::Error { diff --git a/lib/sparesults/Cargo.toml b/lib/sparesults/Cargo.toml index 75c5a0bb..5eae3746 100644 --- a/lib/sparesults/Cargo.toml +++ b/lib/sparesults/Cargo.toml @@ -23,6 +23,7 @@ json-event-parser.workspace = true memchr.workspace = true oxrdf.workspace = true quick-xml.workspace = true +thiserror.workspace = true tokio = { workspace = true, optional = true, features = ["io-util"] } [dev-dependencies] diff --git a/lib/sparesults/src/error.rs b/lib/sparesults/src/error.rs index 8ece28d0..6575a2ea 100644 --- a/lib/sparesults/src/error.rs +++ b/lib/sparesults/src/error.rs @@ -3,48 +3,17 @@ use std::error::Error; use std::ops::Range; use std::sync::Arc; use std::{fmt, io}; +use thiserror::Error; /// Error returned during SPARQL result formats format parsing. -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ParseError { /// I/O error during parsing (file not found...). - Io(io::Error), + #[error(transparent)] + Io(#[from] io::Error), /// An error in the file syntax. - Syntax(SyntaxError), -} - -impl fmt::Display for ParseError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io(e) => e.fmt(f), - Self::Syntax(e) => e.fmt(f), - } - } -} - -impl Error for ParseError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Io(e) => Some(e), - Self::Syntax(e) => Some(e), - } - } -} - -impl From for ParseError { - #[inline] - fn from(error: io::Error) -> Self { - Self::Io(error) - } -} - -impl From for ParseError { - #[inline] - fn from(error: SyntaxError) -> Self { - Self::Syntax(error) - } + #[error(transparent)] + Syntax(#[from] SyntaxError), } impl From for io::Error { From dae629fd4ec9234e883fbaa58058adff7fe31e89 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 8 Feb 2024 18:16:38 -0500 Subject: [PATCH 03/15] Fix thiserror minversion --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2eaeff63..55b7dc02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,9 +290,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.18" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" dependencies = [ "clap_builder", "clap_derive", @@ -300,9 +300,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" dependencies = [ "anstream", "anstyle", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", @@ -324,9 +324,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "codspeed" @@ -786,9 +786,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" dependencies = [ "libc", ] @@ -1787,9 +1787,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "subtle" diff --git a/Cargo.toml b/Cargo.toml index 1c0cc9e6..cab761e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ sha1 = "0.10" sha2 = "0.10" siphasher = ">=0.3, <2.0" text-diff = "0.4" -thiserror = "1" +thiserror = "1.0.56" time = "0.3" tokio = "1.29" url = "2.4" From a62c5102c434151c3c70d8099afead50fddab213 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 8 Feb 2024 18:52:19 -0500 Subject: [PATCH 04/15] Convert all Error + ErrorKind into one error --- Cargo.lock | 3 + lib/oxigraph/src/storage/error.rs | 47 ++++----------- lib/oxrdf/Cargo.toml | 1 + lib/oxrdf/src/parser.rs | 86 ++++++++------------------- lib/oxrdfio/src/error.rs | 77 +++++------------------- lib/oxrdfxml/src/error.rs | 66 +++++---------------- lib/oxrdfxml/src/parser.rs | 26 +++------ lib/oxsdatatypes/Cargo.toml | 3 + lib/oxsdatatypes/src/date_time.rs | 54 ++++------------- lib/oxsdatatypes/src/decimal.rs | 66 ++++++--------------- lib/sparesults/src/csv.rs | 34 +++++------ lib/sparesults/src/error.rs | 97 +++++++++---------------------- lib/spargebra/Cargo.toml | 1 + lib/spargebra/src/parser.rs | 53 ++++------------- 14 files changed, 168 insertions(+), 446 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55b7dc02..85d35123 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1136,6 +1136,7 @@ dependencies = [ "oxiri", "oxsdatatypes", "rand", + "thiserror", ] [[package]] @@ -1176,6 +1177,7 @@ name = "oxsdatatypes" version = "0.2.0-alpha.1" dependencies = [ "js-sys", + "thiserror", ] [[package]] @@ -1761,6 +1763,7 @@ dependencies = [ "oxrdf", "peg", "rand", + "thiserror", ] [[package]] diff --git a/lib/oxigraph/src/storage/error.rs b/lib/oxigraph/src/storage/error.rs index 72f8b5e2..1b13bd9b 100644 --- a/lib/oxigraph/src/storage/error.rs +++ b/lib/oxigraph/src/storage/error.rs @@ -1,7 +1,7 @@ use crate::io::{ParseError, RdfFormat}; use oxiri::IriParseError; use std::error::Error; -use std::{fmt, io}; +use std::io; use thiserror::Error; /// An error related to storage operations (reads, writes...). @@ -15,8 +15,8 @@ pub enum StorageError { #[error(transparent)] Corruption(#[from] CorruptionError), #[doc(hidden)] - #[error(transparent)] - Other(Box), + #[error("{0}")] + Other(#[source] Box), } impl From for io::Error { @@ -31,52 +31,25 @@ impl From for io::Error { } /// An error return if some content in the database is corrupted. -#[derive(Debug)] -pub struct CorruptionError { - inner: CorruptionErrorKind, -} - -#[derive(Debug)] -enum CorruptionErrorKind { +#[derive(Debug, Error)] +pub enum CorruptionError { + #[error("{0}")] Msg(String), - Other(Box), + #[error("{0}")] + Other(#[source] Box), } impl CorruptionError { /// Builds an error from a printable error message. #[inline] pub(crate) fn new(error: impl Into>) -> Self { - Self { - inner: CorruptionErrorKind::Other(error.into()), - } + Self::Other(error.into()) } /// Builds an error from a printable error message. #[inline] pub(crate) fn msg(msg: impl Into) -> Self { - Self { - inner: CorruptionErrorKind::Msg(msg.into()), - } - } -} - -impl fmt::Display for CorruptionError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.inner { - CorruptionErrorKind::Msg(e) => e.fmt(f), - CorruptionErrorKind::Other(e) => e.fmt(f), - } - } -} - -impl Error for CorruptionError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.inner { - CorruptionErrorKind::Msg(_) => None, - CorruptionErrorKind::Other(e) => Some(e.as_ref()), - } + Self::Msg(msg.into()) } } diff --git a/lib/oxrdf/Cargo.toml b/lib/oxrdf/Cargo.toml index 90b2bb12..6fec2123 100644 --- a/lib/oxrdf/Cargo.toml +++ b/lib/oxrdf/Cargo.toml @@ -22,6 +22,7 @@ oxilangtag.workspace = true oxiri.workspace = true oxsdatatypes = { workspace = true, optional = true } rand.workspace = true +thiserror.workspace = true [lints] workspace = true diff --git a/lib/oxrdf/src/parser.rs b/lib/oxrdf/src/parser.rs index 1794540d..2d6f1428 100644 --- a/lib/oxrdf/src/parser.rs +++ b/lib/oxrdf/src/parser.rs @@ -5,9 +5,9 @@ use crate::{ }; #[cfg(feature = "rdf-star")] use crate::{Subject, Triple}; -use std::error::Error; +use std::char; use std::str::{Chars, FromStr}; -use std::{char, fmt}; +use thiserror::Error; /// This limit is set in order to avoid stack overflow error when parsing nested triples due to too many recursive calls. /// The actual limit value is a wet finger compromise between not failing to parse valid files and avoiding to trigger stack overflow errors. @@ -166,11 +166,9 @@ impl FromStr for Variable { "Variable serialization should start with ? or $", )); } - Self::new(&s[1..]).map_err(|error| Self::Err { - kind: TermParseErrorKind::Variable { - value: s.to_owned(), - error, - }, + Self::new(&s[1..]).map_err(|error| Self::Err::Variable { + value: s.to_owned(), + error, }) } } @@ -183,11 +181,9 @@ fn read_named_node(s: &str) -> Result<(NamedNode, &str), TermParseError> { .ok_or_else(|| TermParseError::msg("Named node serialization should end with a >"))?; let (value, remain) = remain.split_at(end); let remain = &remain[1..]; - let term = NamedNode::new(value).map_err(|error| TermParseError { - kind: TermParseErrorKind::Iri { - value: value.to_owned(), - error, - }, + let term = NamedNode::new(value).map_err(|error| TermParseError::Iri { + value: value.to_owned(), + error, })?; Ok((term, remain)) } else { @@ -207,11 +203,9 @@ fn read_blank_node(s: &str) -> Result<(BlankNode, &str), TermParseError> { }) .unwrap_or(remain.len()); let (value, remain) = remain.split_at(end); - let term = BlankNode::new(value).map_err(|error| TermParseError { - kind: TermParseErrorKind::BlankNode { - value: value.to_owned(), - error, - }, + let term = BlankNode::new(value).map_err(|error| TermParseError::BlankNode { + value: value.to_owned(), + error, })?; Ok((term, remain)) } else { @@ -237,11 +231,9 @@ fn read_literal(s: &str) -> Result<(Literal, &str), TermParseError> { let (language, remain) = remain.split_at(end); Ok(( Literal::new_language_tagged_literal(value, language).map_err( - |error| TermParseError { - kind: TermParseErrorKind::LanguageTag { - value: language.to_owned(), - error, - }, + |error| TermParseError::LanguageTag { + value: language.to_owned(), + error, }, )?, remain, @@ -421,61 +413,31 @@ fn read_hexa_char(input: &mut Chars<'_>, len: usize) -> Result) -> fmt::Result { - match &self.kind { - TermParseErrorKind::Iri { error, value } => { - write!(f, "Error while parsing the named node '{value}': {error}") - } - TermParseErrorKind::BlankNode { error, value } => { - write!(f, "Error while parsing the blank node '{value}': {error}") - } - TermParseErrorKind::LanguageTag { error, value } => { - write!(f, "Error while parsing the language tag '{value}': {error}") - } - TermParseErrorKind::Variable { error, value } => { - write!(f, "Error while parsing the variable '{value}': {error}") - } - TermParseErrorKind::Msg { msg } => f.write_str(msg), - } - } -} - -impl Error for TermParseError {} - impl TermParseError { pub(crate) fn msg(msg: &'static str) -> Self { - Self { - kind: TermParseErrorKind::Msg { msg }, - } + Self::Msg { msg } } } diff --git a/lib/oxrdfio/src/error.rs b/lib/oxrdfio/src/error.rs index 86534d46..741ff6d9 100644 --- a/lib/oxrdfio/src/error.rs +++ b/lib/oxrdfio/src/error.rs @@ -1,6 +1,5 @@ -use std::error::Error; +use std::io; use std::ops::Range; -use std::{fmt, io}; use thiserror::Error; /// Error returned during RDF format parsing. @@ -16,18 +15,7 @@ pub enum ParseError { impl ParseError { pub(crate) fn msg(msg: &'static str) -> Self { - Self::Syntax(SyntaxError { - inner: SyntaxErrorKind::Msg { msg }, - }) - } -} - -impl From for SyntaxError { - #[inline] - fn from(error: oxttl::SyntaxError) -> Self { - Self { - inner: SyntaxErrorKind::Turtle(error), - } + Self::Syntax(SyntaxError::Msg { msg }) } } @@ -41,15 +29,6 @@ impl From for ParseError { } } -impl From for SyntaxError { - #[inline] - fn from(error: oxrdfxml::SyntaxError) -> Self { - Self { - inner: SyntaxErrorKind::RdfXml(error), - } - } -} - impl From for ParseError { #[inline] fn from(error: oxrdfxml::ParseError) -> Self { @@ -71,15 +50,13 @@ impl From for io::Error { } /// An error in the syntax of the parsed file. -#[derive(Debug)] -pub struct SyntaxError { - inner: SyntaxErrorKind, -} - -#[derive(Debug)] -enum SyntaxErrorKind { - Turtle(oxttl::SyntaxError), - RdfXml(oxrdfxml::SyntaxError), +#[derive(Debug, Error)] +pub enum SyntaxError { + #[error(transparent)] + Turtle(#[from] oxttl::SyntaxError), + #[error(transparent)] + RdfXml(#[from] oxrdfxml::SyntaxError), + #[error("{msg}")] Msg { msg: &'static str }, } @@ -87,8 +64,8 @@ impl SyntaxError { /// The location of the error inside of the file. #[inline] pub fn location(&self) -> Option> { - match &self.inner { - SyntaxErrorKind::Turtle(e) => { + match &self { + Self::Turtle(e) => { let location = e.location(); Some( TextPosition { @@ -102,29 +79,7 @@ impl SyntaxError { }, ) } - SyntaxErrorKind::RdfXml(_) | SyntaxErrorKind::Msg { .. } => None, - } - } -} - -impl fmt::Display for SyntaxError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.inner { - SyntaxErrorKind::Turtle(e) => e.fmt(f), - SyntaxErrorKind::RdfXml(e) => e.fmt(f), - SyntaxErrorKind::Msg { msg } => f.write_str(msg), - } - } -} - -impl Error for SyntaxError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.inner { - SyntaxErrorKind::Turtle(e) => Some(e), - SyntaxErrorKind::RdfXml(e) => Some(e), - SyntaxErrorKind::Msg { .. } => None, + Self::RdfXml(_) | Self::Msg { .. } => None, } } } @@ -132,10 +87,10 @@ impl Error for SyntaxError { impl From for io::Error { #[inline] fn from(error: SyntaxError) -> Self { - match error.inner { - SyntaxErrorKind::Turtle(error) => error.into(), - SyntaxErrorKind::RdfXml(error) => error.into(), - SyntaxErrorKind::Msg { msg } => Self::new(io::ErrorKind::InvalidData, msg), + match error { + SyntaxError::Turtle(error) => error.into(), + SyntaxError::RdfXml(error) => error.into(), + SyntaxError::Msg { msg } => Self::new(io::ErrorKind::InvalidData, msg), } } } diff --git a/lib/oxrdfxml/src/error.rs b/lib/oxrdfxml/src/error.rs index 798a2f5d..3ed9312d 100644 --- a/lib/oxrdfxml/src/error.rs +++ b/lib/oxrdfxml/src/error.rs @@ -1,8 +1,7 @@ use oxilangtag::LanguageTagParseError; use oxiri::IriParseError; -use std::error::Error; +use std::io; use std::sync::Arc; -use std::{fmt, io}; use thiserror::Error; /// Error returned during RDF/XML parsing. @@ -33,78 +32,45 @@ impl From for ParseError { quick_xml::Error::Io(error) => { Self::Io(Arc::try_unwrap(error).unwrap_or_else(|e| io::Error::new(e.kind(), e))) } - _ => Self::Syntax(SyntaxError { - inner: SyntaxErrorKind::Xml(error), - }), + _ => Self::Syntax(SyntaxError::Xml(error)), } } } /// An error in the syntax of the parsed file. -#[derive(Debug)] -pub struct SyntaxError { - pub(crate) inner: SyntaxErrorKind, -} - -#[derive(Debug)] -pub enum SyntaxErrorKind { - Xml(quick_xml::Error), +#[derive(Debug, Error)] +pub enum SyntaxError { + #[error(transparent)] + Xml(#[from] quick_xml::Error), + #[error("error while parsing IRI '{iri}': {error}")] InvalidIri { iri: String, + #[source] error: IriParseError, }, + #[error("error while parsing language tag '{tag}': {error}")] InvalidLanguageTag { tag: String, + #[source] error: LanguageTagParseError, }, - Msg { - msg: String, - }, + #[error("{msg}")] + Msg { msg: String }, } impl SyntaxError { /// Builds an error from a printable error message. #[inline] pub(crate) fn msg(msg: impl Into) -> Self { - Self { - inner: SyntaxErrorKind::Msg { msg: msg.into() }, - } - } -} - -impl fmt::Display for SyntaxError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.inner { - SyntaxErrorKind::Xml(error) => error.fmt(f), - SyntaxErrorKind::InvalidIri { iri, error } => { - write!(f, "error while parsing IRI '{iri}': {error}") - } - SyntaxErrorKind::InvalidLanguageTag { tag, error } => { - write!(f, "error while parsing language tag '{tag}': {error}") - } - SyntaxErrorKind::Msg { msg } => f.write_str(msg), - } - } -} - -impl Error for SyntaxError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.inner { - SyntaxErrorKind::Xml(error) => Some(error), - SyntaxErrorKind::InvalidIri { error, .. } => Some(error), - SyntaxErrorKind::InvalidLanguageTag { error, .. } => Some(error), - SyntaxErrorKind::Msg { .. } => None, - } + Self::Msg { msg: msg.into() } } } impl From for io::Error { #[inline] fn from(error: SyntaxError) -> Self { - match error.inner { - SyntaxErrorKind::Xml(error) => match error { + match error { + SyntaxError::Xml(error) => match error { quick_xml::Error::Io(error) => { Arc::try_unwrap(error).unwrap_or_else(|e| Self::new(e.kind(), e)) } @@ -113,7 +79,7 @@ impl From for io::Error { } _ => Self::new(io::ErrorKind::InvalidData, error), }, - SyntaxErrorKind::Msg { msg } => Self::new(io::ErrorKind::InvalidData, msg), + SyntaxError::Msg { msg } => Self::new(io::ErrorKind::InvalidData, msg), _ => Self::new(io::ErrorKind::InvalidData, error), } } diff --git a/lib/oxrdfxml/src/parser.rs b/lib/oxrdfxml/src/parser.rs index a4e35784..0ef2adf8 100644 --- a/lib/oxrdfxml/src/parser.rs +++ b/lib/oxrdfxml/src/parser.rs @@ -1,4 +1,4 @@ -use crate::error::{ParseError, SyntaxError, SyntaxErrorKind}; +use crate::error::{ParseError, SyntaxError}; use crate::utils::*; use oxilangtag::LanguageTag; use oxiri::{Iri, IriParseError}; @@ -575,9 +575,7 @@ impl RdfXmlReader { tag } else { LanguageTag::parse(tag.to_ascii_lowercase()) - .map_err(|error| SyntaxError { - inner: SyntaxErrorKind::InvalidLanguageTag { tag, error }, - })? + .map_err(|error| SyntaxError::InvalidLanguageTag { tag, error })? .into_inner() }); } else if attribute.key.as_ref() == b"xml:base" { @@ -588,9 +586,7 @@ impl RdfXmlReader { } else { Iri::parse(iri.clone()) } - .map_err(|error| SyntaxError { - inner: SyntaxErrorKind::InvalidIri { iri, error }, - })?, + .map_err(|error| SyntaxError::InvalidIri { iri, error })?, ) } else { // We ignore other xml attributes @@ -1169,11 +1165,9 @@ impl RdfXmlReader { } else { base_iri.resolve(&relative_iri) } - .map_err(|error| SyntaxError { - inner: SyntaxErrorKind::InvalidIri { - iri: relative_iri, - error, - }, + .map_err(|error| SyntaxError::InvalidIri { + iri: relative_iri, + error, })? .into_inner(), )) @@ -1187,11 +1181,9 @@ impl RdfXmlReader { relative_iri } else { Iri::parse(relative_iri.clone()) - .map_err(|error| SyntaxError { - inner: SyntaxErrorKind::InvalidIri { - iri: relative_iri, - error, - }, + .map_err(|error| SyntaxError::InvalidIri { + iri: relative_iri, + error, })? .into_inner() })) diff --git a/lib/oxsdatatypes/Cargo.toml b/lib/oxsdatatypes/Cargo.toml index fb63c125..0598244b 100644 --- a/lib/oxsdatatypes/Cargo.toml +++ b/lib/oxsdatatypes/Cargo.toml @@ -13,6 +13,9 @@ documentation = "https://docs.rs/oxsdatatypes" edition.workspace = true rust-version.workspace = true +[dependencies] +thiserror.workspace = true + [features] js = ["js-sys"] custom-now = [] diff --git a/lib/oxsdatatypes/src/date_time.rs b/lib/oxsdatatypes/src/date_time.rs index 1d165bbb..0a22473f 100644 --- a/lib/oxsdatatypes/src/date_time.rs +++ b/lib/oxsdatatypes/src/date_time.rs @@ -6,6 +6,7 @@ use std::error::Error; use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; +use thiserror::Error; /// [XML Schema `dateTime` datatype](https://www.w3.org/TR/xmlschema11-2/#dateTime) /// @@ -2039,42 +2040,24 @@ fn time_on_timeline(props: &DateTimeSevenPropertyModel) -> Option { } /// A parsing error -#[derive(Debug, Clone)] -pub struct ParseDateTimeError { - kind: ParseDateTimeErrorKind, -} - -#[derive(Debug, Clone)] -enum ParseDateTimeErrorKind { +#[derive(Debug, Clone, Error)] +pub enum ParseDateTimeError { + #[error("{day} is not a valid day of {month}")] InvalidDayOfMonth { day: u8, month: u8 }, - Overflow(DateTimeOverflowError), + #[error(transparent)] + Overflow(#[from] DateTimeOverflowError), + #[error(transparent)] InvalidTimezone(InvalidTimezoneError), + #[error("{0}")] Message(&'static str), } -impl fmt::Display for ParseDateTimeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.kind { - ParseDateTimeErrorKind::InvalidDayOfMonth { day, month } => { - write!(f, "{day} is not a valid day of {month}") - } - ParseDateTimeErrorKind::Overflow(error) => error.fmt(f), - ParseDateTimeErrorKind::InvalidTimezone(error) => error.fmt(f), - ParseDateTimeErrorKind::Message(msg) => f.write_str(msg), - } - } -} - impl ParseDateTimeError { const fn msg(message: &'static str) -> Self { - Self { - kind: ParseDateTimeErrorKind::Message(message), - } + Self::Message(message) } } -impl Error for ParseDateTimeError {} - // [16] dateTimeLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag 'T' ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag? fn date_time_lexical_rep(input: &str) -> Result<(DateTime, &str), ParseDateTimeError> { let (year, input) = year_frag(input)?; @@ -2326,11 +2309,8 @@ fn timezone_frag(input: &str) -> Result<(TimezoneOffset, &str), ParseDateTimeErr } Ok(( - TimezoneOffset::new(sign * (hours * 60 + i16::from(minutes))).map_err(|e| { - ParseDateTimeError { - kind: ParseDateTimeErrorKind::InvalidTimezone(e), - } - })?, + TimezoneOffset::new(sign * (hours * 60 + i16::from(minutes))) + .map_err(|e| ParseDateTimeError::InvalidTimezone(e))?, input, )) } @@ -2400,9 +2380,7 @@ fn optional_end( fn validate_day_of_month(year: Option, month: u8, day: u8) -> Result<(), ParseDateTimeError> { // Constraint: Day-of-month Values if day > days_in_month(year, month) { - return Err(ParseDateTimeError { - kind: ParseDateTimeErrorKind::InvalidDayOfMonth { day, month }, - }); + return Err(ParseDateTimeError::InvalidDayOfMonth { day, month }); } Ok(()) } @@ -2421,14 +2399,6 @@ impl fmt::Display for DateTimeOverflowError { impl Error for DateTimeOverflowError {} -impl From for ParseDateTimeError { - fn from(error: DateTimeOverflowError) -> Self { - Self { - kind: ParseDateTimeErrorKind::Overflow(error), - } - } -} - /// The value provided as timezone is not valid. /// /// Matches XPath [`FODT0003` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0003). diff --git a/lib/oxsdatatypes/src/decimal.rs b/lib/oxsdatatypes/src/decimal.rs index ee84b604..63f1c357 100644 --- a/lib/oxsdatatypes/src/decimal.rs +++ b/lib/oxsdatatypes/src/decimal.rs @@ -3,6 +3,7 @@ use std::error::Error; use std::fmt; use std::fmt::Write; use std::str::FromStr; +use thiserror::Error; const DECIMAL_PART_DIGITS: u32 = 18; const DECIMAL_PART_POW: i128 = 1_000_000_000_000_000_000; @@ -466,7 +467,7 @@ impl FromStr for Decimal { // (\+|-)?([0-9]+(\.[0-9]*)?|\.[0-9]+) let input = input.as_bytes(); if input.is_empty() { - return Err(PARSE_UNEXPECTED_END); + return Err(ParseDecimalError::UnexpectedEnd); } let (sign, mut input) = match input.first() { @@ -481,9 +482,9 @@ impl FromStr for Decimal { if c.is_ascii_digit() { value = value .checked_mul(10) - .ok_or(PARSE_OVERFLOW)? + .ok_or(ParseDecimalError::Overflow)? .checked_add(sign * i128::from(*c - b'0')) - .ok_or(PARSE_OVERFLOW)?; + .ok_or(ParseDecimalError::Overflow)?; input = &input[1..]; } else { break; @@ -493,12 +494,12 @@ impl FromStr for Decimal { let mut exp = DECIMAL_PART_POW; if let Some(c) = input.first() { if *c != b'.' { - return Err(PARSE_UNEXPECTED_CHAR); + return Err(ParseDecimalError::UnexpectedChar); } input = &input[1..]; if input.is_empty() && !with_before_dot { // We only have a dot - return Err(PARSE_UNEXPECTED_END); + return Err(ParseDecimalError::UnexpectedEnd); } while input.last() == Some(&b'0') { // Hack to avoid underflows @@ -509,25 +510,25 @@ impl FromStr for Decimal { exp /= 10; value = value .checked_mul(10) - .ok_or(PARSE_OVERFLOW)? + .ok_or(ParseDecimalError::Overflow)? .checked_add(sign * i128::from(*c - b'0')) - .ok_or(PARSE_OVERFLOW)?; + .ok_or(ParseDecimalError::Overflow)?; input = &input[1..]; } else { - return Err(PARSE_UNEXPECTED_CHAR); + return Err(ParseDecimalError::UnexpectedChar); } } if exp == 0 { // Underflow - return Err(PARSE_UNDERFLOW); + return Err(ParseDecimalError::Underflow); } } else if !with_before_dot { // It's empty - return Err(PARSE_UNEXPECTED_END); + return Err(ParseDecimalError::UnexpectedEnd); } Ok(Self { - value: value.checked_mul(exp).ok_or(PARSE_OVERFLOW)?, + value: value.checked_mul(exp).ok_or(ParseDecimalError::Overflow)?, }) } } @@ -609,50 +610,21 @@ impl fmt::Display for Decimal { } /// An error when parsing a [`Decimal`]. -#[derive(Debug, Clone)] -pub struct ParseDecimalError { - kind: DecimalParseErrorKind, -} - -#[derive(Debug, Clone)] -enum DecimalParseErrorKind { +#[derive(Debug, Clone, Error)] +pub enum ParseDecimalError { + #[error("Value overflow")] Overflow, + #[error("Value underflow")] Underflow, + #[error("Unexpected character")] UnexpectedChar, + #[error("Unexpected end of string")] UnexpectedEnd, } -const PARSE_OVERFLOW: ParseDecimalError = ParseDecimalError { - kind: DecimalParseErrorKind::Overflow, -}; -const PARSE_UNDERFLOW: ParseDecimalError = ParseDecimalError { - kind: DecimalParseErrorKind::Underflow, -}; -const PARSE_UNEXPECTED_CHAR: ParseDecimalError = ParseDecimalError { - kind: DecimalParseErrorKind::UnexpectedChar, -}; -const PARSE_UNEXPECTED_END: ParseDecimalError = ParseDecimalError { - kind: DecimalParseErrorKind::UnexpectedEnd, -}; - -impl fmt::Display for ParseDecimalError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.kind { - DecimalParseErrorKind::Overflow => f.write_str("Value overflow"), - DecimalParseErrorKind::Underflow => f.write_str("Value underflow"), - DecimalParseErrorKind::UnexpectedChar => f.write_str("Unexpected character"), - DecimalParseErrorKind::UnexpectedEnd => f.write_str("Unexpected end of string"), - } - } -} - -impl Error for ParseDecimalError {} - impl From for ParseDecimalError { fn from(_: TooLargeForDecimalError) -> Self { - Self { - kind: DecimalParseErrorKind::Overflow, - } + Self::Overflow } } diff --git a/lib/sparesults/src/csv.rs b/lib/sparesults/src/csv.rs index bd2fe4b1..7d975bd8 100644 --- a/lib/sparesults/src/csv.rs +++ b/lib/sparesults/src/csv.rs @@ -1,6 +1,6 @@ //! Implementation of [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/) -use crate::error::{ParseError, SyntaxError, SyntaxErrorKind, TextPosition}; +use crate::error::{ParseError, SyntaxError, TextPosition}; use memchr::memchr; use oxrdf::vocab::xsd; use oxrdf::*; @@ -508,23 +508,21 @@ impl TsvSolutionsReader { .sum::(); let start_position_bytes = line.split('\t').take(i).map(|c| c.len() + 1).sum::(); - SyntaxError { - inner: SyntaxErrorKind::Term { - error: e, - term: v.into(), - location: TextPosition { - line: self.reader.line_count - 1, - column: start_position_char.try_into().unwrap(), - offset: self.reader.last_line_start - + u64::try_from(start_position_bytes).unwrap(), - }..TextPosition { - line: self.reader.line_count - 1, - column: (start_position_char + v.chars().count()) - .try_into() - .unwrap(), - offset: self.reader.last_line_start - + u64::try_from(start_position_bytes + v.len()).unwrap(), - }, + SyntaxError::Term { + error: e, + term: v.into(), + location: TextPosition { + line: self.reader.line_count - 1, + column: start_position_char.try_into().unwrap(), + offset: self.reader.last_line_start + + u64::try_from(start_position_bytes).unwrap(), + }..TextPosition { + line: self.reader.line_count - 1, + column: (start_position_char + v.chars().count()) + .try_into() + .unwrap(), + offset: self.reader.last_line_start + + u64::try_from(start_position_bytes + v.len()).unwrap(), }, } })?)) diff --git a/lib/sparesults/src/error.rs b/lib/sparesults/src/error.rs index 6575a2ea..31c55ea2 100644 --- a/lib/sparesults/src/error.rs +++ b/lib/sparesults/src/error.rs @@ -1,8 +1,7 @@ use oxrdf::TermParseError; -use std::error::Error; +use std::io; use std::ops::Range; use std::sync::Arc; -use std::{fmt, io}; use thiserror::Error; /// Error returned during SPARQL result formats format parsing. @@ -42,28 +41,26 @@ impl From for ParseError { quick_xml::Error::Io(error) => { Self::Io(Arc::try_unwrap(error).unwrap_or_else(|e| io::Error::new(e.kind(), e))) } - _ => Self::Syntax(SyntaxError { - inner: SyntaxErrorKind::Xml(error), - }), + _ => Self::Syntax(SyntaxError::Xml(error)), } } } /// An error in the syntax of the parsed file. -#[derive(Debug)] -pub struct SyntaxError { - pub(crate) inner: SyntaxErrorKind, -} - -#[derive(Debug)] -pub(crate) enum SyntaxErrorKind { - Json(json_event_parser::SyntaxError), - Xml(quick_xml::Error), +#[derive(Debug, Error)] +pub enum SyntaxError { + #[error(transparent)] + Json(#[from] json_event_parser::SyntaxError), + #[error(transparent)] + Xml(#[from] quick_xml::Error), + #[error("Error {error} on '{term}' in line {}", location.start.line + 1)] Term { + #[source] error: TermParseError, term: String, location: Range, }, + #[error("{msg}")] Msg { msg: String, location: Option>, @@ -74,30 +71,26 @@ impl SyntaxError { /// Builds an error from a printable error message. #[inline] pub(crate) fn msg(msg: impl Into) -> Self { - Self { - inner: SyntaxErrorKind::Msg { - msg: msg.into(), - location: None, - }, + Self::Msg { + msg: msg.into(), + location: None, } } /// Builds an error from a printable error message and a location #[inline] pub(crate) fn located_message(msg: impl Into, location: Range) -> Self { - Self { - inner: SyntaxErrorKind::Msg { - msg: msg.into(), - location: Some(location), - }, + Self::Msg { + msg: msg.into(), + location: Some(location), } } /// The location of the error inside of the file. #[inline] pub fn location(&self) -> Option> { - match &self.inner { - SyntaxErrorKind::Json(e) => { + match self { + Self::Json(e) => { let location = e.location(); Some( TextPosition { @@ -111,37 +104,9 @@ impl SyntaxError { }, ) } - SyntaxErrorKind::Term { location, .. } => Some(location.clone()), - SyntaxErrorKind::Msg { location, .. } => location.clone(), - SyntaxErrorKind::Xml(_) => None, - } - } -} - -impl fmt::Display for SyntaxError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.inner { - SyntaxErrorKind::Json(e) => e.fmt(f), - SyntaxErrorKind::Xml(e) => e.fmt(f), - SyntaxErrorKind::Term { - error, - term, - location, - } => write!(f, "{error} on '{term}' in line {}", location.start.line + 1), - SyntaxErrorKind::Msg { msg, .. } => f.write_str(msg), - } - } -} - -impl Error for SyntaxError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.inner { - SyntaxErrorKind::Json(e) => Some(e), - SyntaxErrorKind::Xml(e) => Some(e), - SyntaxErrorKind::Term { error, .. } => Some(error), - SyntaxErrorKind::Msg { .. } => None, + Self::Term { location, .. } => Some(location.clone()), + Self::Msg { location, .. } => location.clone(), + Self::Xml(_) => None, } } } @@ -149,9 +114,9 @@ impl Error for SyntaxError { impl From for io::Error { #[inline] fn from(error: SyntaxError) -> Self { - match error.inner { - SyntaxErrorKind::Json(error) => Self::new(io::ErrorKind::InvalidData, error), - SyntaxErrorKind::Xml(error) => match error { + match error { + SyntaxError::Json(error) => Self::new(io::ErrorKind::InvalidData, error), + SyntaxError::Xml(error) => match error { quick_xml::Error::Io(error) => { Arc::try_unwrap(error).unwrap_or_else(|e| Self::new(e.kind(), e)) } @@ -160,16 +125,8 @@ impl From for io::Error { } _ => Self::new(io::ErrorKind::InvalidData, error), }, - SyntaxErrorKind::Term { .. } => Self::new(io::ErrorKind::InvalidData, error), - SyntaxErrorKind::Msg { msg, .. } => Self::new(io::ErrorKind::InvalidData, msg), - } - } -} - -impl From for SyntaxError { - fn from(error: json_event_parser::SyntaxError) -> Self { - Self { - inner: SyntaxErrorKind::Json(error), + SyntaxError::Term { .. } => Self::new(io::ErrorKind::InvalidData, error), + SyntaxError::Msg { msg, .. } => Self::new(io::ErrorKind::InvalidData, msg), } } } diff --git a/lib/spargebra/Cargo.toml b/lib/spargebra/Cargo.toml index 24acb482..f367c57c 100644 --- a/lib/spargebra/Cargo.toml +++ b/lib/spargebra/Cargo.toml @@ -25,6 +25,7 @@ oxiri.workspace = true oxrdf.workspace = true peg.workspace = true rand.workspace = true +thiserror.workspace = true [lints] workspace = true diff --git a/lib/spargebra/src/parser.rs b/lib/spargebra/src/parser.rs index bcb4d491..ff67818d 100644 --- a/lib/spargebra/src/parser.rs +++ b/lib/spargebra/src/parser.rs @@ -9,26 +9,22 @@ use oxrdf::vocab::{rdf, xsd}; use peg::parser; use peg::str::LineCol; use rand::random; +use std::char; use std::collections::{HashMap, HashSet}; -use std::error::Error; use std::mem::take; use std::str::FromStr; -use std::{char, fmt}; +use thiserror::Error; /// Parses a SPARQL query with an optional base IRI to resolve relative IRIs in the query. pub fn parse_query(query: &str, base_iri: Option<&str>) -> Result { let mut state = ParserState::from_base_iri(base_iri)?; - parser::QueryUnit(query, &mut state).map_err(|e| ParseError { - inner: ParseErrorKind::Parser(e), - }) + parser::QueryUnit(query, &mut state).map_err(|e| ParseError::Parser(e)) } /// Parses a SPARQL update with an optional base IRI to resolve relative IRIs in the query. pub fn parse_update(update: &str, base_iri: Option<&str>) -> Result { let mut state = ParserState::from_base_iri(base_iri)?; - let operations = parser::UpdateInit(update, &mut state).map_err(|e| ParseError { - inner: ParseErrorKind::Parser(e), - })?; + let operations = parser::UpdateInit(update, &mut state).map_err(|e| ParseError::Parser(e))?; Ok(Update { operations, base_iri: state.base_iri, @@ -36,37 +32,12 @@ pub fn parse_update(update: &str, base_iri: Option<&str>) -> Result), -} - -impl fmt::Display for ParseError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.inner { - ParseErrorKind::InvalidBaseIri(e) => { - write!(f, "Invalid SPARQL base IRI provided: {e}") - } - ParseErrorKind::Parser(e) => e.fmt(f), - } - } -} - -impl Error for ParseError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.inner { - ParseErrorKind::InvalidBaseIri(e) => Some(e), - ParseErrorKind::Parser(e) => Some(e), - } - } +#[derive(Debug, Error)] +pub enum ParseError { + #[error("Invalid SPARQL base IRI provided: {0}")] + InvalidBaseIri(#[from] IriParseError), + #[error(transparent)] + Parser(#[from] peg::error::ParseError), } struct AnnotatedTerm { @@ -697,9 +668,7 @@ impl ParserState { pub(crate) fn from_base_iri(base_iri: Option<&str>) -> Result { Ok(Self { base_iri: if let Some(base_iri) = base_iri { - Some(Iri::parse(base_iri.to_owned()).map_err(|e| ParseError { - inner: ParseErrorKind::InvalidBaseIri(e), - })?) + Some(Iri::parse(base_iri.to_owned()).map_err(|e| ParseError::InvalidBaseIri(e))?) } else { None }, From 487f61f704994eb971233cf14cf0f6d634f8a666 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 8 Feb 2024 19:05:57 -0500 Subject: [PATCH 05/15] a few more cleanups --- lib/oxigraph/src/sparql/error.rs | 3 +- lib/oxigraph/src/storage/error.rs | 9 +++--- lib/oxigraph/src/storage/small_string.rs | 3 +- lib/oxrdf/src/blank_node.rs | 13 ++------- lib/oxrdf/src/parser.rs | 3 +- lib/oxrdf/src/variable.rs | 13 ++------- lib/oxrdfio/src/error.rs | 5 ++-- lib/oxrdfxml/src/error.rs | 5 ++-- lib/oxsdatatypes/src/date_time.rs | 21 ++++---------- lib/oxsdatatypes/src/decimal.rs | 3 +- lib/oxsdatatypes/src/duration.rs | 35 +++++------------------- lib/oxttl/src/toolkit/error.rs | 8 ++---- lib/sparesults/src/error.rs | 5 ++-- lib/spargebra/src/parser.rs | 3 +- 14 files changed, 33 insertions(+), 96 deletions(-) diff --git a/lib/oxigraph/src/sparql/error.rs b/lib/oxigraph/src/sparql/error.rs index 35eba714..94ee6f57 100644 --- a/lib/oxigraph/src/sparql/error.rs +++ b/lib/oxigraph/src/sparql/error.rs @@ -6,10 +6,9 @@ use crate::storage::StorageError; use std::convert::Infallible; use std::error::Error; use std::io; -use thiserror::Error; /// A SPARQL evaluation error. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum EvaluationError { /// An error in SPARQL parsing. diff --git a/lib/oxigraph/src/storage/error.rs b/lib/oxigraph/src/storage/error.rs index 1b13bd9b..7a119637 100644 --- a/lib/oxigraph/src/storage/error.rs +++ b/lib/oxigraph/src/storage/error.rs @@ -2,10 +2,9 @@ use crate::io::{ParseError, RdfFormat}; use oxiri::IriParseError; use std::error::Error; use std::io; -use thiserror::Error; /// An error related to storage operations (reads, writes...). -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum StorageError { /// Error from the OS I/O layer. @@ -31,7 +30,7 @@ impl From for io::Error { } /// An error return if some content in the database is corrupted. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum CorruptionError { #[error("{0}")] Msg(String), @@ -61,7 +60,7 @@ impl From for io::Error { } /// An error raised while loading a file into a [`Store`](crate::store::Store). -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum LoaderError { /// An error raised while reading the file. #[error(transparent)] @@ -94,7 +93,7 @@ impl From for io::Error { } /// An error raised while writing a file from a [`Store`](crate::store::Store). -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum SerializerError { /// An error raised while writing the content. #[error(transparent)] diff --git a/lib/oxigraph/src/storage/small_string.rs b/lib/oxigraph/src/storage/small_string.rs index 0ffa3f3a..355606da 100644 --- a/lib/oxigraph/src/storage/small_string.rs +++ b/lib/oxigraph/src/storage/small_string.rs @@ -4,7 +4,6 @@ use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::str::{FromStr, Utf8Error}; use std::{fmt, str}; -use thiserror::Error; /// A small inline string #[derive(Clone, Copy, Default)] @@ -169,7 +168,7 @@ impl<'a> TryFrom<&'a str> for SmallString { } } -#[derive(Debug, Clone, Copy, Error)] +#[derive(Debug, Clone, Copy, thiserror::Error)] pub enum BadSmallStringError { #[error("small strings could only contain at most 15 characters, found {0}")] TooLong(usize), diff --git a/lib/oxrdf/src/blank_node.rs b/lib/oxrdf/src/blank_node.rs index ce07226b..6d565fda 100644 --- a/lib/oxrdf/src/blank_node.rs +++ b/lib/oxrdf/src/blank_node.rs @@ -1,5 +1,4 @@ use rand::random; -use std::error::Error; use std::io::Write; use std::{fmt, str}; @@ -345,18 +344,10 @@ fn to_integer_id(id: &str) -> Option { } /// An error raised during [`BlankNode`] IDs validation. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] +#[error("The blank node identifier is invalid")] pub struct BlankNodeIdParseError; -impl fmt::Display for BlankNodeIdParseError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("The blank node identifier is invalid") - } -} - -impl Error for BlankNodeIdParseError {} - #[cfg(test)] mod tests { #![allow(clippy::panic_in_result_fn)] diff --git a/lib/oxrdf/src/parser.rs b/lib/oxrdf/src/parser.rs index 2d6f1428..f89924f6 100644 --- a/lib/oxrdf/src/parser.rs +++ b/lib/oxrdf/src/parser.rs @@ -7,7 +7,6 @@ use crate::{ use crate::{Subject, Triple}; use std::char; use std::str::{Chars, FromStr}; -use thiserror::Error; /// This limit is set in order to avoid stack overflow error when parsing nested triples due to too many recursive calls. /// The actual limit value is a wet finger compromise between not failing to parse valid files and avoiding to trigger stack overflow errors. @@ -413,7 +412,7 @@ fn read_hexa_char(input: &mut Chars<'_>, len: usize) -> Result Result<(), VariableNameParseError> } /// An error raised during [`Variable`] name validation. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] +#[error("The variable name is invalid")] pub struct VariableNameParseError; - -impl fmt::Display for VariableNameParseError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("The variable name is invalid") - } -} - -impl Error for VariableNameParseError {} diff --git a/lib/oxrdfio/src/error.rs b/lib/oxrdfio/src/error.rs index 741ff6d9..ea8fe312 100644 --- a/lib/oxrdfio/src/error.rs +++ b/lib/oxrdfio/src/error.rs @@ -1,9 +1,8 @@ use std::io; use std::ops::Range; -use thiserror::Error; /// Error returned during RDF format parsing. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum ParseError { /// I/O error during parsing (file not found...). #[error(transparent)] @@ -50,7 +49,7 @@ impl From for io::Error { } /// An error in the syntax of the parsed file. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum SyntaxError { #[error(transparent)] Turtle(#[from] oxttl::SyntaxError), diff --git a/lib/oxrdfxml/src/error.rs b/lib/oxrdfxml/src/error.rs index 3ed9312d..1174f09d 100644 --- a/lib/oxrdfxml/src/error.rs +++ b/lib/oxrdfxml/src/error.rs @@ -2,10 +2,9 @@ use oxilangtag::LanguageTagParseError; use oxiri::IriParseError; use std::io; use std::sync::Arc; -use thiserror::Error; /// Error returned during RDF/XML parsing. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum ParseError { /// I/O error during parsing (file not found...). #[error(transparent)] @@ -38,7 +37,7 @@ impl From for ParseError { } /// An error in the syntax of the parsed file. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum SyntaxError { #[error(transparent)] Xml(#[from] quick_xml::Error), diff --git a/lib/oxsdatatypes/src/date_time.rs b/lib/oxsdatatypes/src/date_time.rs index 0a22473f..0f50f5dc 100644 --- a/lib/oxsdatatypes/src/date_time.rs +++ b/lib/oxsdatatypes/src/date_time.rs @@ -6,7 +6,6 @@ use std::error::Error; use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; -use thiserror::Error; /// [XML Schema `dateTime` datatype](https://www.w3.org/TR/xmlschema11-2/#dateTime) /// @@ -2040,7 +2039,7 @@ fn time_on_timeline(props: &DateTimeSevenPropertyModel) -> Option { } /// A parsing error -#[derive(Debug, Clone, Error)] +#[derive(Debug, Clone, thiserror::Error)] pub enum ParseDateTimeError { #[error("{day} is not a valid day of {month}")] InvalidDayOfMonth { day: u8, month: u8 }, @@ -2402,24 +2401,14 @@ impl Error for DateTimeOverflowError {} /// The value provided as timezone is not valid. /// /// Matches XPath [`FODT0003` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0003). -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, thiserror::Error)] +#[error("invalid timezone offset {}:{}", + self.offset_in_minutes / 60, + self.offset_in_minutes.abs() % 60)] pub struct InvalidTimezoneError { offset_in_minutes: i64, } -impl fmt::Display for InvalidTimezoneError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "invalid timezone offset {}:{}", - self.offset_in_minutes / 60, - self.offset_in_minutes.abs() % 60 - ) - } -} - -impl Error for InvalidTimezoneError {} - #[cfg(test)] mod tests { #![allow(clippy::panic_in_result_fn)] diff --git a/lib/oxsdatatypes/src/decimal.rs b/lib/oxsdatatypes/src/decimal.rs index 63f1c357..8005800d 100644 --- a/lib/oxsdatatypes/src/decimal.rs +++ b/lib/oxsdatatypes/src/decimal.rs @@ -3,7 +3,6 @@ use std::error::Error; use std::fmt; use std::fmt::Write; use std::str::FromStr; -use thiserror::Error; const DECIMAL_PART_DIGITS: u32 = 18; const DECIMAL_PART_POW: i128 = 1_000_000_000_000_000_000; @@ -610,7 +609,7 @@ impl fmt::Display for Decimal { } /// An error when parsing a [`Decimal`]. -#[derive(Debug, Clone, Error)] +#[derive(Debug, Clone, thiserror::Error)] pub enum ParseDecimalError { #[error("Value overflow")] Overflow, diff --git a/lib/oxsdatatypes/src/duration.rs b/lib/oxsdatatypes/src/duration.rs index 35f00bce..fe0514ab 100644 --- a/lib/oxsdatatypes/src/duration.rs +++ b/lib/oxsdatatypes/src/duration.rs @@ -1,6 +1,5 @@ use crate::{DateTime, Decimal}; use std::cmp::Ordering; -use std::error::Error; use std::fmt; use std::str::FromStr; use std::time::Duration as StdDuration; @@ -937,7 +936,8 @@ fn decimal_prefix(input: &str) -> (&str, &str) { } /// A parsing error -#[derive(Debug, Clone)] +#[derive(Debug, Clone, thiserror::Error)] +#[error("{msg}")] pub struct ParseDurationError { msg: &'static str, } @@ -946,46 +946,24 @@ const OVERFLOW_ERROR: ParseDurationError = ParseDurationError { msg: "Overflow error", }; -impl fmt::Display for ParseDurationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.msg) - } -} - impl ParseDurationError { const fn msg(msg: &'static str) -> Self { Self { msg } } } -impl Error for ParseDurationError {} - /// An overflow during [`Duration`]-related operations. /// /// Matches XPath [`FODT0002` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002). -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, thiserror::Error)] +#[error("overflow during xsd:duration computation")] pub struct DurationOverflowError; -impl fmt::Display for DurationOverflowError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("overflow during xsd:duration computation") - } -} - -impl Error for DurationOverflowError {} - /// The year-month and the day-time components of a [`Duration`] have an opposite sign. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, thiserror::Error)] +#[error("The xsd:yearMonthDuration and xsd:dayTimeDuration components of a xsd:duration can't have opposite sign")] pub struct OppositeSignInDurationComponentsError; -impl fmt::Display for OppositeSignInDurationComponentsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("The xsd:yearMonthDuration and xsd:dayTimeDuration components of a xsd:duration can't have opposite sign") - } -} - -impl Error for OppositeSignInDurationComponentsError {} - impl From for ParseDurationError { #[inline] fn from(_: OppositeSignInDurationComponentsError) -> Self { @@ -1000,6 +978,7 @@ mod tests { #![allow(clippy::panic_in_result_fn)] use super::*; + use std::error::Error; #[test] fn from_str() -> Result<(), ParseDurationError> { diff --git a/lib/oxttl/src/toolkit/error.rs b/lib/oxttl/src/toolkit/error.rs index a90de168..33f2a916 100644 --- a/lib/oxttl/src/toolkit/error.rs +++ b/lib/oxttl/src/toolkit/error.rs @@ -1,7 +1,5 @@ -use std::error::Error; use std::ops::Range; use std::{fmt, io}; -use thiserror::Error; /// A position in a text i.e. a `line` number starting from 0, a `column` number starting from 0 (in number of code points) and a global file `offset` starting from 0 (in number of bytes). #[derive(Eq, PartialEq, Debug, Clone, Copy)] @@ -14,7 +12,7 @@ pub struct TextPosition { /// An error in the syntax of the parsed file. /// /// It is composed of a message and a byte range in the input. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub struct SyntaxError { pub(super) location: Range, pub(super) message: String, @@ -68,8 +66,6 @@ impl fmt::Display for SyntaxError { } } -impl Error for SyntaxError {} - impl From for io::Error { #[inline] fn from(error: SyntaxError) -> Self { @@ -80,7 +76,7 @@ impl From for io::Error { /// A parsing error. /// /// It is the union of [`SyntaxError`] and [`io::Error`]. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum ParseError { /// I/O error during parsing (file not found...). #[error(transparent)] diff --git a/lib/sparesults/src/error.rs b/lib/sparesults/src/error.rs index 31c55ea2..4b1e4342 100644 --- a/lib/sparesults/src/error.rs +++ b/lib/sparesults/src/error.rs @@ -2,10 +2,9 @@ use oxrdf::TermParseError; use std::io; use std::ops::Range; use std::sync::Arc; -use thiserror::Error; /// Error returned during SPARQL result formats format parsing. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum ParseError { /// I/O error during parsing (file not found...). #[error(transparent)] @@ -47,7 +46,7 @@ impl From for ParseError { } /// An error in the syntax of the parsed file. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum SyntaxError { #[error(transparent)] Json(#[from] json_event_parser::SyntaxError), diff --git a/lib/spargebra/src/parser.rs b/lib/spargebra/src/parser.rs index ff67818d..ecd980a4 100644 --- a/lib/spargebra/src/parser.rs +++ b/lib/spargebra/src/parser.rs @@ -13,7 +13,6 @@ use std::char; use std::collections::{HashMap, HashSet}; use std::mem::take; use std::str::FromStr; -use thiserror::Error; /// Parses a SPARQL query with an optional base IRI to resolve relative IRIs in the query. pub fn parse_query(query: &str, base_iri: Option<&str>) -> Result { @@ -32,7 +31,7 @@ pub fn parse_update(update: &str, base_iri: Option<&str>) -> Result Date: Thu, 8 Feb 2024 19:33:01 -0500 Subject: [PATCH 06/15] and a few more --- lib/oxsdatatypes/src/date_time.rs | 13 +++---------- lib/oxsdatatypes/src/decimal.rs | 12 ++---------- lib/oxsdatatypes/src/integer.rs | 12 ++---------- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/lib/oxsdatatypes/src/date_time.rs b/lib/oxsdatatypes/src/date_time.rs index 0f50f5dc..1acb2197 100644 --- a/lib/oxsdatatypes/src/date_time.rs +++ b/lib/oxsdatatypes/src/date_time.rs @@ -2,7 +2,6 @@ use crate::{DayTimeDuration, Decimal, Duration, YearMonthDuration}; use std::cmp::{min, Ordering}; -use std::error::Error; use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; @@ -2387,17 +2386,10 @@ fn validate_day_of_month(year: Option, month: u8, day: u8) -> Result<(), Pa /// An overflow during [`DateTime`]-related operations. /// /// Matches XPath [`FODT0001` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001). -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, thiserror::Error)] +#[error("overflow during xsd:dateTime computation")] pub struct DateTimeOverflowError; -impl fmt::Display for DateTimeOverflowError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("overflow during xsd:dateTime computation") - } -} - -impl Error for DateTimeOverflowError {} - /// The value provided as timezone is not valid. /// /// Matches XPath [`FODT0003` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0003). @@ -2414,6 +2406,7 @@ mod tests { #![allow(clippy::panic_in_result_fn)] use super::*; + use std::error::Error; #[test] fn from_str() -> Result<(), ParseDateTimeError> { diff --git a/lib/oxsdatatypes/src/decimal.rs b/lib/oxsdatatypes/src/decimal.rs index 8005800d..ecb01af8 100644 --- a/lib/oxsdatatypes/src/decimal.rs +++ b/lib/oxsdatatypes/src/decimal.rs @@ -1,5 +1,4 @@ use crate::{Boolean, Double, Float, Integer, TooLargeForIntegerError}; -use std::error::Error; use std::fmt; use std::fmt::Write; use std::str::FromStr; @@ -630,17 +629,10 @@ impl From for ParseDecimalError { /// The input is too large to fit into a [`Decimal`]. /// /// Matches XPath [`FOCA0001` error](https://www.w3.org/TR/xpath-functions-31/#ERRFOCA0001). -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, thiserror::Error)] +#[error("Value too large for xsd:decimal internal representation")] pub struct TooLargeForDecimalError; -impl fmt::Display for TooLargeForDecimalError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Value too large for xsd:decimal internal representation") - } -} - -impl Error for TooLargeForDecimalError {} - #[cfg(test)] mod tests { #![allow(clippy::panic_in_result_fn)] diff --git a/lib/oxsdatatypes/src/integer.rs b/lib/oxsdatatypes/src/integer.rs index b28638d2..d0a693ea 100644 --- a/lib/oxsdatatypes/src/integer.rs +++ b/lib/oxsdatatypes/src/integer.rs @@ -1,5 +1,4 @@ use crate::{Boolean, Decimal, Double, Float}; -use std::error::Error; use std::fmt; use std::num::ParseIntError; use std::str::FromStr; @@ -264,17 +263,10 @@ impl TryFrom for Integer { /// The input is too large to fit into an [`Integer`]. /// /// Matches XPath [`FOCA0003` error](https://www.w3.org/TR/xpath-functions-31/#ERRFOCA0003). -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, thiserror::Error)] +#[error("Value too large for xsd:integer internal representation")] pub struct TooLargeForIntegerError; -impl fmt::Display for TooLargeForIntegerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Value too large for xsd:integer internal representation") - } -} - -impl Error for TooLargeForIntegerError {} - #[cfg(test)] mod tests { #![allow(clippy::panic_in_result_fn)] From 882984dd41fb864f6a44ed46d6f3ede739c56bd3 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 8 Feb 2024 19:43:31 -0500 Subject: [PATCH 07/15] And i think the last one --- lib/oxigraph/src/storage/backend/rocksdb.rs | 10 ++-------- lib/oxrdfio/src/error.rs | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/oxigraph/src/storage/backend/rocksdb.rs b/lib/oxigraph/src/storage/backend/rocksdb.rs index fed85421..184e30fe 100644 --- a/lib/oxigraph/src/storage/backend/rocksdb.rs +++ b/lib/oxigraph/src/storage/backend/rocksdb.rs @@ -1314,6 +1314,8 @@ impl SstFileWriter { } } +#[derive(thiserror::Error)] +#[error("{}", self.message())] struct ErrorStatus(rocksdb_status_t); unsafe impl Send for ErrorStatus {} @@ -1352,14 +1354,6 @@ impl fmt::Debug for ErrorStatus { } } -impl fmt::Display for ErrorStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.message()) - } -} - -impl Error for ErrorStatus {} - impl From for StorageError { fn from(status: ErrorStatus) -> Self { if status.0.code == rocksdb_status_code_t_rocksdb_status_code_io_error { diff --git a/lib/oxrdfio/src/error.rs b/lib/oxrdfio/src/error.rs index ea8fe312..4968e966 100644 --- a/lib/oxrdfio/src/error.rs +++ b/lib/oxrdfio/src/error.rs @@ -63,7 +63,7 @@ impl SyntaxError { /// The location of the error inside of the file. #[inline] pub fn location(&self) -> Option> { - match &self { + match self { Self::Turtle(e) => { let location = e.location(); Some( From abbae9be5ba741f7debcc932e615713dee3ca6ca Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 8 Feb 2024 20:16:04 -0500 Subject: [PATCH 08/15] clippy --- lib/oxsdatatypes/src/date_time.rs | 2 +- lib/spargebra/src/parser.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/oxsdatatypes/src/date_time.rs b/lib/oxsdatatypes/src/date_time.rs index 1acb2197..04e4a388 100644 --- a/lib/oxsdatatypes/src/date_time.rs +++ b/lib/oxsdatatypes/src/date_time.rs @@ -2308,7 +2308,7 @@ fn timezone_frag(input: &str) -> Result<(TimezoneOffset, &str), ParseDateTimeErr Ok(( TimezoneOffset::new(sign * (hours * 60 + i16::from(minutes))) - .map_err(|e| ParseDateTimeError::InvalidTimezone(e))?, + .map_err(ParseDateTimeError::InvalidTimezone)?, input, )) } diff --git a/lib/spargebra/src/parser.rs b/lib/spargebra/src/parser.rs index ecd980a4..df97fc6b 100644 --- a/lib/spargebra/src/parser.rs +++ b/lib/spargebra/src/parser.rs @@ -17,13 +17,13 @@ use std::str::FromStr; /// Parses a SPARQL query with an optional base IRI to resolve relative IRIs in the query. pub fn parse_query(query: &str, base_iri: Option<&str>) -> Result { let mut state = ParserState::from_base_iri(base_iri)?; - parser::QueryUnit(query, &mut state).map_err(|e| ParseError::Parser(e)) + parser::QueryUnit(query, &mut state).map_err(ParseError::Parser) } /// Parses a SPARQL update with an optional base IRI to resolve relative IRIs in the query. pub fn parse_update(update: &str, base_iri: Option<&str>) -> Result { let mut state = ParserState::from_base_iri(base_iri)?; - let operations = parser::UpdateInit(update, &mut state).map_err(|e| ParseError::Parser(e))?; + let operations = parser::UpdateInit(update, &mut state).map_err(ParseError::Parser)?; Ok(Update { operations, base_iri: state.base_iri, @@ -667,7 +667,7 @@ impl ParserState { pub(crate) fn from_base_iri(base_iri: Option<&str>) -> Result { Ok(Self { base_iri: if let Some(base_iri) = base_iri { - Some(Iri::parse(base_iri.to_owned()).map_err(|e| ParseError::InvalidBaseIri(e))?) + Some(Iri::parse(base_iri.to_owned()).map_err(ParseError::InvalidBaseIri)?) } else { None }, From 6c73370bba32a7fee0e98d97cba68f10f858bcda Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 8 Feb 2024 21:40:58 -0500 Subject: [PATCH 09/15] lower thiserror req --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cab761e0..203ae7a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ sha1 = "0.10" sha2 = "0.10" siphasher = ">=0.3, <2.0" text-diff = "0.4" -thiserror = "1.0.56" +thiserror = "1.0.50" time = "0.3" tokio = "1.29" url = "2.4" From b43b0ab99143e781648b463a23d901518eea28a4 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 9 Feb 2024 00:00:10 -0500 Subject: [PATCH 10/15] move deps after features --- lib/oxsdatatypes/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/oxsdatatypes/Cargo.toml b/lib/oxsdatatypes/Cargo.toml index 0598244b..d0e8aafd 100644 --- a/lib/oxsdatatypes/Cargo.toml +++ b/lib/oxsdatatypes/Cargo.toml @@ -13,13 +13,13 @@ documentation = "https://docs.rs/oxsdatatypes" edition.workspace = true rust-version.workspace = true -[dependencies] -thiserror.workspace = true - [features] js = ["js-sys"] custom-now = [] +[dependencies] +thiserror.workspace = true + [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] js-sys = { workspace = true, optional = true } From a5392a42b2d2a17cf2a5a0140fce9f2e4e6b75ed Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 18 Jan 2024 03:18:57 -0500 Subject: [PATCH 11/15] wip: add WKT support This is a very early draft, I have no idea where i am going with this. Any feedback / guidance is welcome --- Cargo.lock | 18 +- Cargo.toml | 1 + lib/oxsdatatypes/Cargo.toml | 1 + lib/oxsdatatypes/src/geopoint.rs | 1098 ++++++++++++++++++++++++++++++ lib/oxsdatatypes/src/lib.rs | 1 + 5 files changed, 1116 insertions(+), 3 deletions(-) create mode 100644 lib/oxsdatatypes/src/geopoint.rs diff --git a/Cargo.lock b/Cargo.lock index 85d35123..4a5b8aaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -751,12 +751,12 @@ checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "fe8f25ce1159c7740ff0b9b2f5cdf4a8428742ba7c112b9f20f22cd5219c7dab" dependencies = [ "hermit-abi", - "rustix", + "libc", "windows-sys 0.52.0", ] @@ -1178,6 +1178,7 @@ version = "0.2.0-alpha.1" dependencies = [ "js-sys", "thiserror", + "wkt", ] [[package]] @@ -2313,6 +2314,17 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "wkt" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c2252781f8927974e8ba6a67c965a759a2b88ea2b1825f6862426bbb1c8f41" +dependencies = [ + "log", + "num-traits", + "thiserror", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 203ae7a1..be9f3a0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ time = "0.3" tokio = "1.29" url = "2.4" wasm-bindgen = "0.2.83" +wkt = { version = "0.10.3", default-features = false } zstd = ">=0.12, <0.14" # Internal dependencies diff --git a/lib/oxsdatatypes/Cargo.toml b/lib/oxsdatatypes/Cargo.toml index d0e8aafd..21ac4ea6 100644 --- a/lib/oxsdatatypes/Cargo.toml +++ b/lib/oxsdatatypes/Cargo.toml @@ -19,6 +19,7 @@ custom-now = [] [dependencies] thiserror.workspace = true +wkt.workspace = true [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] js-sys = { workspace = true, optional = true } diff --git a/lib/oxsdatatypes/src/geopoint.rs b/lib/oxsdatatypes/src/geopoint.rs new file mode 100644 index 00000000..2038e965 --- /dev/null +++ b/lib/oxsdatatypes/src/geopoint.rs @@ -0,0 +1,1098 @@ +use std::str::FromStr; +use wkt::Wkt; +// use std::time::Geo as StdDuration; + +/// [XML Schema `duration` datatype](https://www.w3.org/TR/xmlschema11-2/#duration) +/// +/// It stores the duration using a pair of a [`YearMonthDuration`] and a [`DayTimeDuration`]. +#[derive(Debug, Clone)] +pub struct GeoPoint { + geom: wkt::Wkt, +} + +type WktError = &'static str; + +impl GeoPoint { + #[inline] + pub fn new() -> Result { + Self::from_str("POINT(0 0)") + } +} + +impl FromStr for GeoPoint { + type Err = WktError; + + fn from_str(input: &str) -> Result { + Ok(Self { + geom: Wkt::from_str(input)?, + }) + } +} +// +// impl fmt::Display for GeoPoint { +// #[allow(clippy::many_single_char_names)] +// #[inline] +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// let ym = self.year_month.months; +// let ss = self.day_time.seconds; +// +// if (ym < 0 && ss > 0.into()) || (ym > 0 && ss < 0.into()) { +// return Err(fmt::Error); // Not able to format with only a part of the duration that is negative +// } +// if ym < 0 || ss < 0.into() { +// write!(f, "-")?; +// } +// write!(f, "P")?; +// +// if ym == 0 && ss == 0.into() { +// return write!(f, "T0S"); +// } +// +// { +// let y = ym / 12; +// let m = ym % 12; +// +// if y != 0 { +// if m == 0 { +// write!(f, "{}Y", y.abs())?; +// } else { +// write!(f, "{}Y{}M", y.abs(), m.abs())?; +// } +// } else if m != 0 || ss == 0.into() { +// write!(f, "{}M", m.abs())?; +// } +// } +// +// { +// let s_int = ss.as_i128(); +// let d = s_int / 86400; +// let h = (s_int % 86400) / 3600; +// let m = (s_int % 3600) / 60; +// let s = ss +// .checked_sub( +// Decimal::try_from(d * 86400 + h * 3600 + m * 60).map_err(|_| fmt::Error)?, +// ) +// .ok_or(fmt::Error)?; +// +// if d != 0 { +// write!(f, "{}D", d.abs())?; +// } +// +// if h != 0 || m != 0 || s != 0.into() { +// write!(f, "T")?; +// if h != 0 { +// write!(f, "{}H", h.abs())?; +// } +// if m != 0 { +// write!(f, "{}M", m.abs())?; +// } +// if s != 0.into() { +// write!(f, "{}S", s.checked_abs().ok_or(fmt::Error)?)?; +// } +// } +// } +// Ok(()) +// } +// } +// +// impl PartialOrd for GeoPoint { +// #[inline] +// fn partial_cmp(&self, other: &Self) -> Option { +// let first = DateTime::new(1969, 9, 1, 0, 0, 0.into(), None).ok()?; +// let first_result = first +// .checked_add_duration(*self)? +// .partial_cmp(&first.checked_add_duration(*other)?); +// let second = DateTime::new(1697, 2, 1, 0, 0, 0.into(), None).ok()?; +// let second_result = second +// .checked_add_duration(*self)? +// .partial_cmp(&second.checked_add_duration(*other)?); +// let third = DateTime::new(1903, 3, 1, 0, 0, 0.into(), None).ok()?; +// let third_result = third +// .checked_add_duration(*self)? +// .partial_cmp(&third.checked_add_duration(*other)?); +// let fourth = DateTime::new(1903, 7, 1, 0, 0, 0.into(), None).ok()?; +// let fourth_result = fourth +// .checked_add_duration(*self)? +// .partial_cmp(&fourth.checked_add_duration(*other)?); +// if first_result == second_result +// && second_result == third_result +// && third_result == fourth_result +// { +// first_result +// } else { +// None +// } +// } +// } +// +// /// [XML Schema `yearMonthDuration` datatype](https://www.w3.org/TR/xmlschema11-2/#yearMonthDuration) +// /// +// /// It stores the duration as a number of months encoded using a [`i64`]. +// #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash, Default)] +// pub struct YearMonthDuration { +// months: i64, +// } +// +// impl YearMonthDuration { +// #[inline] +// pub fn new(months: impl Into) -> Self { +// Self { +// months: months.into(), +// } +// } +// +// #[inline] +// pub fn from_be_bytes(bytes: [u8; 8]) -> Self { +// Self { +// months: i64::from_be_bytes(bytes), +// } +// } +// +// /// [fn:years-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-years-from-duration) +// #[inline] +// pub fn years(self) -> i64 { +// self.months / 12 +// } +// +// /// [fn:months-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-months-from-duration) +// #[inline] +// pub fn months(self) -> i64 { +// self.months % 12 +// } +// +// #[inline] +// pub(crate) const fn all_months(self) -> i64 { +// self.months +// } +// +// #[inline] +// pub fn to_be_bytes(self) -> [u8; 8] { +// self.months.to_be_bytes() +// } +// +// /// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDurations) +// /// +// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). +// #[inline] +// pub fn checked_add(self, rhs: impl Into) -> Option { +// let rhs = rhs.into(); +// Some(Self { +// months: self.months.checked_add(rhs.months)?, +// }) +// } +// +// /// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDurations) +// /// +// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). +// #[inline] +// pub fn checked_sub(self, rhs: impl Into) -> Option { +// let rhs = rhs.into(); +// Some(Self { +// months: self.months.checked_sub(rhs.months)?, +// }) +// } +// +// /// Unary negation. +// /// +// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). +// #[inline] +// pub fn checked_neg(self) -> Option { +// Some(Self { +// months: self.months.checked_neg()?, +// }) +// } +// +// /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity). +// #[inline] +// pub fn is_identical_with(self, other: Self) -> bool { +// self == other +// } +// +// pub const MIN: Self = Self { months: i64::MIN }; +// +// pub const MAX: Self = Self { months: i64::MAX }; +// } +// +// impl From for GeoPoint { +// #[inline] +// fn from(value: YearMonthDuration) -> Self { +// Self { +// year_month: value, +// day_time: DayTimeDuration::default(), +// } +// } +// } +// +// impl TryFrom for YearMonthDuration { +// type Error = DurationOverflowError; +// +// #[inline] +// fn try_from(value: GeoPoint) -> Result { +// if value.day_time == DayTimeDuration::default() { +// Ok(value.year_month) +// } else { +// Err(DurationOverflowError) +// } +// } +// } +// +// impl FromStr for YearMonthDuration { +// type Err = ParseDurationError; +// +// fn from_str(input: &str) -> Result { +// let parts = ensure_complete(input, duration_parts)?; +// if parts.day_time.is_some() { +// return Err(ParseDurationError::msg( +// "There must not be any day or time component in a yearMonthDuration", +// )); +// } +// Ok(Self::new(parts.year_month.ok_or( +// ParseDurationError::msg("No year and month values found"), +// )?)) +// } +// } +// +// impl fmt::Display for YearMonthDuration { +// #[inline] +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// if self.months == 0 { +// write!(f, "P0M") +// } else { +// GeoPoint::from(*self).fmt(f) +// } +// } +// } +// +// impl PartialEq for YearMonthDuration { +// #[inline] +// fn eq(&self, other: &GeoPoint) -> bool { +// GeoPoint::from(*self).eq(other) +// } +// } +// +// impl PartialEq for GeoPoint { +// #[inline] +// fn eq(&self, other: &YearMonthDuration) -> bool { +// self.eq(&Self::from(*other)) +// } +// } +// +// impl PartialOrd for YearMonthDuration { +// #[inline] +// fn partial_cmp(&self, other: &GeoPoint) -> Option { +// GeoPoint::from(*self).partial_cmp(other) +// } +// } +// +// impl PartialOrd for GeoPoint { +// #[inline] +// fn partial_cmp(&self, other: &YearMonthDuration) -> Option { +// self.partial_cmp(&Self::from(*other)) +// } +// } +// +// /// [XML Schema `dayTimeDuration` datatype](https://www.w3.org/TR/xmlschema11-2/#dayTimeDuration) +// /// +// /// It stores the duration as a number of seconds encoded using a [`Decimal`]. +// #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash, Default)] +// pub struct DayTimeDuration { +// seconds: Decimal, +// } +// +// impl DayTimeDuration { +// #[inline] +// pub fn new(seconds: impl Into) -> Self { +// Self { +// seconds: seconds.into(), +// } +// } +// +// #[inline] +// pub fn from_be_bytes(bytes: [u8; 16]) -> Self { +// Self { +// seconds: Decimal::from_be_bytes(bytes), +// } +// } +// +// /// [fn:days-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-days-from-duration) +// #[allow(clippy::cast_possible_truncation)] +// #[inline] +// pub fn days(self) -> i64 { +// (self.seconds.as_i128() / 86400) as i64 +// } +// +// /// [fn:hours-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-hours-from-duration) +// #[allow(clippy::cast_possible_truncation)] +// #[inline] +// pub fn hours(self) -> i64 { +// ((self.seconds.as_i128() % 86400) / 3600) as i64 +// } +// +// /// [fn:minutes-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-minutes-from-duration) +// #[allow(clippy::cast_possible_truncation)] +// #[inline] +// pub fn minutes(self) -> i64 { +// ((self.seconds.as_i128() % 3600) / 60) as i64 +// } +// +// /// [fn:seconds-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-seconds-from-duration) +// #[inline] +// pub fn seconds(self) -> Decimal { +// self.seconds.checked_rem(60).unwrap() +// } +// +// /// The duration in seconds. +// #[inline] +// pub const fn as_seconds(self) -> Decimal { +// self.seconds +// } +// +// #[inline] +// pub fn to_be_bytes(self) -> [u8; 16] { +// self.seconds.to_be_bytes() +// } +// +// /// [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDurations) +// /// +// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). +// #[inline] +// pub fn checked_add(self, rhs: impl Into) -> Option { +// let rhs = rhs.into(); +// Some(Self { +// seconds: self.seconds.checked_add(rhs.seconds)?, +// }) +// } +// +// /// [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDurations) +// /// +// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). +// #[inline] +// pub fn checked_sub(self, rhs: impl Into) -> Option { +// let rhs = rhs.into(); +// Some(Self { +// seconds: self.seconds.checked_sub(rhs.seconds)?, +// }) +// } +// +// /// Unary negation. +// /// +// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). +// #[inline] +// pub fn checked_neg(self) -> Option { +// Some(Self { +// seconds: self.seconds.checked_neg()?, +// }) +// } +// +// /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity). +// #[inline] +// pub fn is_identical_with(self, other: Self) -> bool { +// self == other +// } +// +// pub const MIN: Self = Self { +// seconds: Decimal::MIN, +// }; +// +// pub const MAX: Self = Self { +// seconds: Decimal::MAX, +// }; +// } +// +// impl From for GeoPoint { +// #[inline] +// fn from(value: DayTimeDuration) -> Self { +// Self { +// year_month: YearMonthDuration::default(), +// day_time: value, +// } +// } +// } +// +// impl TryFrom for DayTimeDuration { +// type Error = DurationOverflowError; +// +// #[inline] +// fn try_from(value: GeoPoint) -> Result { +// if value.year_month == YearMonthDuration::default() { +// Ok(value.day_time) +// } else { +// Err(DurationOverflowError) +// } +// } +// } +// +// impl TryFrom for DayTimeDuration { +// type Error = DurationOverflowError; +// +// #[inline] +// fn try_from(value: StdDuration) -> Result { +// Ok(Self { +// seconds: Decimal::new( +// i128::try_from(value.as_nanos()).map_err(|_| DurationOverflowError)?, +// 9, +// ) +// .map_err(|_| DurationOverflowError)?, +// }) +// } +// } +// +// impl TryFrom for StdDuration { +// type Error = DurationOverflowError; +// +// #[inline] +// fn try_from(value: DayTimeDuration) -> Result { +// if value.seconds.is_negative() { +// return Err(DurationOverflowError); +// } +// let secs = value.seconds.checked_floor().ok_or(DurationOverflowError)?; +// let nanos = value +// .seconds +// .checked_sub(secs) +// .ok_or(DurationOverflowError)? +// .checked_mul(1_000_000_000) +// .ok_or(DurationOverflowError)? +// .checked_floor() +// .ok_or(DurationOverflowError)?; +// Ok(StdDuration::new( +// secs.as_i128() +// .try_into() +// .map_err(|_| DurationOverflowError)?, +// nanos +// .as_i128() +// .try_into() +// .map_err(|_| DurationOverflowError)?, +// )) +// } +// } +// +// impl FromStr for DayTimeDuration { +// type Err = ParseDurationError; +// +// fn from_str(input: &str) -> Result { +// let parts = ensure_complete(input, duration_parts)?; +// if parts.year_month.is_some() { +// return Err(ParseDurationError::msg( +// "There must not be any year or month component in a dayTimeDuration", +// )); +// } +// Ok(Self::new(parts.day_time.ok_or(ParseDurationError::msg( +// "No day or time values found", +// ))?)) +// } +// } +// +// impl fmt::Display for DayTimeDuration { +// #[inline] +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// GeoPoint::from(*self).fmt(f) +// } +// } +// +// impl PartialEq for DayTimeDuration { +// #[inline] +// fn eq(&self, other: &GeoPoint) -> bool { +// GeoPoint::from(*self).eq(other) +// } +// } +// +// impl PartialEq for GeoPoint { +// #[inline] +// fn eq(&self, other: &DayTimeDuration) -> bool { +// self.eq(&Self::from(*other)) +// } +// } +// +// impl PartialEq for DayTimeDuration { +// #[inline] +// fn eq(&self, other: &YearMonthDuration) -> bool { +// GeoPoint::from(*self).eq(&GeoPoint::from(*other)) +// } +// } +// +// impl PartialEq for YearMonthDuration { +// #[inline] +// fn eq(&self, other: &DayTimeDuration) -> bool { +// GeoPoint::from(*self).eq(&GeoPoint::from(*other)) +// } +// } +// +// impl PartialOrd for DayTimeDuration { +// #[inline] +// fn partial_cmp(&self, other: &GeoPoint) -> Option { +// GeoPoint::from(*self).partial_cmp(other) +// } +// } +// +// impl PartialOrd for GeoPoint { +// #[inline] +// fn partial_cmp(&self, other: &DayTimeDuration) -> Option { +// self.partial_cmp(&Self::from(*other)) +// } +// } +// +// impl PartialOrd for DayTimeDuration { +// #[inline] +// fn partial_cmp(&self, other: &YearMonthDuration) -> Option { +// GeoPoint::from(*self).partial_cmp(&GeoPoint::from(*other)) +// } +// } +// +// impl PartialOrd for YearMonthDuration { +// #[inline] +// fn partial_cmp(&self, other: &DayTimeDuration) -> Option { +// GeoPoint::from(*self).partial_cmp(&GeoPoint::from(*other)) +// } +// } +// +// // [6] duYearFrag ::= unsignedNoDecimalPtNumeral 'Y' +// // [7] duMonthFrag ::= unsignedNoDecimalPtNumeral 'M' +// // [8] duDayFrag ::= unsignedNoDecimalPtNumeral 'D' +// // [9] duHourFrag ::= unsignedNoDecimalPtNumeral 'H' +// // [10] duMinuteFrag ::= unsignedNoDecimalPtNumeral 'M' +// // [11] duSecondFrag ::= (unsignedNoDecimalPtNumeral | unsignedDecimalPtNumeral) 'S' +// // [12] duYearMonthFrag ::= (duYearFrag duMonthFrag?) | duMonthFrag +// // [13] duTimeFrag ::= 'T' ((duHourFrag duMinuteFrag? duSecondFrag?) | (duMinuteFrag duSecondFrag?) | duSecondFrag) +// // [14] duDayTimeFrag ::= (duDayFrag duTimeFrag?) | duTimeFrag +// // [15] durationLexicalRep ::= '-'? 'P' ((duYearMonthFrag duDayTimeFrag?) | duDayTimeFrag) +// struct DurationParts { +// year_month: Option, +// day_time: Option, +// } +// +// fn duration_parts(input: &str) -> Result<(DurationParts, &str), ParseDurationError> { +// // States +// const START: u32 = 0; +// const AFTER_YEAR: u32 = 1; +// const AFTER_MONTH: u32 = 2; +// const AFTER_DAY: u32 = 3; +// const AFTER_T: u32 = 4; +// const AFTER_HOUR: u32 = 5; +// const AFTER_MINUTE: u32 = 6; +// const AFTER_SECOND: u32 = 7; +// +// let (is_negative, input) = if let Some(left) = input.strip_prefix('-') { +// (true, left) +// } else { +// (false, input) +// }; +// let mut input = expect_char(input, 'P', "Durations must start with 'P'")?; +// let mut state = START; +// let mut year_month: Option = None; +// let mut day_time: Option = None; +// while !input.is_empty() { +// if let Some(left) = input.strip_prefix('T') { +// if state >= AFTER_T { +// return Err(ParseDurationError::msg("Duplicated time separator 'T'")); +// } +// state = AFTER_T; +// input = left; +// } else { +// let (number_str, left) = decimal_prefix(input); +// match left.chars().next() { +// Some('Y') if state < AFTER_YEAR => { +// year_month = Some( +// year_month +// .unwrap_or_default() +// .checked_add( +// apply_i64_neg( +// i64::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, +// is_negative, +// )? +// .checked_mul(12) +// .ok_or(OVERFLOW_ERROR)?, +// ) +// .ok_or(OVERFLOW_ERROR)?, +// ); +// state = AFTER_YEAR; +// } +// Some('M') if state < AFTER_MONTH => { +// year_month = Some( +// year_month +// .unwrap_or_default() +// .checked_add(apply_i64_neg( +// i64::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, +// is_negative, +// )?) +// .ok_or(OVERFLOW_ERROR)?, +// ); +// state = AFTER_MONTH; +// } +// Some('D') if state < AFTER_DAY => { +// if number_str.contains('.') { +// return Err(ParseDurationError::msg( +// "Decimal numbers are not allowed for days", +// )); +// } +// day_time = Some( +// day_time +// .unwrap_or_default() +// .checked_add( +// apply_decimal_neg( +// Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, +// is_negative, +// )? +// .checked_mul(86400) +// .ok_or(OVERFLOW_ERROR)?, +// ) +// .ok_or(OVERFLOW_ERROR)?, +// ); +// state = AFTER_DAY; +// } +// Some('H') if state == AFTER_T => { +// if number_str.contains('.') { +// return Err(ParseDurationError::msg( +// "Decimal numbers are not allowed for hours", +// )); +// } +// day_time = Some( +// day_time +// .unwrap_or_default() +// .checked_add( +// apply_decimal_neg( +// Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, +// is_negative, +// )? +// .checked_mul(3600) +// .ok_or(OVERFLOW_ERROR)?, +// ) +// .ok_or(OVERFLOW_ERROR)?, +// ); +// state = AFTER_HOUR; +// } +// Some('M') if (AFTER_T..AFTER_MINUTE).contains(&state) => { +// if number_str.contains('.') { +// return Err(ParseDurationError::msg( +// "Decimal numbers are not allowed for minutes", +// )); +// } +// day_time = Some( +// day_time +// .unwrap_or_default() +// .checked_add( +// apply_decimal_neg( +// Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, +// is_negative, +// )? +// .checked_mul(60) +// .ok_or(OVERFLOW_ERROR)?, +// ) +// .ok_or(OVERFLOW_ERROR)?, +// ); +// state = AFTER_MINUTE; +// } +// Some('S') if (AFTER_T..AFTER_SECOND).contains(&state) => { +// day_time = Some( +// day_time +// .unwrap_or_default() +// .checked_add(apply_decimal_neg( +// Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, +// is_negative, +// )?) +// .ok_or(OVERFLOW_ERROR)?, +// ); +// state = AFTER_SECOND; +// } +// Some(_) => return Err(ParseDurationError::msg("Unexpected type character")), +// None => { +// return Err(ParseDurationError::msg( +// "Numbers in durations must be followed by a type character", +// )) +// } +// } +// input = &left[1..]; +// } +// } +// +// Ok(( +// DurationParts { +// year_month, +// day_time, +// }, +// input, +// )) +// } +// +// fn apply_i64_neg(value: i64, is_negative: bool) -> Result { +// if is_negative { +// value.checked_neg().ok_or(OVERFLOW_ERROR) +// } else { +// Ok(value) +// } +// } +// +// fn apply_decimal_neg(value: Decimal, is_negative: bool) -> Result { +// if is_negative { +// value.checked_neg().ok_or(OVERFLOW_ERROR) +// } else { +// Ok(value) +// } +// } +// +// fn ensure_complete( +// input: &str, +// parse: impl FnOnce(&str) -> Result<(T, &str), ParseDurationError>, +// ) -> Result { +// let (result, left) = parse(input)?; +// if !left.is_empty() { +// return Err(ParseDurationError::msg("Unrecognized value suffix")); +// } +// Ok(result) +// } +// +// fn expect_char<'a>( +// input: &'a str, +// constant: char, +// error_message: &'static str, +// ) -> Result<&'a str, ParseDurationError> { +// if let Some(left) = input.strip_prefix(constant) { +// Ok(left) +// } else { +// Err(ParseDurationError::msg(error_message)) +// } +// } +// +// fn decimal_prefix(input: &str) -> (&str, &str) { +// let mut end = input.len(); +// let mut dot_seen = false; +// for (i, c) in input.char_indices() { +// if c.is_ascii_digit() { +// // Ok +// } else if c == '.' && !dot_seen { +// dot_seen = true; +// } else { +// end = i; +// break; +// } +// } +// input.split_at(end) +// } +// +// /// A parsing error +// #[derive(Debug, Clone)] +// pub struct ParseDurationError { +// msg: &'static str, +// } +// +// const OVERFLOW_ERROR: ParseDurationError = ParseDurationError { +// msg: "Overflow error", +// }; +// +// impl fmt::Display for ParseDurationError { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// write!(f, "{}", self.msg) +// } +// } +// +// impl ParseDurationError { +// const fn msg(msg: &'static str) -> Self { +// Self { msg } +// } +// } +// +// impl Error for ParseDurationError {} +// +// /// An overflow during [`GeoPoint`]-related operations. +// /// +// /// Matches XPath [`FODT0002` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002). +// #[derive(Debug, Clone, Copy)] +// pub struct DurationOverflowError; +// +// impl fmt::Display for DurationOverflowError { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// write!(f, "overflow during xsd:duration computation") +// } +// } +// +// impl Error for DurationOverflowError {} +// +// /// The year-month and the day-time components of a [`GeoPoint`] have an opposite sign. +// #[derive(Debug, Clone, Copy)] +// pub struct OppositeSignInDurationComponentsError; +// +// impl fmt::Display for OppositeSignInDurationComponentsError { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// write!(f, "The xsd:yearMonthDuration and xsd:dayTimeDuration components of a xsd:duration can't have opposite sign") +// } +// } +// +// impl Error for OppositeSignInDurationComponentsError {} +// +// impl From for ParseDurationError { +// #[inline] +// fn from(_: OppositeSignInDurationComponentsError) -> Self { +// Self { +// msg: "The xsd:yearMonthDuration and xsd:dayTimeDuration components of a xsd:duration can't have opposite sign" +// } +// } +// } +// +// #[cfg(test)] +// mod tests { +// #![allow(clippy::panic_in_result_fn)] +// +// use super::*; +// +// #[test] +// fn from_str() -> Result<(), ParseDurationError> { +// let min = GeoPoint::new(i64::MIN, Decimal::MIN)?; +// let max = GeoPoint::new(i64::MAX, Decimal::MAX)?; +// +// assert_eq!(YearMonthDuration::from_str("P1Y")?.to_string(), "P1Y"); +// assert_eq!(GeoPoint::from_str("P1Y")?.to_string(), "P1Y"); +// assert_eq!(YearMonthDuration::from_str("P1M")?.to_string(), "P1M"); +// assert_eq!(GeoPoint::from_str("P1M")?.to_string(), "P1M"); +// assert_eq!(DayTimeDuration::from_str("P1D")?.to_string(), "P1D"); +// assert_eq!(GeoPoint::from_str("P1D")?.to_string(), "P1D"); +// assert_eq!(DayTimeDuration::from_str("PT1H")?.to_string(), "PT1H"); +// assert_eq!(GeoPoint::from_str("PT1H")?.to_string(), "PT1H"); +// assert_eq!(DayTimeDuration::from_str("PT1M")?.to_string(), "PT1M"); +// assert_eq!(GeoPoint::from_str("PT1M")?.to_string(), "PT1M"); +// assert_eq!(DayTimeDuration::from_str("PT1.1S")?.to_string(), "PT1.1S"); +// assert_eq!(GeoPoint::from_str("PT1.1S")?.to_string(), "PT1.1S"); +// assert_eq!(YearMonthDuration::from_str("-P1Y")?.to_string(), "-P1Y"); +// assert_eq!(GeoPoint::from_str("-P1Y")?.to_string(), "-P1Y"); +// assert_eq!(YearMonthDuration::from_str("-P1M")?.to_string(), "-P1M"); +// assert_eq!(GeoPoint::from_str("-P1M")?.to_string(), "-P1M"); +// assert_eq!(DayTimeDuration::from_str("-P1D")?.to_string(), "-P1D"); +// assert_eq!(GeoPoint::from_str("-P1D")?.to_string(), "-P1D"); +// assert_eq!(DayTimeDuration::from_str("-PT1H")?.to_string(), "-PT1H"); +// assert_eq!(GeoPoint::from_str("-PT1H")?.to_string(), "-PT1H"); +// assert_eq!(DayTimeDuration::from_str("-PT1M")?.to_string(), "-PT1M"); +// assert_eq!(GeoPoint::from_str("-PT1M")?.to_string(), "-PT1M"); +// assert_eq!(DayTimeDuration::from_str("-PT1S")?.to_string(), "-PT1S"); +// assert_eq!(GeoPoint::from_str("-PT1S")?.to_string(), "-PT1S"); +// assert_eq!(DayTimeDuration::from_str("-PT1.1S")?.to_string(), "-PT1.1S"); +// assert_eq!(GeoPoint::from_str("-PT1.1S")?.to_string(), "-PT1.1S"); +// assert_eq!(GeoPoint::from_str(&max.to_string())?, max); +// assert_eq!(GeoPoint::from_str(&min.to_string())?, min); +// assert_eq!(GeoPoint::from_str("PT0H")?.to_string(), "PT0S"); +// assert_eq!(GeoPoint::from_str("-PT0H")?.to_string(), "PT0S"); +// assert_eq!(YearMonthDuration::from_str("P0Y")?.to_string(), "P0M"); +// assert_eq!(DayTimeDuration::from_str("PT0H")?.to_string(), "PT0S"); +// Ok(()) +// } +// +// #[test] +// fn from_std() -> Result<(), DurationOverflowError> { +// assert_eq!( +// GeoPoint::try_from(StdDuration::new(10, 10))?.to_string(), +// "PT10.00000001S" +// ); +// Ok(()) +// } +// +// #[test] +// fn to_std() -> Result<(), Box> { +// let duration = StdDuration::try_from(DayTimeDuration::from_str("PT10.00000001S")?)?; +// assert_eq!(duration.as_secs(), 10); +// assert_eq!(duration.subsec_nanos(), 10); +// Ok(()) +// } +// +// #[test] +// fn to_be_bytes() { +// assert_eq!( +// GeoPoint::from_be_bytes(GeoPoint::MIN.to_be_bytes()), +// GeoPoint::MIN +// ); +// assert_eq!( +// GeoPoint::from_be_bytes(GeoPoint::MAX.to_be_bytes()), +// GeoPoint::MAX +// ); +// assert_eq!( +// YearMonthDuration::from_be_bytes(YearMonthDuration::MIN.to_be_bytes()), +// YearMonthDuration::MIN +// ); +// assert_eq!( +// YearMonthDuration::from_be_bytes(YearMonthDuration::MAX.to_be_bytes()), +// YearMonthDuration::MAX +// ); +// assert_eq!( +// DayTimeDuration::from_be_bytes(DayTimeDuration::MIN.to_be_bytes()), +// DayTimeDuration::MIN +// ); +// assert_eq!( +// DayTimeDuration::from_be_bytes(DayTimeDuration::MAX.to_be_bytes()), +// DayTimeDuration::MAX +// ); +// } +// +// #[test] +// fn equals() -> Result<(), ParseDurationError> { +// assert_eq!( +// YearMonthDuration::from_str("P1Y")?, +// YearMonthDuration::from_str("P12M")? +// ); +// assert_eq!( +// YearMonthDuration::from_str("P1Y")?, +// GeoPoint::from_str("P12M")? +// ); +// assert_eq!( +// GeoPoint::from_str("P1Y")?, +// YearMonthDuration::from_str("P12M")? +// ); +// assert_eq!(GeoPoint::from_str("P1Y")?, GeoPoint::from_str("P12M")?); +// assert_eq!( +// DayTimeDuration::from_str("PT24H")?, +// DayTimeDuration::from_str("P1D")? +// ); +// assert_eq!( +// DayTimeDuration::from_str("PT24H")?, +// GeoPoint::from_str("P1D")? +// ); +// assert_eq!( +// GeoPoint::from_str("PT24H")?, +// DayTimeDuration::from_str("P1D")? +// ); +// assert_eq!(GeoPoint::from_str("PT24H")?, GeoPoint::from_str("P1D")?); +// assert_ne!(GeoPoint::from_str("P1Y")?, GeoPoint::from_str("P365D")?); +// assert_eq!(GeoPoint::from_str("P0Y")?, GeoPoint::from_str("P0D")?); +// assert_ne!(GeoPoint::from_str("P1Y")?, GeoPoint::from_str("P365D")?); +// assert_eq!(GeoPoint::from_str("P2Y")?, GeoPoint::from_str("P24M")?); +// assert_eq!(GeoPoint::from_str("P10D")?, GeoPoint::from_str("PT240H")?); +// assert_eq!( +// GeoPoint::from_str("P2Y0M0DT0H0M0S")?, +// GeoPoint::from_str("P24M")? +// ); +// assert_eq!( +// GeoPoint::from_str("P0Y0M10D")?, +// GeoPoint::from_str("PT240H")? +// ); +// assert_ne!(GeoPoint::from_str("P1M")?, GeoPoint::from_str("P30D")?); +// Ok(()) +// } +// +// #[test] +// #[allow(clippy::neg_cmp_op_on_partial_ord)] +// fn cmp() -> Result<(), ParseDurationError> { +// assert!(GeoPoint::from_str("P1Y1D")? < GeoPoint::from_str("P13MT25H")?); +// assert!(YearMonthDuration::from_str("P1Y")? < YearMonthDuration::from_str("P13M")?); +// assert!(GeoPoint::from_str("P1Y")? < YearMonthDuration::from_str("P13M")?); +// assert!(YearMonthDuration::from_str("P1Y")? < GeoPoint::from_str("P13M")?); +// assert!(DayTimeDuration::from_str("P1D")? < DayTimeDuration::from_str("PT25H")?); +// assert!(DayTimeDuration::from_str("PT1H")? < DayTimeDuration::from_str("PT61M")?); +// assert!(DayTimeDuration::from_str("PT1M")? < DayTimeDuration::from_str("PT61S")?); +// assert!(GeoPoint::from_str("PT1H")? < DayTimeDuration::from_str("PT61M")?); +// assert!(DayTimeDuration::from_str("PT1H")? < GeoPoint::from_str("PT61M")?); +// assert!(YearMonthDuration::from_str("P1M")? < DayTimeDuration::from_str("P40D")?); +// assert!(DayTimeDuration::from_str("P25D")? < YearMonthDuration::from_str("P1M")?); +// Ok(()) +// } +// +// #[test] +// fn years() -> Result<(), ParseDurationError> { +// assert_eq!(GeoPoint::from_str("P20Y15M")?.years(), 21); +// assert_eq!(GeoPoint::from_str("-P15M")?.years(), -1); +// assert_eq!(GeoPoint::from_str("-P2DT15H")?.years(), 0); +// Ok(()) +// } +// +// #[test] +// fn months() -> Result<(), ParseDurationError> { +// assert_eq!(GeoPoint::from_str("P20Y15M")?.months(), 3); +// assert_eq!(GeoPoint::from_str("-P20Y18M")?.months(), -6); +// assert_eq!(GeoPoint::from_str("-P2DT15H0M0S")?.months(), 0); +// Ok(()) +// } +// +// #[test] +// fn days() -> Result<(), ParseDurationError> { +// assert_eq!(GeoPoint::from_str("P3DT10H")?.days(), 3); +// assert_eq!(GeoPoint::from_str("P3DT55H")?.days(), 5); +// assert_eq!(GeoPoint::from_str("P3Y5M")?.days(), 0); +// Ok(()) +// } +// +// #[test] +// fn hours() -> Result<(), ParseDurationError> { +// assert_eq!(GeoPoint::from_str("P3DT10H")?.hours(), 10); +// assert_eq!(GeoPoint::from_str("P3DT12H32M12S")?.hours(), 12); +// assert_eq!(GeoPoint::from_str("PT123H")?.hours(), 3); +// assert_eq!(GeoPoint::from_str("-P3DT10H")?.hours(), -10); +// Ok(()) +// } +// +// #[test] +// fn minutes() -> Result<(), ParseDurationError> { +// assert_eq!(GeoPoint::from_str("P3DT10H")?.minutes(), 0); +// assert_eq!(GeoPoint::from_str("-P5DT12H30M")?.minutes(), -30); +// Ok(()) +// } +// +// #[test] +// fn seconds() -> Result<(), Box> { +// assert_eq!( +// GeoPoint::from_str("P3DT10H12.5S")?.seconds(), +// Decimal::from_str("12.5")? +// ); +// assert_eq!( +// GeoPoint::from_str("-PT256S")?.seconds(), +// Decimal::from_str("-16.0")? +// ); +// Ok(()) +// } +// +// #[test] +// fn add() -> Result<(), ParseDurationError> { +// assert_eq!( +// GeoPoint::from_str("P2Y11M")?.checked_add(GeoPoint::from_str("P3Y3M")?), +// Some(GeoPoint::from_str("P6Y2M")?) +// ); +// assert_eq!( +// GeoPoint::from_str("P2DT12H5M")?.checked_add(GeoPoint::from_str("P5DT12H")?), +// Some(GeoPoint::from_str("P8DT5M")?) +// ); +// assert_eq!( +// GeoPoint::from_str("P1M2D")?.checked_add(GeoPoint::from_str("-P3D")?), +// None +// ); +// assert_eq!( +// GeoPoint::from_str("P1M2D")?.checked_add(GeoPoint::from_str("-P2M")?), +// None +// ); +// Ok(()) +// } +// +// #[test] +// fn sub() -> Result<(), ParseDurationError> { +// assert_eq!( +// GeoPoint::from_str("P2Y11M")?.checked_sub(GeoPoint::from_str("P3Y3M")?), +// Some(GeoPoint::from_str("-P4M")?) +// ); +// assert_eq!( +// GeoPoint::from_str("P2DT12H")?.checked_sub(GeoPoint::from_str("P1DT10H30M")?), +// Some(GeoPoint::from_str("P1DT1H30M")?) +// ); +// assert_eq!( +// GeoPoint::from_str("P1M2D")?.checked_sub(GeoPoint::from_str("P3D")?), +// None +// ); +// assert_eq!( +// GeoPoint::from_str("P1M2D")?.checked_sub(GeoPoint::from_str("P2M")?), +// None +// ); +// Ok(()) +// } +// +// #[test] +// fn minimally_conformant() -> Result<(), ParseDurationError> { +// // All minimally conforming processors must support fractional-second duration values +// // to milliseconds (i.e. those expressible with three fraction digits). +// assert_eq!(GeoPoint::from_str("PT0.001S")?.to_string(), "PT0.001S"); +// assert_eq!(GeoPoint::from_str("-PT0.001S")?.to_string(), "-PT0.001S"); +// +// // All minimally conforming processors must support duration values with months values +// // in the range −119999 to 119999 months (9999 years and 11 months) +// // and seconds values in the range −31622400 to 31622400 seconds (one leap-year). +// assert_eq!( +// GeoPoint::from_str("P119999MT31622400S")?.to_string(), +// "P9999Y11M366D" +// ); +// assert_eq!( +// GeoPoint::from_str("-P119999MT31622400S")?.to_string(), +// "-P9999Y11M366D" +// ); +// Ok(()) +// } +// } diff --git a/lib/oxsdatatypes/src/lib.rs b/lib/oxsdatatypes/src/lib.rs index 336bdd20..690f8fa8 100644 --- a/lib/oxsdatatypes/src/lib.rs +++ b/lib/oxsdatatypes/src/lib.rs @@ -10,6 +10,7 @@ mod decimal; mod double; mod duration; mod float; +mod geopoint; mod integer; pub use self::boolean::Boolean; From 58914523a232b54886609db37650a1baf34487e3 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 8 Feb 2024 16:26:51 -0500 Subject: [PATCH 12/15] wip --- lib/oxsdatatypes/src/geopoint.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/oxsdatatypes/src/geopoint.rs b/lib/oxsdatatypes/src/geopoint.rs index 2038e965..a12e8110 100644 --- a/lib/oxsdatatypes/src/geopoint.rs +++ b/lib/oxsdatatypes/src/geopoint.rs @@ -1,5 +1,6 @@ use std::str::FromStr; -use wkt::Wkt; +use wkt::{Geometry, Wkt}; + // use std::time::Geo as StdDuration; /// [XML Schema `duration` datatype](https://www.w3.org/TR/xmlschema11-2/#duration) @@ -10,6 +11,12 @@ pub struct GeoPoint { geom: wkt::Wkt, } +#[derive(Error, Debug)] +enum GeoPointError { + #[error("WKT type {0} is not supported")] + UnsupportedWktType(String), +} + type WktError = &'static str; impl GeoPoint { @@ -23,9 +30,11 @@ impl FromStr for GeoPoint { type Err = WktError; fn from_str(input: &str) -> Result { - Ok(Self { - geom: Wkt::from_str(input)?, - }) + let geo = Wkt::from_str(input)?; + let Geometry::Point(point) = geo.item else { + return Err("Not a point"); + } + Ok(Self { geom: geo }) } } // From 4f2213a380cb13c6e6f17ae92dacbb44874d72b6 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 9 Feb 2024 01:26:59 -0500 Subject: [PATCH 13/15] parse geopoint --- lib/oxsdatatypes/src/geopoint.rs | 1106 +----------------------------- 1 file changed, 26 insertions(+), 1080 deletions(-) diff --git a/lib/oxsdatatypes/src/geopoint.rs b/lib/oxsdatatypes/src/geopoint.rs index a12e8110..ac02a1b7 100644 --- a/lib/oxsdatatypes/src/geopoint.rs +++ b/lib/oxsdatatypes/src/geopoint.rs @@ -1,4 +1,6 @@ use std::str::FromStr; +use thiserror::Error; +use wkt::types::Point; use wkt::{Geometry, Wkt}; // use std::time::Geo as StdDuration; @@ -7,1101 +9,45 @@ use wkt::{Geometry, Wkt}; /// /// It stores the duration using a pair of a [`YearMonthDuration`] and a [`DayTimeDuration`]. #[derive(Debug, Clone)] -pub struct GeoPoint { - geom: wkt::Wkt, -} +pub struct GeoPoint(Point); #[derive(Error, Debug)] -enum GeoPointError { +pub enum GeoPointError { + #[error("Unable to parse WKT: {0}")] + WktParsingError(&'static str), #[error("WKT type {0} is not supported")] UnsupportedWktType(String), } -type WktError = &'static str; - impl GeoPoint { #[inline] - pub fn new() -> Result { + pub fn new() -> Result { Self::from_str("POINT(0 0)") } } impl FromStr for GeoPoint { - type Err = WktError; + type Err = GeoPointError; fn from_str(input: &str) -> Result { - let geo = Wkt::from_str(input)?; + let geo = Wkt::from_str(input).map_err(GeoPointError::WktParsingError)?; let Geometry::Point(point) = geo.item else { - return Err("Not a point"); - } - Ok(Self { geom: geo }) + return Err(GeoPointError::UnsupportedWktType(geo.item.to_string())); + }; + Ok(Self(point)) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::panic_in_result_fn)] + + use super::*; + + #[test] + fn from_str() { + let pt = GeoPoint::from_str("POINT(10 -20)").unwrap().0 .0.unwrap(); + assert_eq!(pt.x, 10.0); + assert_eq!(pt.y, -20.0); } } -// -// impl fmt::Display for GeoPoint { -// #[allow(clippy::many_single_char_names)] -// #[inline] -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// let ym = self.year_month.months; -// let ss = self.day_time.seconds; -// -// if (ym < 0 && ss > 0.into()) || (ym > 0 && ss < 0.into()) { -// return Err(fmt::Error); // Not able to format with only a part of the duration that is negative -// } -// if ym < 0 || ss < 0.into() { -// write!(f, "-")?; -// } -// write!(f, "P")?; -// -// if ym == 0 && ss == 0.into() { -// return write!(f, "T0S"); -// } -// -// { -// let y = ym / 12; -// let m = ym % 12; -// -// if y != 0 { -// if m == 0 { -// write!(f, "{}Y", y.abs())?; -// } else { -// write!(f, "{}Y{}M", y.abs(), m.abs())?; -// } -// } else if m != 0 || ss == 0.into() { -// write!(f, "{}M", m.abs())?; -// } -// } -// -// { -// let s_int = ss.as_i128(); -// let d = s_int / 86400; -// let h = (s_int % 86400) / 3600; -// let m = (s_int % 3600) / 60; -// let s = ss -// .checked_sub( -// Decimal::try_from(d * 86400 + h * 3600 + m * 60).map_err(|_| fmt::Error)?, -// ) -// .ok_or(fmt::Error)?; -// -// if d != 0 { -// write!(f, "{}D", d.abs())?; -// } -// -// if h != 0 || m != 0 || s != 0.into() { -// write!(f, "T")?; -// if h != 0 { -// write!(f, "{}H", h.abs())?; -// } -// if m != 0 { -// write!(f, "{}M", m.abs())?; -// } -// if s != 0.into() { -// write!(f, "{}S", s.checked_abs().ok_or(fmt::Error)?)?; -// } -// } -// } -// Ok(()) -// } -// } -// -// impl PartialOrd for GeoPoint { -// #[inline] -// fn partial_cmp(&self, other: &Self) -> Option { -// let first = DateTime::new(1969, 9, 1, 0, 0, 0.into(), None).ok()?; -// let first_result = first -// .checked_add_duration(*self)? -// .partial_cmp(&first.checked_add_duration(*other)?); -// let second = DateTime::new(1697, 2, 1, 0, 0, 0.into(), None).ok()?; -// let second_result = second -// .checked_add_duration(*self)? -// .partial_cmp(&second.checked_add_duration(*other)?); -// let third = DateTime::new(1903, 3, 1, 0, 0, 0.into(), None).ok()?; -// let third_result = third -// .checked_add_duration(*self)? -// .partial_cmp(&third.checked_add_duration(*other)?); -// let fourth = DateTime::new(1903, 7, 1, 0, 0, 0.into(), None).ok()?; -// let fourth_result = fourth -// .checked_add_duration(*self)? -// .partial_cmp(&fourth.checked_add_duration(*other)?); -// if first_result == second_result -// && second_result == third_result -// && third_result == fourth_result -// { -// first_result -// } else { -// None -// } -// } -// } -// -// /// [XML Schema `yearMonthDuration` datatype](https://www.w3.org/TR/xmlschema11-2/#yearMonthDuration) -// /// -// /// It stores the duration as a number of months encoded using a [`i64`]. -// #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash, Default)] -// pub struct YearMonthDuration { -// months: i64, -// } -// -// impl YearMonthDuration { -// #[inline] -// pub fn new(months: impl Into) -> Self { -// Self { -// months: months.into(), -// } -// } -// -// #[inline] -// pub fn from_be_bytes(bytes: [u8; 8]) -> Self { -// Self { -// months: i64::from_be_bytes(bytes), -// } -// } -// -// /// [fn:years-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-years-from-duration) -// #[inline] -// pub fn years(self) -> i64 { -// self.months / 12 -// } -// -// /// [fn:months-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-months-from-duration) -// #[inline] -// pub fn months(self) -> i64 { -// self.months % 12 -// } -// -// #[inline] -// pub(crate) const fn all_months(self) -> i64 { -// self.months -// } -// -// #[inline] -// pub fn to_be_bytes(self) -> [u8; 8] { -// self.months.to_be_bytes() -// } -// -// /// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDurations) -// /// -// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). -// #[inline] -// pub fn checked_add(self, rhs: impl Into) -> Option { -// let rhs = rhs.into(); -// Some(Self { -// months: self.months.checked_add(rhs.months)?, -// }) -// } -// -// /// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDurations) -// /// -// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). -// #[inline] -// pub fn checked_sub(self, rhs: impl Into) -> Option { -// let rhs = rhs.into(); -// Some(Self { -// months: self.months.checked_sub(rhs.months)?, -// }) -// } -// -// /// Unary negation. -// /// -// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). -// #[inline] -// pub fn checked_neg(self) -> Option { -// Some(Self { -// months: self.months.checked_neg()?, -// }) -// } -// -// /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity). -// #[inline] -// pub fn is_identical_with(self, other: Self) -> bool { -// self == other -// } -// -// pub const MIN: Self = Self { months: i64::MIN }; -// -// pub const MAX: Self = Self { months: i64::MAX }; -// } -// -// impl From for GeoPoint { -// #[inline] -// fn from(value: YearMonthDuration) -> Self { -// Self { -// year_month: value, -// day_time: DayTimeDuration::default(), -// } -// } -// } -// -// impl TryFrom for YearMonthDuration { -// type Error = DurationOverflowError; -// -// #[inline] -// fn try_from(value: GeoPoint) -> Result { -// if value.day_time == DayTimeDuration::default() { -// Ok(value.year_month) -// } else { -// Err(DurationOverflowError) -// } -// } -// } -// -// impl FromStr for YearMonthDuration { -// type Err = ParseDurationError; -// -// fn from_str(input: &str) -> Result { -// let parts = ensure_complete(input, duration_parts)?; -// if parts.day_time.is_some() { -// return Err(ParseDurationError::msg( -// "There must not be any day or time component in a yearMonthDuration", -// )); -// } -// Ok(Self::new(parts.year_month.ok_or( -// ParseDurationError::msg("No year and month values found"), -// )?)) -// } -// } -// -// impl fmt::Display for YearMonthDuration { -// #[inline] -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// if self.months == 0 { -// write!(f, "P0M") -// } else { -// GeoPoint::from(*self).fmt(f) -// } -// } -// } -// -// impl PartialEq for YearMonthDuration { -// #[inline] -// fn eq(&self, other: &GeoPoint) -> bool { -// GeoPoint::from(*self).eq(other) -// } -// } -// -// impl PartialEq for GeoPoint { -// #[inline] -// fn eq(&self, other: &YearMonthDuration) -> bool { -// self.eq(&Self::from(*other)) -// } -// } -// -// impl PartialOrd for YearMonthDuration { -// #[inline] -// fn partial_cmp(&self, other: &GeoPoint) -> Option { -// GeoPoint::from(*self).partial_cmp(other) -// } -// } -// -// impl PartialOrd for GeoPoint { -// #[inline] -// fn partial_cmp(&self, other: &YearMonthDuration) -> Option { -// self.partial_cmp(&Self::from(*other)) -// } -// } -// -// /// [XML Schema `dayTimeDuration` datatype](https://www.w3.org/TR/xmlschema11-2/#dayTimeDuration) -// /// -// /// It stores the duration as a number of seconds encoded using a [`Decimal`]. -// #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash, Default)] -// pub struct DayTimeDuration { -// seconds: Decimal, -// } -// -// impl DayTimeDuration { -// #[inline] -// pub fn new(seconds: impl Into) -> Self { -// Self { -// seconds: seconds.into(), -// } -// } -// -// #[inline] -// pub fn from_be_bytes(bytes: [u8; 16]) -> Self { -// Self { -// seconds: Decimal::from_be_bytes(bytes), -// } -// } -// -// /// [fn:days-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-days-from-duration) -// #[allow(clippy::cast_possible_truncation)] -// #[inline] -// pub fn days(self) -> i64 { -// (self.seconds.as_i128() / 86400) as i64 -// } -// -// /// [fn:hours-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-hours-from-duration) -// #[allow(clippy::cast_possible_truncation)] -// #[inline] -// pub fn hours(self) -> i64 { -// ((self.seconds.as_i128() % 86400) / 3600) as i64 -// } -// -// /// [fn:minutes-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-minutes-from-duration) -// #[allow(clippy::cast_possible_truncation)] -// #[inline] -// pub fn minutes(self) -> i64 { -// ((self.seconds.as_i128() % 3600) / 60) as i64 -// } -// -// /// [fn:seconds-from-duration](https://www.w3.org/TR/xpath-functions-31/#func-seconds-from-duration) -// #[inline] -// pub fn seconds(self) -> Decimal { -// self.seconds.checked_rem(60).unwrap() -// } -// -// /// The duration in seconds. -// #[inline] -// pub const fn as_seconds(self) -> Decimal { -// self.seconds -// } -// -// #[inline] -// pub fn to_be_bytes(self) -> [u8; 16] { -// self.seconds.to_be_bytes() -// } -// -// /// [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDurations) -// /// -// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). -// #[inline] -// pub fn checked_add(self, rhs: impl Into) -> Option { -// let rhs = rhs.into(); -// Some(Self { -// seconds: self.seconds.checked_add(rhs.seconds)?, -// }) -// } -// -// /// [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDurations) -// /// -// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). -// #[inline] -// pub fn checked_sub(self, rhs: impl Into) -> Option { -// let rhs = rhs.into(); -// Some(Self { -// seconds: self.seconds.checked_sub(rhs.seconds)?, -// }) -// } -// -// /// Unary negation. -// /// -// /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)). -// #[inline] -// pub fn checked_neg(self) -> Option { -// Some(Self { -// seconds: self.seconds.checked_neg()?, -// }) -// } -// -// /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity). -// #[inline] -// pub fn is_identical_with(self, other: Self) -> bool { -// self == other -// } -// -// pub const MIN: Self = Self { -// seconds: Decimal::MIN, -// }; -// -// pub const MAX: Self = Self { -// seconds: Decimal::MAX, -// }; -// } -// -// impl From for GeoPoint { -// #[inline] -// fn from(value: DayTimeDuration) -> Self { -// Self { -// year_month: YearMonthDuration::default(), -// day_time: value, -// } -// } -// } -// -// impl TryFrom for DayTimeDuration { -// type Error = DurationOverflowError; -// -// #[inline] -// fn try_from(value: GeoPoint) -> Result { -// if value.year_month == YearMonthDuration::default() { -// Ok(value.day_time) -// } else { -// Err(DurationOverflowError) -// } -// } -// } -// -// impl TryFrom for DayTimeDuration { -// type Error = DurationOverflowError; -// -// #[inline] -// fn try_from(value: StdDuration) -> Result { -// Ok(Self { -// seconds: Decimal::new( -// i128::try_from(value.as_nanos()).map_err(|_| DurationOverflowError)?, -// 9, -// ) -// .map_err(|_| DurationOverflowError)?, -// }) -// } -// } -// -// impl TryFrom for StdDuration { -// type Error = DurationOverflowError; -// -// #[inline] -// fn try_from(value: DayTimeDuration) -> Result { -// if value.seconds.is_negative() { -// return Err(DurationOverflowError); -// } -// let secs = value.seconds.checked_floor().ok_or(DurationOverflowError)?; -// let nanos = value -// .seconds -// .checked_sub(secs) -// .ok_or(DurationOverflowError)? -// .checked_mul(1_000_000_000) -// .ok_or(DurationOverflowError)? -// .checked_floor() -// .ok_or(DurationOverflowError)?; -// Ok(StdDuration::new( -// secs.as_i128() -// .try_into() -// .map_err(|_| DurationOverflowError)?, -// nanos -// .as_i128() -// .try_into() -// .map_err(|_| DurationOverflowError)?, -// )) -// } -// } -// -// impl FromStr for DayTimeDuration { -// type Err = ParseDurationError; -// -// fn from_str(input: &str) -> Result { -// let parts = ensure_complete(input, duration_parts)?; -// if parts.year_month.is_some() { -// return Err(ParseDurationError::msg( -// "There must not be any year or month component in a dayTimeDuration", -// )); -// } -// Ok(Self::new(parts.day_time.ok_or(ParseDurationError::msg( -// "No day or time values found", -// ))?)) -// } -// } -// -// impl fmt::Display for DayTimeDuration { -// #[inline] -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// GeoPoint::from(*self).fmt(f) -// } -// } -// -// impl PartialEq for DayTimeDuration { -// #[inline] -// fn eq(&self, other: &GeoPoint) -> bool { -// GeoPoint::from(*self).eq(other) -// } -// } -// -// impl PartialEq for GeoPoint { -// #[inline] -// fn eq(&self, other: &DayTimeDuration) -> bool { -// self.eq(&Self::from(*other)) -// } -// } -// -// impl PartialEq for DayTimeDuration { -// #[inline] -// fn eq(&self, other: &YearMonthDuration) -> bool { -// GeoPoint::from(*self).eq(&GeoPoint::from(*other)) -// } -// } -// -// impl PartialEq for YearMonthDuration { -// #[inline] -// fn eq(&self, other: &DayTimeDuration) -> bool { -// GeoPoint::from(*self).eq(&GeoPoint::from(*other)) -// } -// } -// -// impl PartialOrd for DayTimeDuration { -// #[inline] -// fn partial_cmp(&self, other: &GeoPoint) -> Option { -// GeoPoint::from(*self).partial_cmp(other) -// } -// } -// -// impl PartialOrd for GeoPoint { -// #[inline] -// fn partial_cmp(&self, other: &DayTimeDuration) -> Option { -// self.partial_cmp(&Self::from(*other)) -// } -// } -// -// impl PartialOrd for DayTimeDuration { -// #[inline] -// fn partial_cmp(&self, other: &YearMonthDuration) -> Option { -// GeoPoint::from(*self).partial_cmp(&GeoPoint::from(*other)) -// } -// } -// -// impl PartialOrd for YearMonthDuration { -// #[inline] -// fn partial_cmp(&self, other: &DayTimeDuration) -> Option { -// GeoPoint::from(*self).partial_cmp(&GeoPoint::from(*other)) -// } -// } -// -// // [6] duYearFrag ::= unsignedNoDecimalPtNumeral 'Y' -// // [7] duMonthFrag ::= unsignedNoDecimalPtNumeral 'M' -// // [8] duDayFrag ::= unsignedNoDecimalPtNumeral 'D' -// // [9] duHourFrag ::= unsignedNoDecimalPtNumeral 'H' -// // [10] duMinuteFrag ::= unsignedNoDecimalPtNumeral 'M' -// // [11] duSecondFrag ::= (unsignedNoDecimalPtNumeral | unsignedDecimalPtNumeral) 'S' -// // [12] duYearMonthFrag ::= (duYearFrag duMonthFrag?) | duMonthFrag -// // [13] duTimeFrag ::= 'T' ((duHourFrag duMinuteFrag? duSecondFrag?) | (duMinuteFrag duSecondFrag?) | duSecondFrag) -// // [14] duDayTimeFrag ::= (duDayFrag duTimeFrag?) | duTimeFrag -// // [15] durationLexicalRep ::= '-'? 'P' ((duYearMonthFrag duDayTimeFrag?) | duDayTimeFrag) -// struct DurationParts { -// year_month: Option, -// day_time: Option, -// } -// -// fn duration_parts(input: &str) -> Result<(DurationParts, &str), ParseDurationError> { -// // States -// const START: u32 = 0; -// const AFTER_YEAR: u32 = 1; -// const AFTER_MONTH: u32 = 2; -// const AFTER_DAY: u32 = 3; -// const AFTER_T: u32 = 4; -// const AFTER_HOUR: u32 = 5; -// const AFTER_MINUTE: u32 = 6; -// const AFTER_SECOND: u32 = 7; -// -// let (is_negative, input) = if let Some(left) = input.strip_prefix('-') { -// (true, left) -// } else { -// (false, input) -// }; -// let mut input = expect_char(input, 'P', "Durations must start with 'P'")?; -// let mut state = START; -// let mut year_month: Option = None; -// let mut day_time: Option = None; -// while !input.is_empty() { -// if let Some(left) = input.strip_prefix('T') { -// if state >= AFTER_T { -// return Err(ParseDurationError::msg("Duplicated time separator 'T'")); -// } -// state = AFTER_T; -// input = left; -// } else { -// let (number_str, left) = decimal_prefix(input); -// match left.chars().next() { -// Some('Y') if state < AFTER_YEAR => { -// year_month = Some( -// year_month -// .unwrap_or_default() -// .checked_add( -// apply_i64_neg( -// i64::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, -// is_negative, -// )? -// .checked_mul(12) -// .ok_or(OVERFLOW_ERROR)?, -// ) -// .ok_or(OVERFLOW_ERROR)?, -// ); -// state = AFTER_YEAR; -// } -// Some('M') if state < AFTER_MONTH => { -// year_month = Some( -// year_month -// .unwrap_or_default() -// .checked_add(apply_i64_neg( -// i64::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, -// is_negative, -// )?) -// .ok_or(OVERFLOW_ERROR)?, -// ); -// state = AFTER_MONTH; -// } -// Some('D') if state < AFTER_DAY => { -// if number_str.contains('.') { -// return Err(ParseDurationError::msg( -// "Decimal numbers are not allowed for days", -// )); -// } -// day_time = Some( -// day_time -// .unwrap_or_default() -// .checked_add( -// apply_decimal_neg( -// Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, -// is_negative, -// )? -// .checked_mul(86400) -// .ok_or(OVERFLOW_ERROR)?, -// ) -// .ok_or(OVERFLOW_ERROR)?, -// ); -// state = AFTER_DAY; -// } -// Some('H') if state == AFTER_T => { -// if number_str.contains('.') { -// return Err(ParseDurationError::msg( -// "Decimal numbers are not allowed for hours", -// )); -// } -// day_time = Some( -// day_time -// .unwrap_or_default() -// .checked_add( -// apply_decimal_neg( -// Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, -// is_negative, -// )? -// .checked_mul(3600) -// .ok_or(OVERFLOW_ERROR)?, -// ) -// .ok_or(OVERFLOW_ERROR)?, -// ); -// state = AFTER_HOUR; -// } -// Some('M') if (AFTER_T..AFTER_MINUTE).contains(&state) => { -// if number_str.contains('.') { -// return Err(ParseDurationError::msg( -// "Decimal numbers are not allowed for minutes", -// )); -// } -// day_time = Some( -// day_time -// .unwrap_or_default() -// .checked_add( -// apply_decimal_neg( -// Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, -// is_negative, -// )? -// .checked_mul(60) -// .ok_or(OVERFLOW_ERROR)?, -// ) -// .ok_or(OVERFLOW_ERROR)?, -// ); -// state = AFTER_MINUTE; -// } -// Some('S') if (AFTER_T..AFTER_SECOND).contains(&state) => { -// day_time = Some( -// day_time -// .unwrap_or_default() -// .checked_add(apply_decimal_neg( -// Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?, -// is_negative, -// )?) -// .ok_or(OVERFLOW_ERROR)?, -// ); -// state = AFTER_SECOND; -// } -// Some(_) => return Err(ParseDurationError::msg("Unexpected type character")), -// None => { -// return Err(ParseDurationError::msg( -// "Numbers in durations must be followed by a type character", -// )) -// } -// } -// input = &left[1..]; -// } -// } -// -// Ok(( -// DurationParts { -// year_month, -// day_time, -// }, -// input, -// )) -// } -// -// fn apply_i64_neg(value: i64, is_negative: bool) -> Result { -// if is_negative { -// value.checked_neg().ok_or(OVERFLOW_ERROR) -// } else { -// Ok(value) -// } -// } -// -// fn apply_decimal_neg(value: Decimal, is_negative: bool) -> Result { -// if is_negative { -// value.checked_neg().ok_or(OVERFLOW_ERROR) -// } else { -// Ok(value) -// } -// } -// -// fn ensure_complete( -// input: &str, -// parse: impl FnOnce(&str) -> Result<(T, &str), ParseDurationError>, -// ) -> Result { -// let (result, left) = parse(input)?; -// if !left.is_empty() { -// return Err(ParseDurationError::msg("Unrecognized value suffix")); -// } -// Ok(result) -// } -// -// fn expect_char<'a>( -// input: &'a str, -// constant: char, -// error_message: &'static str, -// ) -> Result<&'a str, ParseDurationError> { -// if let Some(left) = input.strip_prefix(constant) { -// Ok(left) -// } else { -// Err(ParseDurationError::msg(error_message)) -// } -// } -// -// fn decimal_prefix(input: &str) -> (&str, &str) { -// let mut end = input.len(); -// let mut dot_seen = false; -// for (i, c) in input.char_indices() { -// if c.is_ascii_digit() { -// // Ok -// } else if c == '.' && !dot_seen { -// dot_seen = true; -// } else { -// end = i; -// break; -// } -// } -// input.split_at(end) -// } -// -// /// A parsing error -// #[derive(Debug, Clone)] -// pub struct ParseDurationError { -// msg: &'static str, -// } -// -// const OVERFLOW_ERROR: ParseDurationError = ParseDurationError { -// msg: "Overflow error", -// }; -// -// impl fmt::Display for ParseDurationError { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// write!(f, "{}", self.msg) -// } -// } -// -// impl ParseDurationError { -// const fn msg(msg: &'static str) -> Self { -// Self { msg } -// } -// } -// -// impl Error for ParseDurationError {} -// -// /// An overflow during [`GeoPoint`]-related operations. -// /// -// /// Matches XPath [`FODT0002` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002). -// #[derive(Debug, Clone, Copy)] -// pub struct DurationOverflowError; -// -// impl fmt::Display for DurationOverflowError { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// write!(f, "overflow during xsd:duration computation") -// } -// } -// -// impl Error for DurationOverflowError {} -// -// /// The year-month and the day-time components of a [`GeoPoint`] have an opposite sign. -// #[derive(Debug, Clone, Copy)] -// pub struct OppositeSignInDurationComponentsError; -// -// impl fmt::Display for OppositeSignInDurationComponentsError { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// write!(f, "The xsd:yearMonthDuration and xsd:dayTimeDuration components of a xsd:duration can't have opposite sign") -// } -// } -// -// impl Error for OppositeSignInDurationComponentsError {} -// -// impl From for ParseDurationError { -// #[inline] -// fn from(_: OppositeSignInDurationComponentsError) -> Self { -// Self { -// msg: "The xsd:yearMonthDuration and xsd:dayTimeDuration components of a xsd:duration can't have opposite sign" -// } -// } -// } -// -// #[cfg(test)] -// mod tests { -// #![allow(clippy::panic_in_result_fn)] -// -// use super::*; -// -// #[test] -// fn from_str() -> Result<(), ParseDurationError> { -// let min = GeoPoint::new(i64::MIN, Decimal::MIN)?; -// let max = GeoPoint::new(i64::MAX, Decimal::MAX)?; -// -// assert_eq!(YearMonthDuration::from_str("P1Y")?.to_string(), "P1Y"); -// assert_eq!(GeoPoint::from_str("P1Y")?.to_string(), "P1Y"); -// assert_eq!(YearMonthDuration::from_str("P1M")?.to_string(), "P1M"); -// assert_eq!(GeoPoint::from_str("P1M")?.to_string(), "P1M"); -// assert_eq!(DayTimeDuration::from_str("P1D")?.to_string(), "P1D"); -// assert_eq!(GeoPoint::from_str("P1D")?.to_string(), "P1D"); -// assert_eq!(DayTimeDuration::from_str("PT1H")?.to_string(), "PT1H"); -// assert_eq!(GeoPoint::from_str("PT1H")?.to_string(), "PT1H"); -// assert_eq!(DayTimeDuration::from_str("PT1M")?.to_string(), "PT1M"); -// assert_eq!(GeoPoint::from_str("PT1M")?.to_string(), "PT1M"); -// assert_eq!(DayTimeDuration::from_str("PT1.1S")?.to_string(), "PT1.1S"); -// assert_eq!(GeoPoint::from_str("PT1.1S")?.to_string(), "PT1.1S"); -// assert_eq!(YearMonthDuration::from_str("-P1Y")?.to_string(), "-P1Y"); -// assert_eq!(GeoPoint::from_str("-P1Y")?.to_string(), "-P1Y"); -// assert_eq!(YearMonthDuration::from_str("-P1M")?.to_string(), "-P1M"); -// assert_eq!(GeoPoint::from_str("-P1M")?.to_string(), "-P1M"); -// assert_eq!(DayTimeDuration::from_str("-P1D")?.to_string(), "-P1D"); -// assert_eq!(GeoPoint::from_str("-P1D")?.to_string(), "-P1D"); -// assert_eq!(DayTimeDuration::from_str("-PT1H")?.to_string(), "-PT1H"); -// assert_eq!(GeoPoint::from_str("-PT1H")?.to_string(), "-PT1H"); -// assert_eq!(DayTimeDuration::from_str("-PT1M")?.to_string(), "-PT1M"); -// assert_eq!(GeoPoint::from_str("-PT1M")?.to_string(), "-PT1M"); -// assert_eq!(DayTimeDuration::from_str("-PT1S")?.to_string(), "-PT1S"); -// assert_eq!(GeoPoint::from_str("-PT1S")?.to_string(), "-PT1S"); -// assert_eq!(DayTimeDuration::from_str("-PT1.1S")?.to_string(), "-PT1.1S"); -// assert_eq!(GeoPoint::from_str("-PT1.1S")?.to_string(), "-PT1.1S"); -// assert_eq!(GeoPoint::from_str(&max.to_string())?, max); -// assert_eq!(GeoPoint::from_str(&min.to_string())?, min); -// assert_eq!(GeoPoint::from_str("PT0H")?.to_string(), "PT0S"); -// assert_eq!(GeoPoint::from_str("-PT0H")?.to_string(), "PT0S"); -// assert_eq!(YearMonthDuration::from_str("P0Y")?.to_string(), "P0M"); -// assert_eq!(DayTimeDuration::from_str("PT0H")?.to_string(), "PT0S"); -// Ok(()) -// } -// -// #[test] -// fn from_std() -> Result<(), DurationOverflowError> { -// assert_eq!( -// GeoPoint::try_from(StdDuration::new(10, 10))?.to_string(), -// "PT10.00000001S" -// ); -// Ok(()) -// } -// -// #[test] -// fn to_std() -> Result<(), Box> { -// let duration = StdDuration::try_from(DayTimeDuration::from_str("PT10.00000001S")?)?; -// assert_eq!(duration.as_secs(), 10); -// assert_eq!(duration.subsec_nanos(), 10); -// Ok(()) -// } -// -// #[test] -// fn to_be_bytes() { -// assert_eq!( -// GeoPoint::from_be_bytes(GeoPoint::MIN.to_be_bytes()), -// GeoPoint::MIN -// ); -// assert_eq!( -// GeoPoint::from_be_bytes(GeoPoint::MAX.to_be_bytes()), -// GeoPoint::MAX -// ); -// assert_eq!( -// YearMonthDuration::from_be_bytes(YearMonthDuration::MIN.to_be_bytes()), -// YearMonthDuration::MIN -// ); -// assert_eq!( -// YearMonthDuration::from_be_bytes(YearMonthDuration::MAX.to_be_bytes()), -// YearMonthDuration::MAX -// ); -// assert_eq!( -// DayTimeDuration::from_be_bytes(DayTimeDuration::MIN.to_be_bytes()), -// DayTimeDuration::MIN -// ); -// assert_eq!( -// DayTimeDuration::from_be_bytes(DayTimeDuration::MAX.to_be_bytes()), -// DayTimeDuration::MAX -// ); -// } -// -// #[test] -// fn equals() -> Result<(), ParseDurationError> { -// assert_eq!( -// YearMonthDuration::from_str("P1Y")?, -// YearMonthDuration::from_str("P12M")? -// ); -// assert_eq!( -// YearMonthDuration::from_str("P1Y")?, -// GeoPoint::from_str("P12M")? -// ); -// assert_eq!( -// GeoPoint::from_str("P1Y")?, -// YearMonthDuration::from_str("P12M")? -// ); -// assert_eq!(GeoPoint::from_str("P1Y")?, GeoPoint::from_str("P12M")?); -// assert_eq!( -// DayTimeDuration::from_str("PT24H")?, -// DayTimeDuration::from_str("P1D")? -// ); -// assert_eq!( -// DayTimeDuration::from_str("PT24H")?, -// GeoPoint::from_str("P1D")? -// ); -// assert_eq!( -// GeoPoint::from_str("PT24H")?, -// DayTimeDuration::from_str("P1D")? -// ); -// assert_eq!(GeoPoint::from_str("PT24H")?, GeoPoint::from_str("P1D")?); -// assert_ne!(GeoPoint::from_str("P1Y")?, GeoPoint::from_str("P365D")?); -// assert_eq!(GeoPoint::from_str("P0Y")?, GeoPoint::from_str("P0D")?); -// assert_ne!(GeoPoint::from_str("P1Y")?, GeoPoint::from_str("P365D")?); -// assert_eq!(GeoPoint::from_str("P2Y")?, GeoPoint::from_str("P24M")?); -// assert_eq!(GeoPoint::from_str("P10D")?, GeoPoint::from_str("PT240H")?); -// assert_eq!( -// GeoPoint::from_str("P2Y0M0DT0H0M0S")?, -// GeoPoint::from_str("P24M")? -// ); -// assert_eq!( -// GeoPoint::from_str("P0Y0M10D")?, -// GeoPoint::from_str("PT240H")? -// ); -// assert_ne!(GeoPoint::from_str("P1M")?, GeoPoint::from_str("P30D")?); -// Ok(()) -// } -// -// #[test] -// #[allow(clippy::neg_cmp_op_on_partial_ord)] -// fn cmp() -> Result<(), ParseDurationError> { -// assert!(GeoPoint::from_str("P1Y1D")? < GeoPoint::from_str("P13MT25H")?); -// assert!(YearMonthDuration::from_str("P1Y")? < YearMonthDuration::from_str("P13M")?); -// assert!(GeoPoint::from_str("P1Y")? < YearMonthDuration::from_str("P13M")?); -// assert!(YearMonthDuration::from_str("P1Y")? < GeoPoint::from_str("P13M")?); -// assert!(DayTimeDuration::from_str("P1D")? < DayTimeDuration::from_str("PT25H")?); -// assert!(DayTimeDuration::from_str("PT1H")? < DayTimeDuration::from_str("PT61M")?); -// assert!(DayTimeDuration::from_str("PT1M")? < DayTimeDuration::from_str("PT61S")?); -// assert!(GeoPoint::from_str("PT1H")? < DayTimeDuration::from_str("PT61M")?); -// assert!(DayTimeDuration::from_str("PT1H")? < GeoPoint::from_str("PT61M")?); -// assert!(YearMonthDuration::from_str("P1M")? < DayTimeDuration::from_str("P40D")?); -// assert!(DayTimeDuration::from_str("P25D")? < YearMonthDuration::from_str("P1M")?); -// Ok(()) -// } -// -// #[test] -// fn years() -> Result<(), ParseDurationError> { -// assert_eq!(GeoPoint::from_str("P20Y15M")?.years(), 21); -// assert_eq!(GeoPoint::from_str("-P15M")?.years(), -1); -// assert_eq!(GeoPoint::from_str("-P2DT15H")?.years(), 0); -// Ok(()) -// } -// -// #[test] -// fn months() -> Result<(), ParseDurationError> { -// assert_eq!(GeoPoint::from_str("P20Y15M")?.months(), 3); -// assert_eq!(GeoPoint::from_str("-P20Y18M")?.months(), -6); -// assert_eq!(GeoPoint::from_str("-P2DT15H0M0S")?.months(), 0); -// Ok(()) -// } -// -// #[test] -// fn days() -> Result<(), ParseDurationError> { -// assert_eq!(GeoPoint::from_str("P3DT10H")?.days(), 3); -// assert_eq!(GeoPoint::from_str("P3DT55H")?.days(), 5); -// assert_eq!(GeoPoint::from_str("P3Y5M")?.days(), 0); -// Ok(()) -// } -// -// #[test] -// fn hours() -> Result<(), ParseDurationError> { -// assert_eq!(GeoPoint::from_str("P3DT10H")?.hours(), 10); -// assert_eq!(GeoPoint::from_str("P3DT12H32M12S")?.hours(), 12); -// assert_eq!(GeoPoint::from_str("PT123H")?.hours(), 3); -// assert_eq!(GeoPoint::from_str("-P3DT10H")?.hours(), -10); -// Ok(()) -// } -// -// #[test] -// fn minutes() -> Result<(), ParseDurationError> { -// assert_eq!(GeoPoint::from_str("P3DT10H")?.minutes(), 0); -// assert_eq!(GeoPoint::from_str("-P5DT12H30M")?.minutes(), -30); -// Ok(()) -// } -// -// #[test] -// fn seconds() -> Result<(), Box> { -// assert_eq!( -// GeoPoint::from_str("P3DT10H12.5S")?.seconds(), -// Decimal::from_str("12.5")? -// ); -// assert_eq!( -// GeoPoint::from_str("-PT256S")?.seconds(), -// Decimal::from_str("-16.0")? -// ); -// Ok(()) -// } -// -// #[test] -// fn add() -> Result<(), ParseDurationError> { -// assert_eq!( -// GeoPoint::from_str("P2Y11M")?.checked_add(GeoPoint::from_str("P3Y3M")?), -// Some(GeoPoint::from_str("P6Y2M")?) -// ); -// assert_eq!( -// GeoPoint::from_str("P2DT12H5M")?.checked_add(GeoPoint::from_str("P5DT12H")?), -// Some(GeoPoint::from_str("P8DT5M")?) -// ); -// assert_eq!( -// GeoPoint::from_str("P1M2D")?.checked_add(GeoPoint::from_str("-P3D")?), -// None -// ); -// assert_eq!( -// GeoPoint::from_str("P1M2D")?.checked_add(GeoPoint::from_str("-P2M")?), -// None -// ); -// Ok(()) -// } -// -// #[test] -// fn sub() -> Result<(), ParseDurationError> { -// assert_eq!( -// GeoPoint::from_str("P2Y11M")?.checked_sub(GeoPoint::from_str("P3Y3M")?), -// Some(GeoPoint::from_str("-P4M")?) -// ); -// assert_eq!( -// GeoPoint::from_str("P2DT12H")?.checked_sub(GeoPoint::from_str("P1DT10H30M")?), -// Some(GeoPoint::from_str("P1DT1H30M")?) -// ); -// assert_eq!( -// GeoPoint::from_str("P1M2D")?.checked_sub(GeoPoint::from_str("P3D")?), -// None -// ); -// assert_eq!( -// GeoPoint::from_str("P1M2D")?.checked_sub(GeoPoint::from_str("P2M")?), -// None -// ); -// Ok(()) -// } -// -// #[test] -// fn minimally_conformant() -> Result<(), ParseDurationError> { -// // All minimally conforming processors must support fractional-second duration values -// // to milliseconds (i.e. those expressible with three fraction digits). -// assert_eq!(GeoPoint::from_str("PT0.001S")?.to_string(), "PT0.001S"); -// assert_eq!(GeoPoint::from_str("-PT0.001S")?.to_string(), "-PT0.001S"); -// -// // All minimally conforming processors must support duration values with months values -// // in the range −119999 to 119999 months (9999 years and 11 months) -// // and seconds values in the range −31622400 to 31622400 seconds (one leap-year). -// assert_eq!( -// GeoPoint::from_str("P119999MT31622400S")?.to_string(), -// "P9999Y11M366D" -// ); -// assert_eq!( -// GeoPoint::from_str("-P119999MT31622400S")?.to_string(), -// "-P9999Y11M366D" -// ); -// Ok(()) -// } -// } From b41b90cd6bc70b57ec0c55608b98b3c48edb6bb0 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 9 Feb 2024 01:40:17 -0500 Subject: [PATCH 14/15] literal --- lib/oxrdf/src/literal.rs | 10 +++++++++- lib/oxsdatatypes/src/geopoint.rs | 7 +++++++ lib/oxsdatatypes/src/lib.rs | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/oxrdf/src/literal.rs b/lib/oxrdf/src/literal.rs index 0872fab5..16055063 100644 --- a/lib/oxrdf/src/literal.rs +++ b/lib/oxrdf/src/literal.rs @@ -1,5 +1,5 @@ use crate::named_node::NamedNode; -use crate::vocab::{rdf, xsd}; +use crate::vocab::{geosparql, rdf, xsd}; use crate::NamedNodeRef; use oxilangtag::{LanguageTag, LanguageTagParseError}; #[cfg(feature = "oxsdatatypes")] @@ -422,6 +422,14 @@ impl From for Literal { } } +#[cfg(feature = "oxsdatatypes")] +impl From for Literal { + #[inline] + fn from(value: GeoPoint) -> Self { + Self::new_typed_literal(value.to_string(), geosparql::WKT_LITERAL) + } +} + /// A borrowed RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal). /// /// The default string formatter is returning an N-Triples, Turtle, and SPARQL compatible representation: diff --git a/lib/oxsdatatypes/src/geopoint.rs b/lib/oxsdatatypes/src/geopoint.rs index ac02a1b7..83d05ed1 100644 --- a/lib/oxsdatatypes/src/geopoint.rs +++ b/lib/oxsdatatypes/src/geopoint.rs @@ -1,3 +1,5 @@ +use std::fmt; +use std::fmt::Formatter; use std::str::FromStr; use thiserror::Error; use wkt::types::Point; @@ -38,6 +40,11 @@ impl FromStr for GeoPoint { } } +impl fmt::Display for GeoPoint { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} #[cfg(test)] mod tests { #![allow(clippy::panic_in_result_fn)] diff --git a/lib/oxsdatatypes/src/lib.rs b/lib/oxsdatatypes/src/lib.rs index 690f8fa8..82005a0d 100644 --- a/lib/oxsdatatypes/src/lib.rs +++ b/lib/oxsdatatypes/src/lib.rs @@ -25,4 +25,5 @@ pub use self::duration::{ ParseDurationError, YearMonthDuration, }; pub use self::float::Float; +pub use self::geopoint::{GeoPoint, GeoPointError}; pub use self::integer::{Integer, TooLargeForIntegerError}; From 338a76b324c7fbf2999e8d8f3a3c5d6ed66ee5df Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 9 Feb 2024 02:55:44 -0500 Subject: [PATCH 15/15] wip --- lib/oxigraph/src/sparql/eval.rs | 8 +++++ lib/oxigraph/src/storage/binary_encoder.rs | 11 +++++++ lib/oxigraph/src/storage/error.rs | 9 ++++++ lib/oxigraph/src/storage/numeric_encoder.rs | 33 ++++++--------------- lib/oxsdatatypes/src/geopoint.rs | 29 ++++++++++++++---- 5 files changed, 60 insertions(+), 30 deletions(-) diff --git a/lib/oxigraph/src/sparql/eval.rs b/lib/oxigraph/src/sparql/eval.rs index 174f41fa..71df01e3 100644 --- a/lib/oxigraph/src/sparql/eval.rs +++ b/lib/oxigraph/src/sparql/eval.rs @@ -13,6 +13,7 @@ use json_event_parser::{JsonEvent, ToWriteJsonWriter}; use md5::Md5; use oxilangtag::LanguageTag; use oxiri::Iri; +use oxrdf::vocab::geosparql; use oxrdf::{TermRef, Variable}; use oxsdatatypes::*; use rand::random; @@ -2930,6 +2931,7 @@ fn to_string_id(dataset: &DatasetView, term: &EncodedTerm) -> Option { Some(build_string_id(dataset, &value.to_string())) } + EncodedTerm::GeoPoint(value) => Some(build_string_id(dataset, &value.to_string())), } } @@ -3318,6 +3320,11 @@ fn equals(a: &EncodedTerm, b: &EncodedTerm) -> Option { _ if b.is_unknown_typed_literal() => None, _ => Some(false), }, + EncodedTerm::GeoPoint(a) => match b { + EncodedTerm::GeoPoint(b) => Some(a == b), + _ if b.is_unknown_typed_literal() => None, + _ => Some(false), + }, EncodedTerm::Triple(a) => { if let EncodedTerm::Triple(b) = b { Some( @@ -3672,6 +3679,7 @@ fn datatype(dataset: &DatasetView, value: &EncodedTerm) -> Option { EncodedTerm::DayTimeDurationLiteral(..) => { Some(encode_named_node(dataset, xsd::DAY_TIME_DURATION)) } + EncodedTerm::GeoPoint(..) => Some(encode_named_node(dataset, geosparql::WKT_LITERAL)), } } diff --git a/lib/oxigraph/src/storage/binary_encoder.rs b/lib/oxigraph/src/storage/binary_encoder.rs index 25626571..3f37f5c0 100644 --- a/lib/oxigraph/src/storage/binary_encoder.rs +++ b/lib/oxigraph/src/storage/binary_encoder.rs @@ -46,6 +46,7 @@ const TYPE_G_MONTH_LITERAL: u8 = 41; const TYPE_DURATION_LITERAL: u8 = 42; const TYPE_YEAR_MONTH_DURATION_LITERAL: u8 = 43; const TYPE_DAY_TIME_DURATION_LITERAL: u8 = 44; +const TYPE_GEO_POINT_LITERAL: u8 = 45; const TYPE_TRIPLE: u8 = 48; #[derive(Clone, Copy)] @@ -388,6 +389,11 @@ impl TermReader for R { self.read_exact(&mut buffer)?; Ok(DayTimeDuration::from_be_bytes(buffer).into()) } + TYPE_GEO_POINT_LITERAL => { + let mut buffer = [0; 16]; + self.read_exact(&mut buffer)?; + Ok(DayTimeDuration::from_be_bytes(buffer).into()) + } TYPE_TRIPLE => Ok(EncodedTriple { subject: self.read_term()?, predicate: self.read_term()?, @@ -622,6 +628,11 @@ pub fn write_term(sink: &mut Vec, term: &EncodedTerm) { sink.push(TYPE_DAY_TIME_DURATION_LITERAL); sink.extend_from_slice(&value.to_be_bytes()) } + EncodedTerm::GeoPoint(value) => { + sink.push(TYPE_GEO_POINT_LITERAL); + sink.extend_from_slice(&value.x.to_be_bytes()); + sink.extend_from_slice(&value.y.to_be_bytes()); + } EncodedTerm::Triple(value) => { sink.push(TYPE_TRIPLE); write_term(sink, &value.subject); diff --git a/lib/oxigraph/src/storage/error.rs b/lib/oxigraph/src/storage/error.rs index 7a119637..681478bf 100644 --- a/lib/oxigraph/src/storage/error.rs +++ b/lib/oxigraph/src/storage/error.rs @@ -1,5 +1,7 @@ use crate::io::{ParseError, RdfFormat}; +use crate::storage::numeric_encoder::EncodedTerm; use oxiri::IriParseError; +use oxrdf::TermRef; use std::error::Error; use std::io; @@ -45,6 +47,13 @@ impl CorruptionError { Self::Other(error.into()) } + /// Builds an error from encoding and the term. + #[inline] + pub(crate) fn from_encoded_term(encoded: &EncodedTerm, term: &TermRef<'_>) -> Self { + // TODO: eventually use a dedicated error enum value + Self::new(format!("Invalid term encoding {encoded:?} for {term}")) + } + /// Builds an error from a printable error message. #[inline] pub(crate) fn msg(msg: impl Into) -> Self { diff --git a/lib/oxigraph/src/storage/numeric_encoder.rs b/lib/oxigraph/src/storage/numeric_encoder.rs index e730a163..8bf3f89c 100644 --- a/lib/oxigraph/src/storage/numeric_encoder.rs +++ b/lib/oxigraph/src/storage/numeric_encoder.rs @@ -95,6 +95,7 @@ pub enum EncodedTerm { DurationLiteral(Duration), YearMonthDurationLiteral(YearMonthDuration), DayTimeDurationLiteral(DayTimeDuration), + GeoPoint(GeoPoint), Triple(Arc), } @@ -266,6 +267,7 @@ impl Hash for EncodedTerm { Self::YearMonthDurationLiteral(value) => value.hash(state), Self::DayTimeDurationLiteral(value) => value.hash(state), Self::Triple(value) => value.hash(state), + EncodedTerm::GeoPoint(_) => {} } } } @@ -713,19 +715,13 @@ pub fn insert_term Result<(), StorageError>>( if let EncodedTerm::NamedNode { iri_id } = encoded { insert_str(iri_id, node.as_str()) } else { - Err( - CorruptionError::new(format!("Invalid term encoding {encoded:?} for {term}")) - .into(), - ) + Err(CorruptionError::from_encoded_term(encoded, &term).into()) } } TermRef::BlankNode(node) => match encoded { EncodedTerm::BigBlankNode { id_id } => insert_str(id_id, node.as_str()), EncodedTerm::SmallBlankNode(..) | EncodedTerm::NumericalBlankNode { .. } => Ok(()), - _ => Err( - CorruptionError::new(format!("Invalid term encoding {encoded:?} for {term}")) - .into(), - ), + _ => Err(CorruptionError::from_encoded_term(encoded, &term).into()), }, TermRef::Literal(literal) => match encoded { EncodedTerm::BigStringLiteral { value_id } @@ -736,10 +732,7 @@ pub fn insert_term Result<(), StorageError>>( if let Some(language) = literal.language() { insert_str(language_id, language) } else { - Err(CorruptionError::new(format!( - "Invalid term encoding {encoded:?} for {term}" - )) - .into()) + Err(CorruptionError::from_encoded_term(encoded, &term).into()) } } EncodedTerm::BigBigLangStringLiteral { @@ -750,10 +743,7 @@ pub fn insert_term Result<(), StorageError>>( if let Some(language) = literal.language() { insert_str(language_id, language) } else { - Err(CorruptionError::new(format!( - "Invalid term encoding {encoded:?} for {term}" - )) - .into()) + Err(CorruptionError::from_encoded_term(encoded, &term).into()) } } EncodedTerm::SmallTypedLiteral { datatype_id, .. } => { @@ -784,10 +774,7 @@ pub fn insert_term Result<(), StorageError>>( | EncodedTerm::DurationLiteral(..) | EncodedTerm::YearMonthDurationLiteral(..) | EncodedTerm::DayTimeDurationLiteral(..) => Ok(()), - _ => Err( - CorruptionError::new(format!("Invalid term encoding {encoded:?} for {term}")) - .into(), - ), + _ => Err(CorruptionError::from_encoded_term(encoded, &term).into()), }, TermRef::Triple(triple) => { if let EncodedTerm::Triple(encoded) = encoded { @@ -799,10 +786,7 @@ pub fn insert_term Result<(), StorageError>>( )?; insert_term(triple.object.as_ref(), &encoded.object, insert_str) } else { - Err( - CorruptionError::new(format!("Invalid term encoding {encoded:?} for {term}")) - .into(), - ) + Err(CorruptionError::from_encoded_term(encoded, &term).into()) } } } @@ -1035,6 +1019,7 @@ impl Decoder for S { EncodedTerm::DurationLiteral(value) => Ok(Literal::from(*value).into()), EncodedTerm::YearMonthDurationLiteral(value) => Ok(Literal::from(*value).into()), EncodedTerm::DayTimeDurationLiteral(value) => Ok(Literal::from(*value).into()), + EncodedTerm::GeoPoint(value) => Ok(Literal::from(*value).into()), EncodedTerm::Triple(triple) => Ok(self.decode_triple(triple)?.into()), } } diff --git a/lib/oxsdatatypes/src/geopoint.rs b/lib/oxsdatatypes/src/geopoint.rs index 83d05ed1..16a5b51c 100644 --- a/lib/oxsdatatypes/src/geopoint.rs +++ b/lib/oxsdatatypes/src/geopoint.rs @@ -2,7 +2,7 @@ use std::fmt; use std::fmt::Formatter; use std::str::FromStr; use thiserror::Error; -use wkt::types::Point; +use wkt::types::{Coord, Point}; use wkt::{Geometry, Wkt}; // use std::time::Geo as StdDuration; @@ -10,8 +10,11 @@ use wkt::{Geometry, Wkt}; /// [XML Schema `duration` datatype](https://www.w3.org/TR/xmlschema11-2/#duration) /// /// It stores the duration using a pair of a [`YearMonthDuration`] and a [`DayTimeDuration`]. -#[derive(Debug, Clone)] -pub struct GeoPoint(Point); +#[derive(Debug, Clone, Copy, Default, PartialEq)] +pub struct GeoPoint { + pub x: f32, + pub y: f32, +} #[derive(Error, Debug)] pub enum GeoPointError { @@ -26,6 +29,14 @@ impl GeoPoint { pub fn new() -> Result { Self::from_str("POINT(0 0)") } + + #[inline] + pub fn from_be_bytes(bytes: [u8; 8]) -> Self { + Self { + x: f32::from_be_bytes(bytes[0..4].try_into().unwrap()), + y: f32::from_be_bytes(bytes[4..8].try_into().unwrap()), + } + } } impl FromStr for GeoPoint { @@ -33,16 +44,22 @@ impl FromStr for GeoPoint { fn from_str(input: &str) -> Result { let geo = Wkt::from_str(input).map_err(GeoPointError::WktParsingError)?; - let Geometry::Point(point) = geo.item else { + let Geometry::Point(Point(Some(Coord { + x, + y, + z: None, + m: None, + }))) = geo.item + else { return Err(GeoPointError::UnsupportedWktType(geo.item.to_string())); }; - Ok(Self(point)) + Ok(Self { x, y }) } } impl fmt::Display for GeoPoint { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.0.fmt(f) + write!(f, "POINT({} {})", self.x, self.y) } } #[cfg(test)]