Skip to content

Commit 7026161

Browse files
authored
New work item: crate r2c2_statement (#6)
* layout for a new work-item 'term' * Update term/src/lib.rs Add comment to clarify that the proposed design can be challenged. * rename 'r2c2_term' to 'r2c2_statement' see #6 (comment) * proposed first implementation of r2c2_statement in addition to defining the core traits and types, it proposes 2 proof-of-concept implementations, for oxrdf and rdf_types, (behind the feature gate 'poc_impl') and demonstrate interoperability between the two by testing roundtripping of both implementation via the other * split crate in 2 * statement contains only traits and simple wrapper types * statement_validation contains code for validating the contract of the wrapper types * add missing code owner, and removing dummy crate
1 parent b498530 commit 7026161

File tree

21 files changed

+2795
-40
lines changed

21 files changed

+2795
-40
lines changed

.github/CODEOWNER

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
# CG chairs own the whole repository -- and in particular of the CODEOWNERS file itself.
77
* @pchampin @Tpt
88

9-
# Owners of the /dummy work-item
10-
/dummy @pchampin @Tpt # ...
11-
12-
# Owners of another work item
13-
# ...
9+
# Owners of individual work items
10+
/statement @pchampin # welcoming co-owners
11+
/statement_validation @pchampin # welcoming co-owners

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
[workspace]
22

33
members = [
4-
"dummy",
4+
"statement",
5+
"statement_validation",
56
]
67
resolver = "3"
78

@@ -15,7 +16,8 @@ license-file = "./LICENSE.md"
1516
keywords = ["rdf", "linked-data", "semantic-web", "w3c"] # no more than 5
1617

1718
[workspace.dependencies]
18-
dummy = { version = "0.1.0", path = "dummy" }
19+
r2c2_statement = { version = "0.1.0", path = "statement" }
20+
r2c2_statement_validation = { version = "0.1.0", path = "statement_validation" }
1921

2022
[workspace.lints.clippy]
2123
enum_glob_use = "allow"

dummy/Cargo.toml

Lines changed: 0 additions & 15 deletions
This file was deleted.

dummy/src/lib.rs

Lines changed: 0 additions & 18 deletions
This file was deleted.

statement/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "r2c2_statement"
3+
version.workspace = true
4+
authors.workspace = true
5+
edition.workspace = true
6+
repository.workspace = true
7+
readme.workspace = true
8+
license-file.workspace = true
9+
keywords.workspace = true
10+
11+
[dependencies]
12+
langtag = { version = "0.4.0", optional = true }
13+
oxrdf = { version = "0.2.4", optional = true, features = ["rdf-star"] }
14+
rdf-types = { version = "0.22.5", optional = true }
15+
16+
[lints]
17+
workspace = true
18+
19+
[features]
20+
poc_impl = ["dep:langtag", "dep:oxrdf", "dep:rdf-types"]

statement/src/_graph_name.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use std::borrow::Cow;
2+
3+
use crate::Iri;
4+
5+
/// A trait for [RDF terms] allowed as a [graph name] in an [RDF dataset].
6+
///
7+
/// [RDF terms]: https://www.w3.org/TR/rdf12-concepts/#dfn-rdf-term
8+
/// [graph name]: https://www.w3.org/TR/rdf12-concepts/#dfn-graph-name
9+
/// [RDF dataset]: https://www.w3.org/TR/rdf12-concepts/#dfn-rdf-dataset
10+
pub trait GraphName {
11+
/// Return a [`GraphNameProxy`] representing this graph name.
12+
///
13+
/// [RDF term]: https://www.w3.org/TR/rdf12-concepts/#dfn-rdf-term
14+
fn as_graph_name_proxy(&self) -> GraphNameProxy<'_>;
15+
16+
/// Return the [kind](GraphNameKind) of this graph name.
17+
///
18+
/// # Implementers
19+
/// A default implementation is provided for this method, based on [`GraphName::as_graph_name_proxy`].
20+
/// It may be useful to override it, especially for types where the inner values of [`GraphNameProxy`]
21+
/// are allocated as owned [`Cow<str>`](std::borrow::Cow) rather than borrowed.
22+
fn graph_name_kind(&self) -> GraphNameKind {
23+
match self.as_graph_name_proxy() {
24+
GraphNameProxy::Iri(_) => GraphNameKind::Iri,
25+
GraphNameProxy::BlankNode(_) => GraphNameKind::BlankNode,
26+
}
27+
}
28+
29+
/// Whether this graph_name is [ground](https://https://www.w3.org/TR/rdf12-concepts/#dfn-ground).
30+
fn ground(&self) -> bool {
31+
match self.graph_name_kind() {
32+
GraphNameKind::Iri => true,
33+
GraphNameKind::BlankNode => false,
34+
}
35+
}
36+
}
37+
38+
/// An enum conveying the inner information of a value implementing [`GraphName`].
39+
/// The return type of [`GraphName::as_graph_name_proxy`].
40+
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
41+
pub enum GraphNameProxy<'a> {
42+
/// An [IRI](https://www.w3.org/TR/rdf12-concepts/#section-IRIs)
43+
Iri(Iri<'a>),
44+
/// A [blank node](https://www.w3.org/TR/rdf12-concepts/#dfn-blank-node)
45+
///
46+
/// The inner value is an internal [blank node identifier](https://www.w3.org/TR/rdf12-concepts/#dfn-blank-node-identifier).
47+
/// This identifier is not part of RDF's abstract syntax, and only *locally* identifies the blank node.A
48+
///
49+
/// Note that this API does not impose any constraint on blank node identifiers,
50+
/// but concrete syntax usually do, so serializer may alter these identifiers.
51+
BlankNode(Cow<'a, str>),
52+
}
53+
54+
/// An enum representing the different kinds of [RDF terms] that can be [graph name].
55+
/// The return type of [`GraphName::graph_name_kind`].
56+
///
57+
/// [RDF terms]: https://www.w3.org/TR/rdf12-concepts/#dfn-rdf-term
58+
/// [graph name]: https://www.w3.org/TR/rdf12-concepts/#dfn-graph_name
59+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
60+
pub enum GraphNameKind {
61+
/// An [IRI](https://www.w3.org/TR/rdf12-concepts/#section-IRIs)
62+
Iri,
63+
/// A [blank node](https://www.w3.org/TR/rdf12-concepts/#dfn-blank-node)
64+
BlankNode,
65+
}
66+
67+
/// Any reference to a [`GraphName`] also trivially implements [`GraphName`]
68+
/// (as all methods of [`GraphName`] apply to `&self` anyway).
69+
impl<T: GraphName> GraphName for &'_ T {
70+
fn as_graph_name_proxy(&self) -> GraphNameProxy<'_> {
71+
(*self).as_graph_name_proxy()
72+
}
73+
74+
fn graph_name_kind(&self) -> GraphNameKind {
75+
(*self).graph_name_kind()
76+
}
77+
78+
fn ground(&self) -> bool {
79+
(*self).ground()
80+
}
81+
}
82+
83+
/// [`GraphNameProxy`] implements the trait [`GraphName`].
84+
/// This has not particular interest for [`GraphNameProxy`]s obtained from another [`GraphName`]-implementing type,
85+
/// via the [`GraphName::as_graph_name_proxy`] method.
86+
///
87+
/// It can be useful, on the other hand, to provide a straightforward implementation of [`GraphName`]
88+
/// (e.g. for testing or prototyping).
89+
impl GraphName for GraphNameProxy<'_> {
90+
fn as_graph_name_proxy(&self) -> GraphNameProxy<'_> {
91+
match self {
92+
GraphNameProxy::Iri(iri) => GraphNameProxy::Iri(iri.borrowed()),
93+
GraphNameProxy::BlankNode(cow) => GraphNameProxy::BlankNode(Cow::from(cow.as_ref())),
94+
}
95+
}
96+
}

statement/src/_iri.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::borrow::Cow;
2+
3+
/// Wrapper around a [`Cow<str>`] guaranteeing that the underlying text satisfies [RFC3987].
4+
///
5+
/// [RFC3987]: https://datatracker.ietf.org/doc/rfc3987/
6+
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7+
pub struct Iri<'a>(Cow<'a, str>);
8+
9+
impl<'a> Iri<'a> {
10+
/// Return a new [`Iri`], assuming the argument is a valid IRI.
11+
pub fn new_unchecked(txt: impl Into<Cow<'a, str>>) -> Self {
12+
Iri(txt.into())
13+
}
14+
15+
/// Return the inner [`Cow<str>`](Cow).
16+
pub fn unwrap(self) -> Cow<'a, str> {
17+
self.0
18+
}
19+
20+
/// Apply a function to the inner txt, assuming the result of the function is still a valid IRI.
21+
pub fn unchecked_map(self, mut f: impl FnMut(Cow<'a, str>) -> Cow<'a, str>) -> Self {
22+
Self(f(self.0))
23+
}
24+
25+
/// Borrow this [`Iri`] as another [`Iri`].
26+
pub fn borrowed(&self) -> Iri<'_> {
27+
Iri::new_unchecked(self.as_ref())
28+
}
29+
}
30+
31+
impl std::borrow::Borrow<str> for Iri<'_> {
32+
fn borrow(&self) -> &str {
33+
self.0.as_ref()
34+
}
35+
}
36+
37+
impl std::convert::AsRef<str> for Iri<'_> {
38+
fn as_ref(&self) -> &str {
39+
self.0.as_ref()
40+
}
41+
}
42+
43+
impl std::ops::Deref for Iri<'_> {
44+
type Target = str;
45+
46+
fn deref(&self) -> &Self::Target {
47+
self.0.as_ref()
48+
}
49+
}
50+
51+
impl std::cmp::PartialEq<&str> for Iri<'_> {
52+
fn eq(&self, other: &&str) -> bool {
53+
self.0.as_ref() == *other
54+
}
55+
}
56+
57+
impl std::cmp::PartialEq<Iri<'_>> for &str {
58+
fn eq(&self, other: &Iri) -> bool {
59+
*self == other.0.as_ref()
60+
}
61+
}
62+
63+
impl std::cmp::PartialOrd<&str> for Iri<'_> {
64+
fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> {
65+
Some(self.0.as_ref().cmp(other))
66+
}
67+
}
68+
69+
impl std::cmp::PartialOrd<Iri<'_>> for &str {
70+
fn partial_cmp(&self, other: &Iri<'_>) -> Option<std::cmp::Ordering> {
71+
Some(self.cmp(&other.0.as_ref()))
72+
}
73+
}
74+
75+
impl std::fmt::Display for Iri<'_> {
76+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77+
write!(f, "<{}>", self.0.as_ref())
78+
}
79+
}
80+
81+
#[cfg(test)]
82+
mod test {
83+
use super::*;
84+
85+
#[test]
86+
fn as_str() {
87+
let ex = "http://example.org/foo/bar";
88+
let iri1 = Iri::new_unchecked(ex.to_string());
89+
assert!(iri1.starts_with("http:"));
90+
assert_eq!(iri1, ex);
91+
assert_eq!(ex, iri1);
92+
assert!("http:" < iri1 && iri1 < "i");
93+
}
94+
95+
#[test]
96+
fn borrowed() {
97+
let ex = "http://example.org/foo/bar";
98+
let iri1 = Iri::new_unchecked(ex.to_string());
99+
let iri2 = iri1.borrowed();
100+
assert_eq!(iri1, iri2);
101+
}
102+
103+
#[test]
104+
fn display() {
105+
let ex = "http://example.org/foo/bar";
106+
let iri1 = Iri::new_unchecked(ex.to_string());
107+
assert_eq!(iri1.to_string(), format!("<{ex}>"));
108+
}
109+
}

statement/src/_literal.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
mod _language_tag;
2+
use std::borrow::Cow;
3+
4+
pub use _language_tag::*;
5+
6+
use crate::Iri;
7+
8+
/// The different possible value for literals' [base direction].
9+
///
10+
/// [base direction]: https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction
11+
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
12+
pub enum BaseDir {
13+
#[default]
14+
/// The [base direction] `ltr` (left to right)
15+
///
16+
/// [base direction]: https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction
17+
Ltr,
18+
/// The [base direction] `rtl` (right to left)
19+
///
20+
/// [base direction]: https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction
21+
Rtl,
22+
}
23+
24+
/// A utility type representing an RDF [literal].
25+
///
26+
/// [literal]: https://www.w3.org/TR/rdf12-concepts/#dfn-literal
27+
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
28+
pub enum Literal<'a> {
29+
/// A literal with a specified datatype.
30+
Typed(Cow<'a, str>, Iri<'a>),
31+
/// A [language tagged string](https://www.w3.org/TR/rdf12-concepts/#dfn-language-tagged-string),
32+
/// or a [directional language tagged string](https://www.w3.org/TR/rdf12-concepts/#dfn-language-tagged-string),
33+
/// depending on the presence of a [`BaseDir`] in the third component.
34+
LanguageString(Cow<'a, str>, LangTag<'a>, Option<BaseDir>),
35+
}
36+
37+
impl Literal<'_> {
38+
/// Borrow this [`Literal`] as another [`Literal`].
39+
pub fn borrowed(&self) -> Literal {
40+
match self {
41+
Literal::Typed(lex, iri) => Literal::Typed(Cow::from(lex.as_ref()), iri.borrowed()),
42+
Literal::LanguageString(lex, lang_tag, base_dir) => {
43+
Literal::LanguageString(Cow::from(lex.as_ref()), lang_tag.borrowed(), *base_dir)
44+
}
45+
}
46+
}
47+
48+
/// [lexical form](https://www.w3.org/TR/rdf12-concepts/#dfn-lexical-form) of this literal
49+
pub fn lexical_form(&self) -> Cow<str> {
50+
let ref_cow = match self {
51+
Literal::Typed(lex, ..) => lex,
52+
Literal::LanguageString(lex, ..) => lex,
53+
};
54+
Cow::from(ref_cow.as_ref())
55+
}
56+
57+
/// [datatype IRI](https://www.w3.org/TR/rdf12-concepts/#dfn-datatype-iri) of this literal
58+
pub fn datatype_iri(&self) -> Iri<'_> {
59+
match self {
60+
Literal::Typed(_, iri) => iri.borrowed(),
61+
Literal::LanguageString(_, _, None) => Iri::new_unchecked(RDF_LANG_STRING),
62+
Literal::LanguageString(_, _, Some(_)) => Iri::new_unchecked(RDF_DIR_LANG_STRING),
63+
}
64+
}
65+
66+
/// [language tag](https://www.w3.org/TR/rdf12-concepts/#dfn-language-tag) of this literal, if any
67+
pub fn language_tag(&self) -> Option<LangTag<'_>> {
68+
if let Literal::LanguageString(_, tag, _) = self {
69+
Some(tag.borrowed())
70+
} else {
71+
None
72+
}
73+
}
74+
75+
/// [base direction](https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction) of this literal, if any
76+
pub fn base_direction(&self) -> Option<BaseDir> {
77+
if let Literal::LanguageString(_, _, Some(dir)) = self {
78+
Some(*dir)
79+
} else {
80+
None
81+
}
82+
}
83+
}
84+
85+
static RDF_LANG_STRING: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString";
86+
static RDF_DIR_LANG_STRING: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString";

0 commit comments

Comments
 (0)