Skip to content

Commit

Permalink
improve documentation and book
Browse files Browse the repository at this point in the history
also improve the API by allowing &str * NsTerm
to generate datatype literals.
  • Loading branch information
pchampin committed Nov 15, 2023
1 parent 2144a63 commit 88d31df
Show file tree
Hide file tree
Showing 15 changed files with 334 additions and 105 deletions.
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
[submodule "json-ld-api"]
path = json-ld-api
url = https://github.com/w3c/json-ld-api
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,14 @@ and finally:
* [`sophia`] is the “all-inclusive” crate,
re-exporting symbols from all the crates above.
(actually, `sophia_xml` is only available if the `xml` feature is enabled)

In addition to the [API documentation](https://docs.rs/sophia/),
a high-level [user documentation](https://pchampin.github.io/sophia_rs/) is available (although not quite complete yet).

## Licence

[CECILL-B] (compatible with BSD)

## Testing

The test suite depends on the [the [JSON-LD test-suite]
which is included as a `git` submodule.
In order to run all the tests, you need to execute the following commands:
```bash
$ git submodule init
$ git submodule update
```

## Citation

When using Sophia, please use the following citation:
Expand All @@ -69,6 +62,7 @@ Bibtex:
The following third-party crates are using or extending Sophia

* [`hdt`](https://crates.io/crates/hdt) provides an implementation of Sophia's traits based on the [HDT](https://www.rdfhdt.org/) format.
* [`manas`](https://crates.io/crates/manas) is a modular framework for implementing [Solid](https://solidproject.org/) compatible servers

## History

Expand Down
1 change: 1 addition & 0 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ pub mod term;
pub mod triple;

/// Re-export MownStr to avoid dependency version mismatch.
///
pub use mownstr::MownStr;
24 changes: 20 additions & 4 deletions api/src/ns.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! Standard and custom namespaces.
//!
//! This module provides:
//! * the [`Namespace`](struct.Namespace.html) type for defining custom namespace;
//! * modules corresponding to the most common namespaces.
//! * the [`Namespace`](struct.Namespace.html) type for defining custom dynamic namespace;
//! * the [`namespace`] macro, for defning custom static namespaces;
//! * modules corresponding to the most common namespaces
//! (generated via the [`namespace`] macro).
//!
//! # Example
//! # Example use
//! ```
//! use sophia_api::ns::{Namespace, NsTerm, rdf, rdfs, xsd};
//! use sophia_api::ns::{Namespace, rdf, rdfs, xsd};
//!
//! let schema = Namespace::new("http://schema.org/").unwrap();
//! let s_name = schema.get("name").unwrap();
Expand All @@ -16,6 +18,20 @@
//! g.push([&s_name, &rdf::type_, &rdf::Property]);
//! g.push([&s_name, &rdfs::range, &xsd::string]);
//! ```
//!
//! # Datatyped literals
//!
//! Note also that the terms generated via the [`namespace`] macro
//! can be used to easily produced datatyped literals,
//! by simply "multiplying" a string by its datatype:
//!
//! ```
//! # use sophia_api::{term::Term, ns::xsd};
//! let date = "2023-11-15" * xsd::date ;
//! assert!(date.is_literal());
//! assert_eq!(date.lexical_form().unwrap(), "2023-11-15");
//! assert_eq!(date.datatype().unwrap(), xsd::date.iri().unwrap());
//! ```
use mownstr::MownStr;
use sophia_iri::InvalidIri;
use std::borrow::Borrow;
Expand Down
17 changes: 17 additions & 0 deletions api/src/ns/_term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ impl<'a> NsTerm<'a> {
self.to_string().into()
})
}

/// Return an [`IriRef`] representing this term.
pub fn to_iriref(self) -> IriRef<MownStr<'a>> {
if self.suffix.is_empty() {
self.ns.map_unchecked(MownStr::from)
} else {
IriRef::new_unchecked(self.to_string().into())
}
}
}

impl<'a> Term for NsTerm<'a> {
Expand Down Expand Up @@ -93,3 +102,11 @@ mod test {
assert!(t3a != t1b);
}
}

impl<'a> std::ops::Mul<NsTerm<'a>> for &'a str {
type Output = crate::term::SimpleTerm<'a>;

fn mul(self, rhs: NsTerm<'a>) -> Self::Output {
crate::term::SimpleTerm::LiteralDatatype(self.into(), rhs.to_iriref())
}
}
63 changes: 42 additions & 21 deletions api/src/term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@ pub enum TermKind {
/// However, while all other methods have a default implementtation (returning `None`),
/// those corresponding to the supported kinds MUST be overridden accordingly,
/// otherwise they will panic.
///
/// See below for an explaination of this design choice.
///
/// In order to test that all the methods are implemented consistently,
/// consider using the macro [`assert_consistent_term_impl`].
/// The macro should be invoked on various instances of the type,
/// at least one for each [kind](Term::kind) that the type supports.
///
/// # Design rationale
///
/// The methods defined by this trait are not independant:
Expand All @@ -88,23 +92,19 @@ pub trait Term: std::fmt::Debug {
/// A type of [`Term`] that can be borrowed from this type
/// (i.e. that can be obtained from a simple reference to this type).
/// It is used in particular for accessing constituents of quoted tripes ([`Term::triple`])
/// or for sharing this term with a function that expects `T: Term` (rather than `&T`).
///
/// In "standard" cases, this type is `&Self`.
///
/// Exceptions, where this type is `Self` instead, are
/// * some [`Term`] implementations implementing [`Copy`];
/// * in particular, the implementation of [`Term`] by *references* to "standard" [`Term`] implementations.
/// or for sharing this term with a function that expects `T: Term` (rather than `&T`)
/// using [`Term::borrow_term`].
///
/// This design makes `T: Term` conceptually equivalent `T: Borrow<Term>`
/// (but the latter is not valid, since `Term` is a trait).
/// In this pattern, [`Term::borrow_term`] plays the rols of [`Borrow::borrow`](std::borrow).
/// In "standard" cases, this type is either `&Self` or `Self`
/// (for types implementing [`Copy`]).
///
/// # Note to implementors
/// When in doubt, set this to `&Self`.
/// If that is not possible and your type implements [`Copy`],
/// consider setting this to `Self`.
/// Otherwise, your implementations is probably not a good fit for implementing [`Term`].
/// * When in doubt, set `BorrowTerm<'x>` to `&'x Self`.
/// * If your type implements [`Copy`],
/// consider setting it to `Self`.
/// * If your type is a wrapper `W(T)` where `T: Term`,
/// consider setting it to `W(T::BorrowTerm<'x>)`.
/// * If none of the options above are possible, your type is probably not a good fit for implementing [`Term`].
type BorrowTerm<'x>: Term + Copy
where
Self: 'x;
Expand Down Expand Up @@ -272,9 +272,30 @@ pub trait Term: std::fmt::Debug {
.then(|| unimplemented!("Default implementation should have been overridden"))
}

/// Get something implementing [`Term`] from a simple reference to `self`.
/// Get something implementing [`Term`] from a simple reference to `self`,
/// representing the same RDF term as `self`.
///
/// See [`Term::BorrowTerm`] for more detail.
/// # Wny do functions in Sophia expect `T: Term` and never `&T: Term`?
/// To understand the rationale of this design choice,
/// consider an imaginary type `Foo`.
/// A function `f(x: Foo)` requires users to waive the ownership of the `Foo` value they want to pass to the function.
/// This is not always suited to the users needs.
/// On the other hand, a function `g(x: &Foo)` not only allows, but *forces* its users to maintain ownership of the `Foo` value they want to pass to the function.
/// Again, there are situations where this is not suitable.
/// The standard solution to this problem is to use the [`Borrow`](std::borrow) trait:
/// a function `h<T>(x: T) where T: Borrow<Foo>` allows `x` to be passed either by *value* (transferring ownership)
/// or by reference (simply borrowing the caller's `Foo`).
///
/// While this design pattern is usable with a single type (`Foo` in our example above),
/// it is not usable with a trait, such as `Term`:
/// the following trait bound is not valid in Rust: `T: Borrow<Term>`.
/// Yet, we would like any function expecting a terms to be able to either take its ownership or simply borrow it,
/// depending on the caller's needs and preferences.
///
/// The `borrow_term` methods offer a solution to this problem,
/// and therefore the trait bound `T: Term` must be thought of as equivalent to `T: Borrow<Term>`:
/// the caller can chose to either waive ownership of its term (by passing it directly)
/// or keep it (by passing the result of `borrow_term()` instead).
fn borrow_term(&self) -> Self::BorrowTerm<'_>;

/// Iter over all the constituents of this term.
Expand Down Expand Up @@ -376,7 +397,7 @@ pub trait Term: std::fmt::Debug {
/// * Quoted triples are ordered in lexicographical order
///
/// NB: literals are ordered by their *lexical* value,
/// so for example, `"10"^^xsd:integer` come *before* `"2"^^xsd:integer`.
/// so for example, `"10"^^xsd:integer` comes *before* `"2"^^xsd:integer`.
fn cmp<T>(&self, other: T) -> Ordering
where
T: Term,
Expand Down Expand Up @@ -447,7 +468,7 @@ pub trait Term: std::fmt::Debug {

/// Convert this term in another type.
///
/// This method is to [`FromTerm`] was [`Into::into`] is to [`From`].
/// This method is to [`FromTerm`] what [`Into::into`] is to [`From`].
///
/// NB: if you want to make a *copy* of this term without consuming it,
/// you can use `this_term.`[`borrow_term`](Term::borrow_term)`().into_term::<T>()`.
Expand All @@ -461,10 +482,10 @@ pub trait Term: std::fmt::Debug {

/// Try to convert this term into another type.
///
/// This method is to [`FromTerm`] was [`TryInto::try_into`] is to [`TryFrom`].
/// This method is to [`TryFromTerm`] what [`TryInto::try_into`] is to [`TryFrom`].
///
/// NB: if you want to make a *copy* of this term without consuming it,
/// you can use `this_term.`[`borrow_term`](Term::borrow_term)`().into_term::<T>()`.
/// you can use `this_term.`[`borrow_term`](Term::borrow_term)`().try_into_term::<T>()`.
#[inline]
fn try_into_term<T: TryFromTerm>(self) -> Result<T, T::Error>
where
Expand Down
27 changes: 14 additions & 13 deletions api/src/term/language_tag.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
//! I define the [`LanguageTag`] wrapper type,
//! which guarantees that the underlying `str`
//! is a valid [BCP47](https://tools.ietf.org/search/bcp47) language tag.
//!
//! A [`LanguageTag`] can be combined to a `&str` with the `*` operator,
//! to produce an RDF [language tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string)
//! implementing the [`Term`](crate::term::Term) trait:
//!
//! ```
//! # use sophia_api::term::{LanguageTag, Term};
//! let fr = LanguageTag::new_unchecked("fr");
//! let message = "Bonjour le monde" * fr;
//! assert!(message.is_literal());
//! assert_eq!(message.lexical_form().unwrap(), "Bonjour le monde");
//! assert_eq!(message.language_tag().unwrap(), fr);
//! ```

use lazy_static::lazy_static;
use regex::Regex;
Expand Down Expand Up @@ -42,6 +29,20 @@ lazy_static! {
/// is a valid [BCP47](https://tools.ietf.org/search/bcp47) language tag.
///
/// NB: it is actually more permissive than BCP47.
///
/// A [`LanguageTag`] can be combined to a `&str` with the `*` operator,
/// to produce an RDF [language tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string)
/// implementing the [`Term`](crate::term::Term) trait:
///
/// ```
/// # use sophia_api::{ns::rdf, term::{LanguageTag, Term}};
/// let fr = LanguageTag::new_unchecked("fr");
/// let message = "Bonjour le monde" * fr;
/// assert!(message.is_literal());
/// assert_eq!(message.lexical_form().unwrap(), "Bonjour le monde");
/// assert_eq!(message.datatype().unwrap(), rdf::langString.iri().unwrap());
/// assert_eq!(message.language_tag().unwrap(), fr);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct LanguageTag<T: Borrow<str>>(T);

Expand Down
6 changes: 0 additions & 6 deletions book/src/00_introduction.md

This file was deleted.

7 changes: 4 additions & 3 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

[Executive summary](./README.md)

- [Introduction](./00_introduction.md)
- [Getting Started](./01_getting_started.md)
- [Changes since version 0.7](./90_changes_since_07.md)
- [Introduction](./ch00_introduction.md)
- [Getting Started](./ch01_getting_started.md)
- [RDF Terms](./ch02_rdf_terms.md)
- [Changes since version 0.7](./ch90_changes_since_07.md)
34 changes: 34 additions & 0 deletions book/src/ch00_introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Introduction

The [sophia crate](https://crates.io/crates/sophia) aims at providing a comprehensive toolkit for working with RDF and Linked Data in Rust.

[RDF] is a data model designed to exchange knowledge on the Web in an interoperable way. Each piece of knowledge in RDF (a [statement]) is represented by a [triple], made of three [terms]. A set of triples forms an RDF [graph]. Finally, several graphs can be grouped in a collection called a [dataset], where each graph is identified by a unique name.

In Sophia, each of these core concepts is modeled by a trait, which can be implemented in multiple ways (see for example the [`Graph`] trait and [some of the types implementing it](https://docs.rs/sophia_api/latest/sophia_api/graph/trait.Graph.html#foreign-impls)). Sophia is therefore not meant to provide the "ultimate" implementation of RDF in Rust, but a generic framework to help various implementations to interoperate with each other (in the spirit of [Apache Commons RDF] for Java or [RDFJS] for Javascript/Typescript).

## Generalized vs. Strict RDF model
The data model supported by this Sophia is in fact
a superset of the RDF data model as defined by the W3C.
When the distinction matters,
they will be called, respectively,
the *generalized* RDF model, and the *strict* RDF model.
The generalized RDF model extends RDF as follows:
* In addition to standard RDF terms (IRIs, blank nodes and literals),
Sophia supports
- RDF-star [quoted triples](https://www.w3.org/2021/12/rdf-star.html#dfn-quoted)
- Variables (a concept borrowed from [SPARQL] or [Notation3])
* Sophia allows any kind of term in any position (subject, predicate, object, graph name).
* Sophia allow IRIs to be relative IRI references
(while in strict RDF, [IRIs must be absolute](https://www.w3.org/TR/rdf11-concepts/#h3_section-IRIs)).

[RDF]: https://www.w3.org/TR/rdf-concepts/
[statement]: https://www.w3.org/TR/rdf-concepts/#dfn-rdf-statement
[triple]: https://www.w3.org/TR/rdf-concepts/#dfn-rdf-triple
[terms]: https://www.w3.org/TR/rdf-concepts/#dfn-rdf-term
[graph]: https://www.w3.org/TR/rdf-concepts/#dfn-rdf-graph
[dataset]: https://www.w3.org/TR/rdf-concepts/#dfn-rdf-dataset

[`Graph`]: https://docs.rs/sophia_api/latest/sophia_api/graph/trait.Graph.html
[Apache Commons RDF]: https://github.com/apache/commons-rdf/
[RDF/JS]: https://rdf.js.org/

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Getting Started

Following a short example how to build a graph, mutate it and serialize it back.
Below is a short example demonstrating how to build a graph, mutate it and serialize it back.


Add the sophia crate to your dependencies in `Cargo.toml`
Expand All @@ -12,7 +12,6 @@ sophia = "0.8.0-alpha.3"

Add these lines of code and run the program.
```rust,noplayground
# extern crate sophia;
use sophia::api::prelude::*;
use sophia::api::ns::Namespace;
use sophia::inmem::graph::LightGraph;
Expand Down
Loading

0 comments on commit 88d31df

Please sign in to comment.