JS: Adds termType: "Quad" to quads and adds variable support to RDF/JS implementation

pull/171/head
Tpt 4 years ago
parent f76f0396e8
commit 3ca149fa97
  1. 2
      js/README.md
  2. 4
      js/package.json
  3. 346
      js/src/model.rs
  4. 6
      js/src/store.rs
  5. 17
      js/test/store.js

@ -44,7 +44,7 @@ for (binding of store.query("SELECT ?name WHERE { <http://example/> <http://sche
Oxigraph currently provides a simple JS API.
It is centered around the `MemoryStore` class.
The `NamedNode`, `BlankNode`, `Literal`, `DefaultGraph`, `Quad` and `DataFactory` types
The `NamedNode`, `BlankNode`, `Literal`, `DefaultGraph`, `Variable`, `Quad` and `DataFactory` types
are following the [RDF/JS datamodel specification](https://rdf.js.org/data-model-spec/).
To import `MemoryStore` using Node:

@ -3,8 +3,8 @@
"description": "Oxigraph JS build and tests",
"private": true,
"devDependencies": {
"mocha": "^8.0.1",
"@rdfjs/data-model": "1.1.2",
"mocha": "^9.0.0",
"@rdfjs/data-model": "^1.3.0",
"standard": "^16.0.0"
},
"scripts": {

@ -2,7 +2,9 @@ use crate::format_err;
use crate::utils::to_err;
use js_sys::{Reflect, UriError};
use oxigraph::model::*;
use oxigraph::sparql::Variable;
use std::convert::TryFrom;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(js_name = DataFactory)]
@ -57,6 +59,11 @@ impl JsDataFactory {
JsDefaultGraph {}
}
#[wasm_bindgen(js_name = variable)]
pub fn variable(&self, value: String) -> Result<JsVariable, JsValue> {
Ok(Variable::new(value).map_err(to_err)?.into())
}
#[wasm_bindgen(js_name = triple)]
pub fn triple(
&self,
@ -64,12 +71,7 @@ impl JsDataFactory {
predicate: &JsValue,
object: &JsValue,
) -> Result<JsQuad, JsValue> {
Ok(JsQuad {
subject: self.from_js.to_term(subject)?,
predicate: self.from_js.to_term(predicate)?,
object: self.from_js.to_term(object)?,
graph_name: JsTerm::DefaultGraph(JsDefaultGraph {}),
})
self.quad(subject, predicate, object, &JsValue::UNDEFINED)
}
#[wasm_bindgen(js_name = quad)]
@ -80,26 +82,28 @@ impl JsDataFactory {
object: &JsValue,
graph: &JsValue,
) -> Result<JsQuad, JsValue> {
Ok(JsQuad {
subject: self.from_js.to_term(subject)?,
predicate: self.from_js.to_term(predicate)?,
object: self.from_js.to_term(object)?,
graph_name: if graph.is_undefined() || graph.is_null() {
JsTerm::DefaultGraph(JsDefaultGraph {})
} else {
self.from_js.to_term(graph)?
},
})
Ok(self
.from_js
.to_quad_from_parts(subject, predicate, object, graph)?
.into())
}
#[wasm_bindgen(js_name = fromTerm)]
pub fn convert_term(&self, original: &JsValue) -> Result<JsValue, JsValue> {
Ok(self.from_js.to_term(original)?.into())
Ok(if original.is_null() {
JsValue::NULL
} else {
self.from_js.to_term(original)?.into()
})
}
#[wasm_bindgen(js_name = fromQuad)]
pub fn convert_quad(&self, original: &JsValue) -> Result<JsQuad, JsValue> {
self.from_js.to_quad(original)
pub fn convert_quad(&self, original: &JsValue) -> Result<JsValue, JsValue> {
Ok(if original.is_null() {
JsValue::NULL
} else {
JsQuad::from(self.from_js.to_quad(original)?).into()
})
}
}
@ -316,12 +320,128 @@ impl JsDefaultGraph {
}
}
#[wasm_bindgen(js_name = Variable)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsVariable {
inner: Variable,
}
#[wasm_bindgen(js_class = Variable)]
impl JsVariable {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"Variable".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
self.inner.as_str().to_owned()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::Variable(other))) =
FromJsConverter::default().to_optional_term(&other)
{
self == &other
} else {
false
}
}
}
impl From<Variable> for JsVariable {
fn from(inner: Variable) -> Self {
Self { inner }
}
}
impl From<JsVariable> for Variable {
fn from(node: JsVariable) -> Self {
node.inner
}
}
#[wasm_bindgen(js_name = Quad)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsQuad {
inner: Quad,
}
#[wasm_bindgen(js_class = Quad)]
impl JsQuad {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"Quad".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
"".to_owned()
}
#[wasm_bindgen(getter = subject)]
pub fn subject(&self) -> JsValue {
JsTerm::from(self.inner.subject.clone()).into()
}
#[wasm_bindgen(getter = predicate)]
pub fn predicate(&self) -> JsValue {
JsTerm::from(self.inner.predicate.clone()).into()
}
#[wasm_bindgen(getter = object)]
pub fn object(&self) -> JsValue {
JsTerm::from(self.inner.object.clone()).into()
}
#[wasm_bindgen(getter = graph)]
pub fn graph(&self) -> JsValue {
JsTerm::from(self.inner.graph_name.clone()).into()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::Quad(other))) = FromJsConverter::default().to_optional_term(&other) {
self == &other
} else {
false
}
}
}
impl From<Triple> for JsQuad {
fn from(inner: Triple) -> Self {
Self {
inner: inner.in_graph(GraphName::DefaultGraph),
}
}
}
impl From<Quad> for JsQuad {
fn from(inner: Quad) -> Self {
Self { inner }
}
}
impl From<JsQuad> for Quad {
fn from(quad: JsQuad) -> Self {
quad.inner
}
}
impl From<JsQuad> for Triple {
fn from(quad: JsQuad) -> Self {
quad.inner.into()
}
}
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub enum JsTerm {
NamedNode(JsNamedNode),
BlankNode(JsBlankNode),
Literal(JsLiteral),
DefaultGraph(JsDefaultGraph),
Variable(JsVariable),
Quad(JsQuad),
}
impl From<JsTerm> for JsValue {
@ -331,6 +451,8 @@ impl From<JsTerm> for JsValue {
JsTerm::BlankNode(v) => v.into(),
JsTerm::Literal(v) => v.into(),
JsTerm::DefaultGraph(v) => v.into(),
JsTerm::Variable(v) => v.into(),
JsTerm::Quad(v) => v.into(),
}
}
}
@ -367,7 +489,7 @@ impl From<Subject> for JsTerm {
match node {
Subject::NamedNode(node) => node.into(),
Subject::BlankNode(node) => node.into(),
Subject::Triple(_) => unimplemented!(),
Subject::Triple(node) => node.into(),
}
}
}
@ -378,7 +500,7 @@ impl From<Term> for JsTerm {
Term::NamedNode(node) => node.into(),
Term::BlankNode(node) => node.into(),
Term::Literal(literal) => literal.into(),
Term::Triple(_) => unimplemented!(),
Term::Triple(node) => node.into(),
}
}
}
@ -393,6 +515,30 @@ impl From<GraphName> for JsTerm {
}
}
impl From<Variable> for JsTerm {
fn from(variable: Variable) -> Self {
JsTerm::Variable(variable.into())
}
}
impl From<Triple> for JsTerm {
fn from(triple: Triple) -> Self {
JsTerm::Quad(triple.into())
}
}
impl From<Arc<Triple>> for JsTerm {
fn from(triple: Arc<Triple>) -> Self {
triple.as_ref().clone().into()
}
}
impl From<Quad> for JsTerm {
fn from(quad: Quad) -> Self {
JsTerm::Quad(quad.into())
}
}
impl TryFrom<JsTerm> for NamedNode {
type Error = JsValue;
@ -408,6 +554,11 @@ impl TryFrom<JsTerm> for NamedNode {
literal.inner
)),
JsTerm::DefaultGraph(_) => Err(format_err!("The default graph is not a named node")),
JsTerm::Variable(variable) => Err(format_err!(
"The variable {} is not a named node",
variable.inner
)),
JsTerm::Quad(quad) => Err(format_err!("The quad {} is not a named node", quad.inner)),
}
}
}
@ -423,9 +574,17 @@ impl TryFrom<JsTerm> for NamedOrBlankNode {
"The literal {} is not a possible named or blank node term",
literal.inner
)),
JsTerm::DefaultGraph(_) => {
Err(format_err!("The default graph is not a possible RDF term"))
}
JsTerm::DefaultGraph(_) => Err(format_err!(
"The default graph is not a possible named or blank node term"
)),
JsTerm::Variable(variable) => Err(format_err!(
"The variable {} is not a possible named or blank node term",
variable.inner
)),
JsTerm::Quad(quad) => Err(format_err!(
"The quad {} is not a possible named or blank node term",
quad.inner
)),
}
}
}
@ -438,12 +597,17 @@ impl TryFrom<JsTerm> for Subject {
JsTerm::NamedNode(node) => Ok(node.into()),
JsTerm::BlankNode(node) => Ok(node.into()),
JsTerm::Literal(literal) => Err(format_err!(
"The literal {} is not a possible named or blank node term",
"The literal {} is not a possible RDF subject",
literal.inner
)),
JsTerm::DefaultGraph(_) => {
Err(format_err!("The default graph is not a possible RDF term"))
}
JsTerm::DefaultGraph(_) => Err(format_err!(
"The default graph is not a possible RDF subject"
)),
JsTerm::Variable(variable) => Err(format_err!(
"The variable {} is not a possible RDF subject",
variable.inner
)),
JsTerm::Quad(quad) => Ok(Triple::from(quad).into()),
}
}
}
@ -459,6 +623,11 @@ impl TryFrom<JsTerm> for Term {
JsTerm::DefaultGraph(_) => {
Err(format_err!("The default graph is not a possible RDF term"))
}
JsTerm::Variable(variable) => Err(format_err!(
"The variable {} is not a possible RDF term",
variable.inner
)),
JsTerm::Quad(quad) => Ok(Triple::from(quad).into()),
}
}
}
@ -475,72 +644,18 @@ impl TryFrom<JsTerm> for GraphName {
literal.inner
)),
JsTerm::DefaultGraph(_) => Ok(GraphName::DefaultGraph),
JsTerm::Variable(variable) => Err(format_err!(
"The variable {} is not a possible RDF term",
variable.inner
)),
JsTerm::Quad(quad) => Err(format_err!(
"The quad {} is not a possible RDF term",
quad.inner
)),
}
}
}
#[wasm_bindgen(js_name = Quad)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsQuad {
subject: JsTerm,
predicate: JsTerm,
object: JsTerm,
graph_name: JsTerm,
}
#[wasm_bindgen(js_class = Quad)]
impl JsQuad {
#[wasm_bindgen(getter = subject)]
pub fn subject(&self) -> JsValue {
self.subject.clone().into()
}
#[wasm_bindgen(getter = predicate)]
pub fn predicate(&self) -> JsValue {
self.predicate.clone().into()
}
#[wasm_bindgen(getter = object)]
pub fn object(&self) -> JsValue {
self.object.clone().into()
}
#[wasm_bindgen(getter = graph)]
pub fn graph(&self) -> JsValue {
self.graph_name.clone().into()
}
pub fn equals(&self, other: &JsValue) -> bool {
FromJsConverter::default()
.to_quad(other)
.map_or(false, |other| self == &other)
}
}
impl From<Quad> for JsQuad {
fn from(quad: Quad) -> Self {
Self {
subject: quad.subject.into(),
predicate: quad.predicate.into(),
object: quad.object.into(),
graph_name: quad.graph_name.into(),
}
}
}
impl TryFrom<JsQuad> for Quad {
type Error = JsValue;
fn try_from(quad: JsQuad) -> Result<Self, JsValue> {
Ok(Quad {
subject: Subject::try_from(quad.subject)?,
predicate: NamedNode::try_from(quad.predicate)?,
object: Term::try_from(quad.object)?,
graph_name: GraphName::try_from(quad.graph_name)?,
})
}
}
pub struct FromJsConverter {
term_type: JsValue,
value: JsValue,
@ -608,11 +723,31 @@ impl FromJsConverter {
}
}
"DefaultGraph" => Ok(JsTerm::DefaultGraph(JsDefaultGraph {})),
"Variable" => Ok(Variable::new(
&Reflect::get(&value, &self.value)?
.as_string()
.ok_or_else(|| format_err!("Variable should have a string value"))?,
)
.map_err(to_err)?
.into()),
"Quad" => Ok(self.to_quad(value)?.into()),
_ => Err(format_err!(
"The termType {} is not supported by Oxigraph",
term_type
)),
}
} else if term_type.is_undefined() {
// It's a quad without the proper type
if Reflect::has(value, &self.subject)?
&& Reflect::has(value, &self.predicate)?
&& Reflect::has(value, &self.object)?
{
Ok(self.to_quad(value)?.into())
} else {
Err(format_err!(
"RDF term objects should have a termType attribute"
))
}
} else {
Err(format_err!("The object termType field should be a string"))
}
@ -626,12 +761,31 @@ impl FromJsConverter {
}
}
pub fn to_quad(&self, value: &JsValue) -> Result<JsQuad, JsValue> {
Ok(JsQuad {
subject: self.to_term(&Reflect::get(value, &self.subject)?)?,
predicate: self.to_term(&Reflect::get(value, &self.predicate)?)?,
object: self.to_term(&Reflect::get(value, &self.object)?)?,
graph_name: self.to_term(&Reflect::get(value, &self.graph)?)?,
pub fn to_quad(&self, value: &JsValue) -> Result<Quad, JsValue> {
self.to_quad_from_parts(
&Reflect::get(&value, &self.subject)?,
&Reflect::get(&value, &self.predicate)?,
&Reflect::get(&value, &self.object)?,
&Reflect::get(&value, &self.graph)?,
)
}
pub fn to_quad_from_parts(
&self,
subject: &JsValue,
predicate: &JsValue,
object: &JsValue,
graph_name: &JsValue,
) -> Result<Quad, JsValue> {
Ok(Quad {
subject: Subject::try_from(self.to_term(subject)?)?,
predicate: NamedNode::try_from(self.to_term(predicate)?)?,
object: Term::try_from(self.to_term(object)?)?,
graph_name: if graph_name.is_undefined() {
GraphName::DefaultGraph
} else {
GraphName::try_from(self.to_term(&graph_name)?)?
},
})
}
}

@ -41,21 +41,21 @@ impl JsStore {
pub fn add(&self, quad: &JsValue) -> Result<(), JsValue> {
self.store
.insert(&self.from_js.to_quad(quad)?.try_into()?)
.insert(&self.from_js.to_quad(quad)?.into())
.map_err(to_err)?;
Ok(())
}
pub fn delete(&self, quad: &JsValue) -> Result<(), JsValue> {
self.store
.remove(&self.from_js.to_quad(quad)?.try_into()?)
.remove(&self.from_js.to_quad(quad)?)
.map_err(to_err)?;
Ok(())
}
pub fn has(&self, quad: &JsValue) -> Result<bool, JsValue> {
self.store
.contains(&self.from_js.to_quad(quad)?.try_into()?)
.contains(&self.from_js.to_quad(quad)?)
.map_err(to_err)
}

@ -5,22 +5,27 @@ const assert = require('assert')
const dataFactory = require('@rdfjs/data-model')
const ex = dataFactory.namedNode('http://example.com')
const triple = dataFactory.triple(
dataFactory.blankNode('s'),
dataFactory.namedNode('http://example.com/p'),
dataFactory.literal('o')
)
describe('Store', function () {
describe('#add()', function () {
it('an added quad should be in the store', function () {
const store = new Store()
store.add(dataFactory.triple(ex, ex, ex))
assert(store.has(dataFactory.triple(ex, ex, ex)))
store.add(dataFactory.triple(ex, ex, triple))
assert(store.has(dataFactory.triple(ex, ex, triple)))
})
})
describe('#delete()', function () {
it('an removed quad should not be in the store anymore', function () {
const store = new Store([dataFactory.triple(ex, ex, ex)])
assert(store.has(dataFactory.triple(ex, ex, ex)))
store.delete(dataFactory.triple(ex, ex, ex))
assert(!store.has(dataFactory.triple(ex, ex, ex)))
const store = new Store([dataFactory.triple(triple, ex, ex)])
assert(store.has(dataFactory.triple(triple, ex, ex)))
store.delete(dataFactory.triple(triple, ex, ex))
assert(!store.has(dataFactory.triple(triple, ex, ex)))
})
})

Loading…
Cancel
Save