wip and more tests

feat/orm
Laurin Weger 7 days ago
parent 2ae66373d9
commit 32c17eb543
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 304
      nextgraph/src/tests/orm.rs
  2. 83
      ng-verifier/src/orm/utils.rs
  3. 3
      package.json
  4. 3
      sdk/ng-sdk-js/examples/multi-framework-signals/package.json

@ -330,6 +330,29 @@ INSERT DATA {
async fn test_orm_creation() {
// Setup wallet and document
let (_wallet, session_id) = create_or_open_wallet().await;
// Tests below all in this test, to prevent waiting times through wallet creation.
// ===
test_orm_big_object(session_id).await;
// ===
test_orm_root_array(session_id).await;
// ===
test_orm_with_optional(session_id).await;
// ===
test_orm_literal(session_id).await;
// ===
test_orm_multi_type(session_id).await;
// ===
test_orm_nested(session_id).await;
}
async fn test_orm_big_object(session_id: u64) {
let doc_nuri = create_doc_with_data(
session_id,
r#"
@ -413,16 +436,6 @@ INSERT DATA {
}
}
cancel_fn();
//
// ===
test_orm_root_array(session_id).await;
// ===
test_orm_with_optional(session_id).await;
// ===
test_orm_literal(session_id).await;
}
async fn test_orm_root_array(session_id: u64) {
@ -655,9 +668,6 @@ INSERT DATA {
.into(),
);
// TODO =======
// obj3 valid even though it should not.
let shape_type = OrmShapeType {
schema,
shape: "http://example.org/OptionShape".to_string(),
@ -683,6 +693,274 @@ INSERT DATA {
}
cancel_fn();
}
async fn test_orm_multi_type(session_id: u64) {
let doc_nuri = create_doc_with_data(
session_id,
r#"
PREFIX ex: <http://example.org/>
INSERT DATA {
<urn:test:oj1>
ex:strOrNum "a string" ;
ex:strOrNum "another string" ;
ex:strOrNum 2 .
# Invalid because false is not string or number.
<urn:test:obj2>
ex:strOrNum "a string2" ;
ex:strOrNum 2 ;
ex:strOrNum false .
}
"#
.to_string(),
)
.await;
// Define the ORM schema
let mut schema = HashMap::new();
schema.insert(
"http://example.org/MultiTypeShape".to_string(),
OrmSchemaShape {
iri: "http://example.org/MultiTypeShape".to_string(),
predicates: vec![OrmSchemaPredicate {
iri: "http://example.org/strOrNum".to_string(),
extra: Some(true),
maxCardinality: -1,
minCardinality: 1,
readablePredicate: "strOrNum".to_string(),
dataTypes: vec![
OrmSchemaDataType {
valType: OrmSchemaLiteralType::string,
literals: None,
shape: None,
},
OrmSchemaDataType {
valType: OrmSchemaLiteralType::number,
literals: None,
shape: None,
},
],
}
.into()],
}
.into(),
);
let shape_type = OrmShapeType {
schema,
shape: "http://example.org/MultiTypeShape".to_string(),
};
let nuri = NuriV0::new_from(&doc_nuri).expect("parse nuri");
let (mut receiver, cancel_fn) = orm_start(nuri, shape_type, session_id)
.await
.expect("orm_start");
while let Some(app_response) = receiver.next().await {
let orm_json = match app_response {
AppResponse::V0(v) => match v {
AppResponseV0::OrmInitial(json) => Some(json),
_ => None,
},
}
.unwrap();
log_info!("ORM JSON arrived for multi type test\n: {:?}", orm_json);
break;
}
cancel_fn();
}
async fn test_orm_nested(session_id: u64) {
let doc_nuri = create_doc_with_data(
session_id,
r#"
PREFIX ex: <http://example.org/>
INSERT DATA {
# Valid
<urn:test:oj1>
ex:str "obj1 str" ;
ex:nestedWithExtra [
ex:nestedStr "obj1 nested with extra valid" ;
ex:nestedNum 2
] , [
# Invalid, nestedNum is missing but okay because extra.
ex:nestedStr "obj1 nested with extra invalid"
] ;
ex:nestedWithoutExtra [
ex:nestedStr "obj1 nested without extra valid" ;
ex:nestedNum 2
] .
# Invalid because nestedWithoutExtra has an invalid child.
<urn:test:oj2>
ex:str "obj2 str" ;
ex:nestedWithExtra [
ex:nestedStr "obj2: a nested string valid" ;
ex:nestedNum 2
] ;
ex:nestedWithoutExtra [
ex:nestedStr "obj2 nested without extra valid" ;
ex:nestedNum 2
] ,
# Invalid because nestedNum is missing.
[
ex:nestedStr "obj2 nested without extra invalid"
] .
}
"#
.to_string(),
)
.await;
// Define the ORM schema
let mut schema = HashMap::new();
schema.insert(
"http://example.org/RootShape".to_string(),
OrmSchemaShape {
iri: "http://example.org/RootShape".to_string(),
predicates: vec![
OrmSchemaPredicate {
iri: "http://example.org/str".to_string(),
extra: None,
maxCardinality: 1,
minCardinality: 1,
readablePredicate: "str".to_string(),
dataTypes: vec![OrmSchemaDataType {
valType: OrmSchemaLiteralType::string,
literals: None,
shape: None,
}],
}
.into(),
OrmSchemaPredicate {
iri: "http://example.org/nestedWithExtra".to_string(),
extra: Some(true),
maxCardinality: 1,
minCardinality: 1,
readablePredicate: "nestedWithExtra".to_string(),
dataTypes: vec![OrmSchemaDataType {
valType: OrmSchemaLiteralType::shape,
literals: None,
shape: Some("http://example.org/NestedShapeWithExtra".to_string()),
}],
}
.into(),
OrmSchemaPredicate {
iri: "http://example.org/nestedWithoutExtra".to_string(),
extra: Some(false),
maxCardinality: 1,
minCardinality: 1,
readablePredicate: "nestedWithoutExtra".to_string(),
dataTypes: vec![OrmSchemaDataType {
valType: OrmSchemaLiteralType::shape,
literals: None,
shape: Some("http://example.org/NestedShapeWithoutExtra".to_string()),
}],
}
.into(),
],
}
.into(),
);
schema.insert(
"http://example.org/NestedShapeWithExtra".to_string(),
OrmSchemaShape {
iri: "http://example.org/NestedShapeWithExtra".to_string(),
predicates: vec![
OrmSchemaPredicate {
iri: "http://example.org/nestedStr".to_string(),
extra: None,
readablePredicate: "nestedStr".to_string(),
maxCardinality: 1,
minCardinality: 1,
dataTypes: vec![OrmSchemaDataType {
valType: OrmSchemaLiteralType::string,
literals: None,
shape: None,
}],
}
.into(),
OrmSchemaPredicate {
iri: "http://example.org/nestedNum".to_string(),
extra: None,
readablePredicate: "nestedNum".to_string(),
maxCardinality: 1,
minCardinality: 1,
dataTypes: vec![OrmSchemaDataType {
valType: OrmSchemaLiteralType::number,
literals: None,
shape: None,
}],
}
.into(),
],
}
.into(),
);
schema.insert(
"http://example.org/NestedShapeWithoutExtra".to_string(),
OrmSchemaShape {
iri: "http://example.org/NestedShapeWithoutExtra".to_string(),
predicates: vec![
OrmSchemaPredicate {
iri: "http://example.org/nestedStr".to_string(),
extra: None,
readablePredicate: "nestedStr".to_string(),
maxCardinality: 1,
minCardinality: 1,
dataTypes: vec![OrmSchemaDataType {
valType: OrmSchemaLiteralType::string,
literals: None,
shape: None,
}],
}
.into(),
OrmSchemaPredicate {
iri: "http://example.org/nestedNum".to_string(),
extra: None,
readablePredicate: "nestedNum".to_string(),
maxCardinality: 1,
minCardinality: 1,
dataTypes: vec![OrmSchemaDataType {
valType: OrmSchemaLiteralType::number,
literals: None,
shape: None,
}],
}
.into(),
],
}
.into(),
);
let shape_type = OrmShapeType {
schema,
shape: "http://example.org/RootShape".to_string(),
};
let nuri = NuriV0::new_from(&doc_nuri).expect("parse nuri");
let (mut receiver, cancel_fn) = orm_start(nuri, shape_type, session_id)
.await
.expect("orm_start");
while let Some(app_response) = receiver.next().await {
let orm_json = match app_response {
AppResponse::V0(v) => match v {
AppResponseV0::OrmInitial(json) => Some(json),
_ => None,
},
}
.unwrap();
log_info!("ORM JSON arrived for nested test\n: {:?}", orm_json);
break;
}
cancel_fn();
}
//
// Helpers
fn create_big_schema() -> OrmSchema {

@ -88,27 +88,26 @@ pub fn shape_type_to_sparql(
where_statements: &mut Vec<String>,
var_counter: &mut i32,
visited_shapes: &mut HashSet<String>,
in_recursion: bool,
) {
// Prevent infinite recursion on cyclic schemas.
// TODO: We could handle this as IRI string reference.
if visited_shapes.contains(&shape.iri) {
return;
}
let mut new_where_statements: Vec<String> = vec![];
let mut new_construct_statements: Vec<String> = vec![];
visited_shapes.insert(shape.iri.clone());
// Add statements for each predicate.
for predicate in &shape.predicates {
let mut union_branches = Vec::new();
let mut allowed_literals = Vec::new();
// Predicate constraints might have more than one acceptable data type. Traverse each.
// It is assumed that constant literals, nested shapes and regular types are not mixed.
// Predicate constraints might have more than one acceptable nested shape. Traverse each.
for datatype in &predicate.dataTypes {
if datatype.valType == OrmSchemaLiteralType::literal {
// Collect allowed literals and as strings
// (already in SPARQL-format, e.g. `"a astring"`, `<http:ex.co/>`, `true`, or `42`).
allowed_literals.extend(literal_to_sparql_str(datatype.clone()));
} else if datatype.valType == OrmSchemaLiteralType::shape {
if datatype.valType == OrmSchemaLiteralType::shape {
let shape_iri = &datatype.shape.clone().unwrap();
let nested_shape = schema.get(shape_iri).unwrap();
@ -117,7 +116,7 @@ pub fn shape_type_to_sparql(
// Each shape option gets its own var.
let obj_var_name = get_new_var_name(var_counter);
construct_statements.push(format!(
new_construct_statements.push(format!(
" ?{} <{}> ?{}",
subject_var_name, predicate.iri, obj_var_name
));
@ -136,33 +135,15 @@ pub fn shape_type_to_sparql(
where_statements,
var_counter,
visited_shapes,
true,
);
}
}
// The where statement which might be wrapped in OPTIONAL.
// The where statement (which may be wrapped in OPTIONAL).
let where_body: String;
if !allowed_literals.is_empty()
&& !predicate.extra.unwrap_or(false)
&& predicate.minCardinality > 0
{
// If we have literal requirements and they are not optional ("extra"),
// Add CONSTRUCT, WHERE, and FILTER.
let pred_var_name = get_new_var_name(var_counter);
construct_statements.push(format!(
" ?{} <{}> ?{}",
subject_var_name, predicate.iri, pred_var_name
));
where_body = format!(
" ?{s} <{p}> ?{o} . \n FILTER(?{o} IN ({lits}))",
s = subject_var_name,
p = predicate.iri,
o = pred_var_name,
lits = allowed_literals.join(", ")
);
} else if !union_branches.is_empty() {
if !union_branches.is_empty() {
// We have nested shape(s) which were already added to CONSTRUCT above.
// Join them with UNION.
@ -174,25 +155,50 @@ pub fn shape_type_to_sparql(
} else {
// Regular predicate data type. Just add basic CONSTRUCT and WHERE statements.
let pred_var_name = get_new_var_name(var_counter);
construct_statements.push(format!(
let obj_var_name = get_new_var_name(var_counter);
new_construct_statements.push(format!(
" ?{} <{}> ?{}",
subject_var_name, predicate.iri, pred_var_name
subject_var_name, predicate.iri, obj_var_name
));
where_body = format!(
" ?{} <{}> ?{}",
subject_var_name, predicate.iri, pred_var_name
subject_var_name, predicate.iri, obj_var_name
);
}
// Wrap in optional, if necessary.
// Wrap in optional, if predicate is optional
if predicate.minCardinality < 1 {
where_statements.push(format!(" OPTIONAL {{\n{}\n }}", where_body));
new_where_statements.push(format!(" OPTIONAL {{\n{}\n }}", where_body));
} else {
where_statements.push(where_body);
new_where_statements.push(where_body);
};
}
if in_recursion {
// All statements in recursive objects need to be optional
// because we want to fetch _all_ nested objects,
// invalid ones too, for later validation.
let pred_var_name = get_new_var_name(var_counter);
let obj_var_name = get_new_var_name(var_counter);
// The "catch any triple in subject" where statement
construct_statements.push(format!(
" ?{} ?{} ?{}",
subject_var_name, pred_var_name, obj_var_name
));
let joined_where_statements = new_where_statements.join(" .\n");
// We do a join of the where statements (which will take care of querying further nested objects)
// and the "catch any triple in subject" where statement.
where_statements.push(format!(
" {{?{} ?{} ?{}}}\n UNION {{\n {}\n }}",
subject_var_name, pred_var_name, obj_var_name, joined_where_statements
));
} else {
where_statements.append(&mut new_where_statements);
construct_statements.append(&mut new_construct_statements);
}
visited_shapes.remove(&shape.iri);
}
@ -209,11 +215,12 @@ pub fn shape_type_to_sparql(
&mut where_statements,
&mut var_counter,
&mut visited_shapes,
false,
);
// Filter subjects, if present.
if let Some(subjects) = filter_subjects {
log_debug!("filter_subjects: {:?}", subjects);
// log_debug!("filter_subjects: {:?}", subjects);
let subjects_str = subjects
.iter()
.map(|s| format!("<{}>", s))

@ -15,5 +15,8 @@
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0"
},
"engines": {
"node": ">=22.18"
},
"packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b"
}

@ -43,5 +43,8 @@
"@types/react-dom": "19.1.7",
"vite": "7.1.3",
"vitest": "^3.2.4"
},
"engines": {
"node": ">=22.18"
}
}

Loading…
Cancel
Save