SPARQL endpoint for query and update

master
Niko PLP 1 month ago
parent 273a13b4e5
commit 0287993dbf
  1. 22
      Cargo.lock
  2. 60
      nextgraph/src/local_broker.rs
  3. 1
      ng-broker/src/server_broker.rs
  4. 3
      ng-net/src/actors/app/request.rs
  5. 50
      ng-net/src/app_protocol.rs
  6. 4
      ng-sdk-js/Cargo.toml
  7. 30
      ng-sdk-js/app-node/index.js
  8. 69
      ng-sdk-js/src/lib.rs
  9. 821
      ng-sdk-js/src/model.rs
  10. 87
      ng-verifier/src/request_processor.rs
  11. 3
      ng-verifier/src/verifier.rs

22
Cargo.lock generated

@ -3451,9 +3451,11 @@ dependencies = [
"ng-repo",
"ng-wallet",
"once_cell",
"oxrdf",
"rand 0.7.3",
"serde",
"serde-wasm-bindgen",
"serde_bare",
"serde_bytes",
"serde_json",
"wasm-bindgen",
@ -3884,19 +3886,20 @@ checksum = "d05417ee46e2eb40dd9d590b4d67fc2408208b3a48a6b7f71d2bc1d7ce12a3e0"
[[package]]
name = "oxrdf"
version = "0.2.0-alpha.4"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#a4e2847810c384811c3efc0b844c1cbdb997cb71"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#c7f873f904617c201e359196717eb2133d91cef5"
dependencies = [
"oxilangtag",
"oxiri",
"oxsdatatypes",
"rand 0.8.5",
"serde",
"thiserror",
]
[[package]]
name = "oxrdfio"
version = "0.1.0-alpha.5"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#a4e2847810c384811c3efc0b844c1cbdb997cb71"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#c7f873f904617c201e359196717eb2133d91cef5"
dependencies = [
"oxrdf",
"oxrdfxml",
@ -3907,7 +3910,7 @@ dependencies = [
[[package]]
name = "oxrdfxml"
version = "0.1.0-alpha.5"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#a4e2847810c384811c3efc0b844c1cbdb997cb71"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#c7f873f904617c201e359196717eb2133d91cef5"
dependencies = [
"oxilangtag",
"oxiri",
@ -3919,16 +3922,17 @@ dependencies = [
[[package]]
name = "oxsdatatypes"
version = "0.2.0-alpha.1"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#a4e2847810c384811c3efc0b844c1cbdb997cb71"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#c7f873f904617c201e359196717eb2133d91cef5"
dependencies = [
"js-sys",
"serde",
"thiserror",
]
[[package]]
name = "oxttl"
version = "0.1.0-alpha.5"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#a4e2847810c384811c3efc0b844c1cbdb997cb71"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#c7f873f904617c201e359196717eb2133d91cef5"
dependencies = [
"memchr",
"oxilangtag",
@ -5238,7 +5242,7 @@ dependencies = [
[[package]]
name = "sparesults"
version = "0.2.0-alpha.4"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#a4e2847810c384811c3efc0b844c1cbdb997cb71"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#c7f873f904617c201e359196717eb2133d91cef5"
dependencies = [
"json-event-parser",
"memchr",
@ -5250,7 +5254,7 @@ dependencies = [
[[package]]
name = "spargebra"
version = "0.3.0-alpha.4"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#a4e2847810c384811c3efc0b844c1cbdb997cb71"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#c7f873f904617c201e359196717eb2133d91cef5"
dependencies = [
"oxilangtag",
"oxiri",
@ -5263,7 +5267,7 @@ dependencies = [
[[package]]
name = "sparopt"
version = "0.1.0-alpha.5-dev"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#a4e2847810c384811c3efc0b844c1cbdb997cb71"
source = "git+https://git.nextgraph.org/NextGraph/oxigraph.git?branch=main#c7f873f904617c201e359196717eb2133d91cef5"
dependencies = [
"oxrdf",
"rand 0.8.5",
@ -6364,6 +6368,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
dependencies = [
"cfg-if",
"serde",
"serde_json",
"wasm-bindgen-macro",
]

@ -2071,21 +2071,26 @@ pub async fn app_request(request: AppRequest) -> Result<AppResponse, NgError> {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(request.session_id())?;
match &broker.config {
LocalBrokerConfig::Headless(_) => {
broker.send_request_headless(request).await
},
_ => {
let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(request.session_id())?;
if is_remote {
let session = broker.remote_sessions_list[real_session_id]
.as_ref()
.ok_or(NgError::SessionNotFound)?;
session.send_request(request).await
} else {
let session = broker.opened_sessions_list[real_session_id]
.as_mut()
.ok_or(NgError::SessionNotFound)?;
session.verifier.app_request(request).await
if is_remote {
let session = broker.remote_sessions_list[real_session_id]
.as_ref()
.ok_or(NgError::SessionNotFound)?;
session.send_request(request).await
} else {
let session = broker.opened_sessions_list[real_session_id]
.as_mut()
.ok_or(NgError::SessionNotFound)?;
session.verifier.app_request(request).await
}
}
}
}
/// process any type of app request that returns a stream of values
@ -2096,18 +2101,25 @@ pub async fn app_request_stream(
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(request.session_id())?;
match &broker.config {
LocalBrokerConfig::Headless(_) => {
broker.send_request_stream_headless(request).await
},
_ => {
let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(request.session_id())?;
if is_remote {
let session = broker.remote_sessions_list[real_session_id]
.as_ref()
.ok_or(NgError::SessionNotFound)?;
session.send_request_stream(request).await
} else {
let session = broker.opened_sessions_list[real_session_id]
.as_mut()
.ok_or(NgError::SessionNotFound)?;
session.verifier.app_request_stream(request).await
if is_remote {
let session = broker.remote_sessions_list[real_session_id]
.as_ref()
.ok_or(NgError::SessionNotFound)?;
session.send_request_stream(request).await
} else {
let session = broker.opened_sessions_list[real_session_id]
.as_mut()
.ok_or(NgError::SessionNotFound)?;
session.verifier.app_request_stream(request).await
}
}
}
}

@ -462,7 +462,6 @@ impl IServerBroker for ServerBroker {
.app_request(req)
.await
.map_err(|e| e.into());
fsm.lock()
.await
.send_in_reply_to(res.into(), request_id)

@ -101,7 +101,8 @@ impl From<AppResponse> for AppMessage {
impl From<AppResponse> for ProtocolMessage {
fn from(response: AppResponse) -> ProtocolMessage {
response.into()
let app_msg: AppMessage = response.into();
app_msg.into()
}
}

@ -115,6 +115,18 @@ impl NuriV0 {
locator: vec![],
}
}
pub fn new_entire_user_site() -> Self {
Self {
target: NuriTargetV0::UserSite,
entire_store: false,
object: None,
branch: None,
overlay: None,
access: vec![],
topic: None,
locator: vec![],
}
}
pub fn new(_from: String) -> Self {
todo!();
}
@ -134,10 +146,21 @@ pub enum AppRequestCommandV0 {
impl AppRequestCommandV0 {
pub fn is_stream(&self) -> bool {
match self {
Self::FilePut | Self::Create | Self::Delete | Self::UnPin | Self::Pin => false,
Self::Fetch(_) | Self::FileGet => true,
Self::Fetch(AppFetchContentV0::Subscribe) | Self::FileGet => true,
Self::FilePut
| Self::Create
| Self::Delete
| Self::UnPin
| Self::Pin
| Self::Fetch(_) => false,
}
}
pub fn new_read_query() -> Self {
AppRequestCommandV0::Fetch(AppFetchContentV0::ReadQuery)
}
pub fn new_write_query() -> Self {
AppRequestCommandV0::Fetch(AppFetchContentV0::WriteQuery)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -323,6 +346,12 @@ pub enum AppRequestPayload {
V0(AppRequestPayloadV0),
}
impl AppRequestPayload {
pub fn new_sparql_query(query: String) -> Self {
AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0(query)))
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DiscretePatch {
/// A yrs::Update
@ -402,11 +431,26 @@ pub enum AppResponseV0 {
#[serde(with = "serde_bytes")]
FileBinary(Vec<u8>),
FileMeta(FileMetaV0),
QueryResult, // see sparesults
#[serde(with = "serde_bytes")]
QueryResult(Vec<u8>), // a serialized [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/)
#[serde(with = "serde_bytes")]
Graph(Vec<u8>), // a serde serialization of a list of triples. can be transformed on the client side to RDF-JS data model, or JSON-LD, or else (Turtle,...) http://rdf.js.org/data-model-spec/
Ok,
True,
False,
Error(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AppResponse {
V0(AppResponseV0),
}
impl AppResponse {
pub fn error(err: String) -> Self {
AppResponse::V0(AppResponseV0::Error(err))
}
pub fn ok() -> Self {
AppResponse::V0(AppResponseV0::Ok)
}
}

@ -21,18 +21,20 @@ crate-type = ["cdylib"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
serde_json = "1.0"
async-std = { version = "1.12.0", features = ["attributes","unstable"] }
once_cell = "1.17.1"
getrandom = { version = "0.1.1", features = ["wasm-bindgen"] }
rand = { version = "0.7", features = ["getrandom"] }
wasm-bindgen = "0.2"
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
ng-repo = { path = "../ng-repo" }
ng-net = { path = "../ng-net" }
ng-client-ws = { path = "../ng-client-ws" }
ng-wallet = { path = "../ng-wallet" }
nextgraph = { path = "../nextgraph" }
oxrdf = { git = "https://git.nextgraph.org/NextGraph/oxigraph.git", branch="main", features = ["rdf-star", "oxsdatatypes"] }
# [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]
# version = "0.2.7"
# features = ["js"]

@ -19,22 +19,38 @@ let config = {
};
ng.init_headless(config).then( async() => {
let session_id;
try {
//let user_id = "ABIojb8XGAGCL4R_-81Kix8vJnSsfpvu8jwi6T-wTQWt";
//let user_id = "ABA1FBm8ofqCXutaf96pRTWvgXDaCG2JLuRlthzoV4a2";
let user_id = "AJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE";
//let user_id = await ng.admin_create_user(config);
let user_id = await ng.admin_create_user(config);
console.log("user created: ",user_id);
let session = await ng.session_headless_start(user_id);
let other_user_id = "AJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE";
let session = await ng.session_headless_start(other_user_id);
session_id = session.session_id;
console.log(session);
let sparql_result = await ng.sparql_query(session.session_id, "SELECT * WHERE { ?s ?p ?o }");
console.log(sparql_result);
let quads = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }");
for (const q of quads) {
console.log(q.subject.toString(), q.predicate.toString(), q.object.toString(), q.graph.toString())
}
let result = await ng.sparql_update(session.session_id, "INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }");
console.log(result);
let res = await ng.session_headless_stop(session.session_id, true);
// the 2nd argument `false` means do not `force_close` the dataset.
// it stays in memory even when the session is stopped. (not all the dataset is in memory. just some metadata)
// if you set this to true, the dataset is closed and removed from memory on the server.
// next time you will open a session for this user, the dataset will be loaded again.
let res = await ng.session_headless_stop(session.session_id, false);
console.log(res);
} catch (e) {
console.error(e);
if (session_id) await ng.session_headless_stop(session_id, true);
}
})
.catch(err => {

@ -11,6 +11,8 @@
#![cfg(target_arch = "wasm32")]
mod model;
use std::collections::HashMap;
use std::net::IpAddr;
use std::str::FromStr;
@ -22,6 +24,8 @@ use serde::{Deserialize, Serialize};
use serde_json::json;
// use js_sys::Reflect;
use async_std::stream::StreamExt;
use js_sys::Array;
use oxrdf::Triple;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
@ -53,6 +57,8 @@ use ng_wallet::*;
use nextgraph::local_broker::*;
use crate::model::*;
#[wasm_bindgen]
pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue {
let res = retrieve_local_bootstrap(location, invite.as_string(), false).await;
@ -200,6 +206,68 @@ pub async fn session_headless_stop(session_id: JsValue, force_close: bool) -> Re
Ok(())
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen]
pub async fn sparql_query(session_id: JsValue, sparql: String) -> Result<JsValue, JsValue> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Invalid session_id".to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_read_query(),
nuri: NuriV0::new_entire_user_site(),
payload: Some(AppRequestPayload::new_sparql_query(sparql)),
session_id,
});
let response = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
let AppResponse::V0(res) = response;
match res {
AppResponseV0::False => return Ok(JsValue::FALSE),
AppResponseV0::True => return Ok(JsValue::TRUE),
AppResponseV0::Graph(graph) => {
let triples: Vec<Triple> = serde_bare::from_slice(&graph)
.map_err(|_| "Deserialization error of graph".to_string())?;
let results = Array::new();
for triple in triples {
results.push(&JsQuad::from(triple).into());
}
Ok(results.into())
}
AppResponseV0::QueryResult(buf) => {
let string = String::from_utf8(buf)
.map_err(|_| "Deserialization error of JSON QueryResult String".to_string())?;
js_sys::JSON::parse(&string)
}
AppResponseV0::Error(e) => Err(e.to_string().into()),
_ => Err("invalid AppResponse".to_string().into()),
}
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen]
pub async fn sparql_update(session_id: JsValue, sparql: String) -> Result<(), String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Invalid session_id".to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_write_query(),
nuri: NuriV0::new_entire_user_site(),
payload: Some(AppRequestPayload::new_sparql_query(sparql)),
session_id,
});
let _ = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
Ok(())
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen]
pub async fn admin_create_user(js_config: JsValue) -> Result<JsValue, String> {
@ -531,7 +599,6 @@ pub async fn app_request_stream(
}
}
}
//log_info!("END OF LOOP");
Ok(())
}

@ -0,0 +1,821 @@
#![allow(dead_code, clippy::inherent_to_string, clippy::unused_self)]
use js_sys::{Error, Reflect, UriError};
use oxrdf::Triple;
use oxrdf::*;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
#[macro_export]
macro_rules! format_err {
($msg:literal $(,)?) => {
::wasm_bindgen::JsValue::from(::js_sys::Error::new($msg))
};
($fmt:literal, $($arg:tt)*) => {
::wasm_bindgen::JsValue::from(::js_sys::Error::new(&format!($fmt, $($arg)*)))
};
}
#[allow(clippy::needless_pass_by_value)]
pub fn to_err(e: impl ToString) -> JsValue {
JsValue::from(Error::new(&e.to_string()))
}
thread_local! {
pub static FROM_JS: FromJsConverter = FromJsConverter::default();
}
#[wasm_bindgen(js_name = namedNode)]
pub fn named_node(value: String) -> Result<JsNamedNode, JsValue> {
NamedNode::new(value)
.map(Into::into)
.map_err(|v| UriError::new(&v.to_string()).into())
}
#[wasm_bindgen(js_name = blankNode)]
pub fn blank_node(value: Option<String>) -> Result<JsBlankNode, JsValue> {
Ok(if let Some(value) = value {
BlankNode::new(value).map_err(to_err)?
} else {
BlankNode::default()
}
.into())
}
#[wasm_bindgen]
pub fn literal(
value: Option<String>,
language_or_datatype: &JsValue,
) -> Result<JsLiteral, JsValue> {
if language_or_datatype.is_null() || language_or_datatype.is_undefined() {
Ok(Literal::new_simple_literal(value.unwrap_or_default()).into())
} else if language_or_datatype.is_string() {
Ok(Literal::new_language_tagged_literal(
value.unwrap_or_default(),
language_or_datatype.as_string().unwrap_or_default(),
)
.map_err(to_err)?
.into())
} else if let JsTerm::NamedNode(datatype) = FROM_JS.with(|c| c.to_term(language_or_datatype))? {
Ok(Literal::new_typed_literal(value.unwrap_or_default(), datatype).into())
} else {
Err(format_err!("The literal datatype should be a NamedNode"))
}
}
#[wasm_bindgen(js_name = defaultGraph)]
pub fn default_graph() -> JsDefaultGraph {
JsDefaultGraph
}
#[wasm_bindgen(js_name = variable)]
pub fn variable(value: String) -> Result<JsVariable, JsValue> {
Ok(Variable::new(value).map_err(to_err)?.into())
}
#[wasm_bindgen(js_name = triple)]
pub fn triple(subject: &JsValue, predicate: &JsValue, object: &JsValue) -> Result<JsQuad, JsValue> {
quad(subject, predicate, object, &JsValue::UNDEFINED)
}
#[wasm_bindgen(js_name = quad)]
pub fn quad(
subject: &JsValue,
predicate: &JsValue,
object: &JsValue,
graph: &JsValue,
) -> Result<JsQuad, JsValue> {
Ok(FROM_JS
.with(|c| c.to_quad_from_parts(subject, predicate, object, graph))?
.into())
}
#[wasm_bindgen(js_name = fromTerm)]
pub fn from_term(original: &JsValue) -> Result<JsValue, JsValue> {
Ok(if original.is_null() {
JsValue::NULL
} else {
FROM_JS.with(|c| c.to_term(original))?.into()
})
}
#[wasm_bindgen(js_name = fromQuad)]
pub fn from_quad(original: &JsValue) -> Result<JsValue, JsValue> {
Ok(if original.is_null() {
JsValue::NULL
} else {
JsQuad::from(FROM_JS.with(|c| c.to_quad(original))?).into()
})
}
#[wasm_bindgen(js_name = NamedNode)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsNamedNode {
inner: NamedNode,
}
#[wasm_bindgen(js_class = NamedNode)]
impl JsNamedNode {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"NamedNode".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
self.inner.as_str().to_owned()
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::NamedNode(other))) =
FromJsConverter::default().to_optional_term(other)
{
self == &other
} else {
false
}
}
}
impl From<NamedNode> for JsNamedNode {
fn from(inner: NamedNode) -> Self {
Self { inner }
}
}
impl From<JsNamedNode> for NamedNode {
fn from(node: JsNamedNode) -> Self {
node.inner
}
}
impl From<JsNamedNode> for NamedOrBlankNode {
fn from(node: JsNamedNode) -> Self {
node.inner.into()
}
}
impl From<JsNamedNode> for Subject {
fn from(node: JsNamedNode) -> Self {
node.inner.into()
}
}
impl From<JsNamedNode> for Term {
fn from(node: JsNamedNode) -> Self {
node.inner.into()
}
}
impl From<JsNamedNode> for GraphName {
fn from(node: JsNamedNode) -> Self {
node.inner.into()
}
}
#[wasm_bindgen(js_name = BlankNode)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsBlankNode {
inner: BlankNode,
}
#[wasm_bindgen(js_class = BlankNode)]
impl JsBlankNode {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"BlankNode".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
self.inner.as_str().to_owned()
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::BlankNode(other))) =
FromJsConverter::default().to_optional_term(other)
{
self == &other
} else {
false
}
}
}
impl From<BlankNode> for JsBlankNode {
fn from(inner: BlankNode) -> Self {
Self { inner }
}
}
impl From<JsBlankNode> for BlankNode {
fn from(node: JsBlankNode) -> Self {
node.inner
}
}
impl From<JsBlankNode> for NamedOrBlankNode {
fn from(node: JsBlankNode) -> Self {
node.inner.into()
}
}
impl From<JsBlankNode> for Subject {
fn from(node: JsBlankNode) -> Self {
node.inner.into()
}
}
impl From<JsBlankNode> for Term {
fn from(node: JsBlankNode) -> Self {
node.inner.into()
}
}
impl From<JsBlankNode> for GraphName {
fn from(node: JsBlankNode) -> Self {
node.inner.into()
}
}
#[wasm_bindgen(js_name = Literal)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsLiteral {
inner: Literal,
}
#[wasm_bindgen(js_class = Literal)]
impl JsLiteral {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"Literal".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
self.inner.value().to_owned()
}
#[wasm_bindgen(getter)]
pub fn language(&self) -> String {
self.inner.language().unwrap_or("").to_owned()
}
#[wasm_bindgen(getter)]
pub fn datatype(&self) -> JsNamedNode {
self.inner.datatype().into_owned().into()
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::Literal(other))) = FromJsConverter::default().to_optional_term(other)
{
self == &other
} else {
false
}
}
}
impl From<Literal> for JsLiteral {
fn from(inner: Literal) -> Self {
Self { inner }
}
}
impl From<JsLiteral> for Literal {
fn from(node: JsLiteral) -> Self {
node.inner
}
}
impl From<JsLiteral> for Term {
fn from(node: JsLiteral) -> Self {
node.inner.into()
}
}
#[wasm_bindgen(js_name = DefaultGraph)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsDefaultGraph;
#[wasm_bindgen(js_class = DefaultGraph)]
impl JsDefaultGraph {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"DefaultGraph".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
String::new()
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
"DEFAULT".to_owned()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::DefaultGraph(other))) =
FromJsConverter::default().to_optional_term(other)
{
self == &other
} else {
false
}
}
}
#[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()
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
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 {
String::new()
}
#[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()
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
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 {
fn from(value: JsTerm) -> Self {
match value {
JsTerm::NamedNode(v) => v.into(),
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(),
}
}
}
impl From<NamedNode> for JsTerm {
fn from(node: NamedNode) -> Self {
Self::NamedNode(node.into())
}
}
impl From<BlankNode> for JsTerm {
fn from(node: BlankNode) -> Self {
Self::BlankNode(node.into())
}
}
impl From<Literal> for JsTerm {
fn from(literal: Literal) -> Self {
Self::Literal(literal.into())
}
}
impl From<NamedOrBlankNode> for JsTerm {
fn from(node: NamedOrBlankNode) -> Self {
match node {
NamedOrBlankNode::NamedNode(node) => node.into(),
NamedOrBlankNode::BlankNode(node) => node.into(),
}
}
}
impl From<Subject> for JsTerm {
fn from(node: Subject) -> Self {
match node {
Subject::NamedNode(node) => node.into(),
Subject::BlankNode(node) => node.into(),
Subject::Triple(node) => node.into(),
}
}
}
impl From<Term> for JsTerm {
fn from(term: Term) -> Self {
match term {
Term::NamedNode(node) => node.into(),
Term::BlankNode(node) => node.into(),
Term::Literal(literal) => literal.into(),
Term::Triple(node) => node.into(),
}
}
}
impl From<GraphName> for JsTerm {
fn from(name: GraphName) -> Self {
match name {
GraphName::NamedNode(node) => node.into(),
GraphName::BlankNode(node) => node.into(),
GraphName::DefaultGraph => Self::DefaultGraph(JsDefaultGraph),
}
}
}
impl From<Variable> for JsTerm {
fn from(variable: Variable) -> Self {
Self::Variable(variable.into())
}
}
impl From<Triple> for JsTerm {
fn from(triple: Triple) -> Self {
Self::Quad(triple.into())
}
}
impl From<Box<Triple>> for JsTerm {
fn from(triple: Box<Triple>) -> Self {
triple.as_ref().clone().into()
}
}
impl From<Quad> for JsTerm {
fn from(quad: Quad) -> Self {
Self::Quad(quad.into())
}
}
impl TryFrom<JsTerm> for NamedNode {
type Error = JsValue;
fn try_from(value: JsTerm) -> Result<Self, Self::Error> {
match value {
JsTerm::NamedNode(node) => Ok(node.into()),
JsTerm::BlankNode(node) => Err(format_err!(
"The blank node {} is not a named node",
node.inner
)),
JsTerm::Literal(literal) => Err(format_err!(
"The literal {} is not a named node",
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)),
}
}
}
impl TryFrom<JsTerm> for NamedOrBlankNode {
type Error = JsValue;
fn try_from(value: JsTerm) -> Result<Self, Self::Error> {
match value {
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",
literal.inner
)),
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
)),
}
}
}
impl TryFrom<JsTerm> for Subject {
type Error = JsValue;
fn try_from(value: JsTerm) -> Result<Self, Self::Error> {
match value {
JsTerm::NamedNode(node) => Ok(node.into()),
JsTerm::BlankNode(node) => Ok(node.into()),
JsTerm::Literal(literal) => Err(format_err!(
"The literal {} is not a possible RDF subject",
literal.inner
)),
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()),
}
}
}
impl TryFrom<JsTerm> for Term {
type Error = JsValue;
fn try_from(value: JsTerm) -> Result<Self, Self::Error> {
match value {
JsTerm::NamedNode(node) => Ok(node.into()),
JsTerm::BlankNode(node) => Ok(node.into()),
JsTerm::Literal(literal) => Ok(literal.into()),
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()),
}
}
}
impl TryFrom<JsTerm> for GraphName {
type Error = JsValue;
fn try_from(value: JsTerm) -> Result<Self, Self::Error> {
match value {
JsTerm::NamedNode(node) => Ok(node.into()),
JsTerm::BlankNode(node) => Ok(node.into()),
JsTerm::Literal(literal) => Err(format_err!(
"The literal {} is not a possible graph name",
literal.inner
)),
JsTerm::DefaultGraph(_) => Ok(Self::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
)),
}
}
}
pub struct FromJsConverter {
term_type: JsValue,
value: JsValue,
language: JsValue,
datatype: JsValue,
subject: JsValue,
predicate: JsValue,
object: JsValue,
graph: JsValue,
}
impl Default for FromJsConverter {
fn default() -> Self {
Self {
term_type: JsValue::from_str("termType"),
value: JsValue::from_str("value"),
language: JsValue::from_str("language"),
datatype: JsValue::from_str("datatype"),
subject: JsValue::from_str("subject"),
predicate: JsValue::from_str("predicate"),
object: JsValue::from_str("object"),
graph: JsValue::from_str("graph"),
}
}
}
impl FromJsConverter {
pub fn to_term(&self, value: &JsValue) -> Result<JsTerm, JsValue> {
let term_type = Reflect::get(value, &self.term_type)?;
if let Some(term_type) = term_type.as_string() {
match term_type.as_str() {
"NamedNode" => Ok(NamedNode::new(
Reflect::get(value, &self.value)?
.as_string()
.ok_or_else(|| format_err!("NamedNode should have a string value"))?,
)
.map_err(|v| UriError::new(&v.to_string()))?
.into()),
"BlankNode" => Ok(BlankNode::new(
Reflect::get(value, &self.value)?
.as_string()
.ok_or_else(|| format_err!("BlankNode should have a string value"))?,
)
.map_err(to_err)?
.into()),
"Literal" => {
if let JsTerm::NamedNode(datatype) =
self.to_term(&Reflect::get(value, &self.datatype)?)?
{
let datatype = NamedNode::from(datatype);
let literal_value = Reflect::get(value, &self.value)?
.as_string()
.ok_or_else(|| format_err!("Literal should have a string value"))?;
Ok(match datatype.as_str() {
"http://www.w3.org/2001/XMLSchema#string" => Literal::new_simple_literal(literal_value),
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" => Literal::new_language_tagged_literal(literal_value, Reflect::get(value, &self.language)?.as_string().ok_or_else(
|| format_err!("Literal with rdf:langString datatype should have a language"),
)?).map_err(to_err)?,
_ => Literal::new_typed_literal(literal_value, datatype)
}.into())
} else {
Err(format_err!(
"Literal should have a datatype that is a NamedNode"
))
}
}
"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 {term_type} is not supported by Oxigraph"
)),
}
} 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"))
}
}
pub fn to_optional_term(&self, value: &JsValue) -> Result<Option<JsTerm>, JsValue> {
if value.is_null() || value.is_undefined() {
Ok(None)
} else {
self.to_term(value).map(Some)
}
}
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)?)?
},
})
}
}

@ -14,6 +14,7 @@ use std::sync::Arc;
use futures::channel::mpsc;
use futures::SinkExt;
use futures::StreamExt;
use ng_oxigraph::sparql::{results::*, Query, QueryResults};
use ng_repo::errors::*;
use ng_repo::file::{RandomAccessFile, ReadFile};
@ -152,6 +153,45 @@ impl Verifier {
Ok((repo_id, branch, store_repo))
}
pub fn handle_query_results(results: QueryResults) -> Result<AppResponse, String> {
Ok(match results {
QueryResults::Solutions(solutions) => {
let serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Json);
let mut solutions_writer = serializer
.serialize_solutions_to_write(Vec::new(), solutions.variables().to_vec())
.map_err(|_| "QueryResult serializer error")?;
for solution in solutions {
solutions_writer
.write(&solution.map_err(|e| e.to_string())?)
.map_err(|_| "QueryResult serializer error")?;
}
AppResponse::V0(AppResponseV0::QueryResult(
solutions_writer
.finish()
.map_err(|_| "QueryResult serializer error")?,
))
}
QueryResults::Boolean(b) => {
if b {
AppResponse::V0(AppResponseV0::True)
} else {
AppResponse::V0(AppResponseV0::False)
}
}
QueryResults::Graph(quads) => {
let mut results = vec![];
for quad in quads {
match quad {
Err(e) => return Ok(AppResponse::error(e.to_string())),
Ok(triple) => results.push(triple),
}
}
AppResponse::V0(AppResponseV0::Graph(serde_bare::to_vec(&results).unwrap()))
}
})
}
pub(crate) async fn process(
&mut self,
command: &AppRequestCommandV0,
@ -159,6 +199,53 @@ impl Verifier {
payload: Option<AppRequestPayload>,
) -> Result<AppResponse, NgError> {
match command {
AppRequestCommandV0::Fetch(fetch) => match fetch {
AppFetchContentV0::ReadQuery => {
if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0(
query,
)))) = payload
{
log_debug!("query={}", query);
let store = self.graph_dataset.as_ref().unwrap();
let parsed = Query::parse(&query, None);
if parsed.is_err() {
return Ok(AppResponse::error(parsed.unwrap_err().to_string()));
}
let mut parsed = parsed.unwrap();
parsed.dataset_mut().set_default_graph_as_union();
let results = store.query(parsed);
return Ok(match results {
Err(e) => AppResponse::error(e.to_string()),
Ok(qr) => {
let res = Self::handle_query_results(qr);
match res {
Ok(ok) => ok,
Err(s) => AppResponse::error(s),
}
}
});
} else {
return Err(NgError::InvalidPayload);
}
}
AppFetchContentV0::WriteQuery => {
if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0(
query,
)))) = payload
{
let store = self.graph_dataset.as_ref().unwrap();
let res = store.update(&query);
return Ok(match res {
Err(e) => AppResponse::error(e.to_string()),
Ok(_) => AppResponse::ok(),
});
} else {
return Err(NgError::InvalidPayload);
}
}
_ => unimplemented!(),
},
AppRequestCommandV0::FilePut => match payload {
None => return Err(NgError::InvalidPayload),
Some(AppRequestPayload::V0(v0)) => match v0 {

@ -78,8 +78,7 @@ use crate::user_storage::UserStorage;
pub struct Verifier {
pub(crate) config: VerifierConfig,
pub connected_broker: BrokerPeerId,
#[allow(dead_code)]
graph_dataset: Option<ng_oxigraph::store::Store>,
pub(crate) graph_dataset: Option<ng_oxigraph::store::Store>,
pub(crate) user_storage: Option<Arc<Box<dyn UserStorage>>>,
block_storage: Option<Arc<std::sync::RwLock<dyn BlockStorage + Send + Sync>>>,
last_seq_num: u64,

Loading…
Cancel
Save