diff --git a/demo/dynamic-policy.ts b/demo/dynamic-policy.ts
new file mode 100644
index 0000000..6ac6988
--- /dev/null
+++ b/demo/dynamic-policy.ts
@@ -0,0 +1,107 @@
+import { Parser, Writer } from 'n3';
+import { materializePolicy, ODRLEngineMultipleSteps, ODRLEvaluator, resourceToOptimisedTurtle } from "../dist/index";
+
+
+// Variant on test case 036: Read request from Alice to resource X returns into yes (temporal lt) (Alice Request Read X).
+// https://github.com/SolidLabResearch/ODRL-Test-Suite/blob/main/data/test_cases/testcase-036-alice-read-x.ttl
+const policy = `
+@prefix odrl: .
+@prefix ex: .
+@prefix dct: .
+@prefix odrluc: .
+
+ a odrl:Set ;
+ odrl:uid ;
+ dct:description "ALICE may READ resource X when it is before 'ex:updateValue' (see sotw)." ;
+ dct:source ;
+ odrl:permission .
+
+ a odrl:Permission ;
+ odrl:assignee ex:alice ;
+ odrl:action odrl:read ;
+ odrl:target ex:x ;
+ odrl:constraint .
+
+ odrl:leftOperand odrl:dateTime ;
+ odrl:operator odrl:lt ;
+ odrl:rightOperandReference ex:operandReference1 .
+
+ex:operandReference1 a odrluc:OperandReference ;
+ odrluc:reference ex:externalSource ;
+ odrluc:path ex:updatedValue .
+`
+
+// state of the world -> with external value indicating the time
+const sotw = `
+@prefix ex: .
+@prefix temp: .
+@prefix dct: .
+
+ a ex:Sotw ;
+ ex:includes temp:currentTime .
+
+temp:currentTime dct:issued "2017-02-12T11:20:10.999Z"^^ .
+
+# external value that will be materialized in the policy
+ex:externalSource ex:updatedValue "2018-02-12T11:20:10.999Z"^^ .
+`
+
+const request = `
+@prefix odrl: .
+@prefix ex: .
+@prefix dct: .
+
+ a odrl:Request ;
+ odrl:uid ;
+ dct:description "Requesting Party ALICE requests to READ resource X." ;
+ odrl:permission .
+
+ a odrl:Permission ;
+ odrl:assignee ex:alice ;
+ odrl:action odrl:read ;
+ odrl:target ex:x .
+`
+
+async function main() {
+ const parser = new Parser()
+ const writer = new Writer()
+
+ const odrlDynamicPolicyQuads = parser.parse(policy)
+
+ const odrlRequestQuads = parser.parse(request)
+ const stateOfTheWorldQuads = parser.parse(sotw)
+
+ const instantiatedPolicyQuads = materializePolicy(odrlDynamicPolicyQuads, stateOfTheWorldQuads)
+
+ console.log("Instantiated Policy:")
+ console.log(writer.quadsToString(instantiatedPolicyQuads));
+
+
+ // reasoning over dynamic policy
+ const evaluator = new ODRLEvaluator(new ODRLEngineMultipleSteps());
+ const reasoningResult = await evaluator.evaluate(
+ odrlDynamicPolicyQuads,
+ odrlRequestQuads,
+ stateOfTheWorldQuads)
+
+ const output = writer.quadsToString(reasoningResult);
+ console.log("Compliance Report")
+ console.log(output);
+
+ const prefixes = {
+ 'odrl': 'http://www.w3.org/ns/odrl/2/',
+ 'ex': 'http://example.org/',
+ 'temp': 'http://example.com/request/',
+ 'dct': 'http://purl.org/dc/terms/',
+ 'xsd': 'http://www.w3.org/2001/XMLSchema#',
+ 'foaf': 'http://xmlns.com/foaf/0.1/',
+ 'report': 'https://w3id.org/force/compliance-report#'
+ }
+
+ // created report with N3
+ // @ts-ignore
+ console.log(resourceToOptimisedTurtle(reasoningResult, prefixes));
+
+}
+main()
+
diff --git a/package-lock.json b/package-lock.json
index 33a8bde..ab81132 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,9 +14,10 @@
"eyereasoner": "^16.18.4",
"n3": "^1.20.4",
"rdf-isomorphic": "^1.3.1",
+ "rdf-lens": "^1.3.5",
"rdf-parse": "^3.0.0",
"rdf-store-stream": "^2.0.1",
- "rdf-vocabulary": "^1.0.0",
+ "rdf-vocabulary": "^1.0.1",
"streamify-string": "^1.0.1",
"tmp": "^0.2.3",
"uuidv4": "^6.2.13"
@@ -1210,6 +1211,17 @@
"url": "https://github.com/sponsors/Borewit"
}
},
+ "node_modules/@treecg/types": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/@treecg/types/-/types-0.4.6.tgz",
+ "integrity": "sha512-iL8rBhAX4INvfuHZzJ1RELh1i/fspviylENZTk6D2p/xDazY1pTkd+y/hxKYVWCC0jHNolDWRsH7ozwaFGNQKA==",
+ "dependencies": {
+ "@rdfjs/types": "*",
+ "loglevel": "^1.8.1",
+ "loglevel-plugin-prefix": "^0.8.4",
+ "rdf-data-factory": "^1.1.0"
+ }
+ },
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
@@ -3504,6 +3516,23 @@
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
"dev": true
},
+ "node_modules/loglevel": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
+ "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
+ "engines": {
+ "node": ">= 0.6.0"
+ },
+ "funding": {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/loglevel"
+ }
+ },
+ "node_modules/loglevel-plugin-prefix": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz",
+ "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g=="
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3986,6 +4015,38 @@
"rdf-terms": "^1.7.0"
}
},
+ "node_modules/rdf-lens": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/rdf-lens/-/rdf-lens-1.3.5.tgz",
+ "integrity": "sha512-tSHPMRNnkJEYUXgm9Fb6Xbhc54wXfSsc9eIH3PkK6NLTGRUIMDVCcLxXVkj+IyvkemCjWPsweAx2/FX9ZwLZUQ==",
+ "dependencies": {
+ "@rdfjs/types": "^2.0.1",
+ "@treecg/types": "^0.4.6",
+ "@types/n3": "^1.21.1",
+ "n3": "^1.23.1",
+ "rdf-data-factory": "^2.0.2"
+ }
+ },
+ "node_modules/rdf-lens/node_modules/@rdfjs/types": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-2.0.1.tgz",
+ "integrity": "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/rdf-lens/node_modules/rdf-data-factory": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/rdf-data-factory/-/rdf-data-factory-2.0.2.tgz",
+ "integrity": "sha512-WzPoYHwQYWvIP9k+7IBLY1b4nIDitzAK4mA37WumAF/Cjvu/KOtYJH9IPZnUTWNSd5K2+pq4vrcE9WZC4sRHhg==",
+ "dependencies": {
+ "@rdfjs/types": "^2.0.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/rubensworks/"
+ }
+ },
"node_modules/rdf-parse": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/rdf-parse/-/rdf-parse-3.0.0.tgz",
@@ -4058,11 +4119,11 @@
}
},
"node_modules/rdf-vocabulary": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/rdf-vocabulary/-/rdf-vocabulary-1.0.0.tgz",
- "integrity": "sha512-aDMeOiyr7ZUx9UjjgQogXW5e+KfJSxa+HML5BIXE2lQiXp+qn9FggfnejBkcnqUCoypFtjDvejZ3UN3gGkurTw==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rdf-vocabulary/-/rdf-vocabulary-1.0.1.tgz",
+ "integrity": "sha512-0I7osG03qJ/7O/cEY/1wDLWnx+JBPoxPa9Iz/8856W7Ci7XNLBj8ZSWZCqVCOi/v5P6nenw5V9gkmPkg5TGZkg==",
"dependencies": {
- "@rdfjs/types": "^1.1.0"
+ "@rdfjs/types": "*"
}
},
"node_modules/rdfa-streaming-parser": {
diff --git a/package.json b/package.json
index c4c15b7..2adf9e9 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"clean": "rm -rf ./dist",
"demo:test-engine": "ts-node demo/test-n3-engine.ts",
"demo:test-evaluator": "ts-node demo/test-n3-evaluator.ts",
+ "demo:dynamic-policy": "ts-node demo/dynamic-policy.ts",
"prepare": "npm run build",
"release": "npm run build && npm publish --access public",
"test": "jest"
@@ -39,8 +40,8 @@
"@types/jest": "^29.5.12",
"@types/tmp": "^0.2.6",
"jest": "^29.7.0",
- "ts-jest": "^29.2.5",
"jest-rdf": "^2.0.0",
+ "ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
},
@@ -50,9 +51,10 @@
"eyereasoner": "^16.18.4",
"n3": "^1.20.4",
"rdf-isomorphic": "^1.3.1",
+ "rdf-lens": "^1.3.5",
"rdf-parse": "^3.0.0",
"rdf-store-stream": "^2.0.1",
- "rdf-vocabulary": "^1.0.0",
+ "rdf-vocabulary": "^1.0.1",
"streamify-string": "^1.0.1",
"tmp": "^0.2.3",
"uuidv4": "^6.2.13"
diff --git a/src/evaluator/DynamicConstraint.ts b/src/evaluator/DynamicConstraint.ts
new file mode 100644
index 0000000..b1edd26
--- /dev/null
+++ b/src/evaluator/DynamicConstraint.ts
@@ -0,0 +1,66 @@
+import { Quad, Term } from "@rdfjs/types";
+import { Literal, NamedNode, Store } from 'n3';
+import { BasicLensM, CBDLens, Cont, pred, ShaclPath } from "rdf-lens";
+import { ODRL, ODRLUC, RDF } from "../util/Vocabularies";
+
+// use RDF lens instead of clownface for SHACL Property Paths: https://ceur-ws.org/Vol-3759/paper13.pdf
+export const pathLens = pred(ODRLUC.terms.path)
+ .one()
+ .then(ShaclPath);
+
+// Utility function to extract the SHACL property path of the Dynamic ODRL Policy using `odrluc:path`
+export function getPath(id: Term, quads: Quad[]): BasicLensM {
+ return pathLens.execute({ id, quads }).mapAll((x) => x.id);
+}
+
+// Utility function to extract the value from an external resource using a SHACL Property path
+export function usePath(id: Term, quads: Quad[], lens: BasicLensM): Term[] {
+ return lens.execute({ id, quads })
+}
+
+// implements dynamic policy constraint algorithm
+// algorithm to fetch value using SHACL path from sotw in policy
+// will only materialize when RightOperandReference class is present
+export function materializePolicy(dynamicPolicy: Quad[], stateOfTheWorld: Quad[]): Quad[] {
+ const odrlPolicyStore = new Store(dynamicPolicy)
+ const odrlDynamicPolicyStore = new Store(dynamicPolicy)
+
+ // get all constraints containing references
+ const operandReferenceNodes = odrlDynamicPolicyStore.getSubjects(RDF.terms.type, ODRLUC.terms.OperandReference, null);
+
+ for (const operandReferenceNode of operandReferenceNodes) {
+ // ODRL constraint that has that given rightOperandReference
+ const constraintNodes = odrlDynamicPolicyStore.getSubjects(ODRL.terms.rightOperandReference, operandReferenceNode, null)
+
+ // external resource
+ const externalResource = odrlDynamicPolicyStore.getObjects(operandReferenceNode, ODRLUC.terms.reference, null)[0]
+ if (!externalResource) {
+ throw Error("There is no source present in the Dynamic Policy Constraint.")
+ }
+
+ // SHACL Property path
+ const lens = getPath(operandReferenceNode, dynamicPolicy);
+
+ // extract SHACL Property Path using RDF-Lens
+ const getValue = usePath(externalResource, stateOfTheWorld, lens);
+ const instantiatedValue = getValue[0]; // according to the algorithm (see paper), there should be only one term
+
+ if (!(instantiatedValue instanceof NamedNode || instantiatedValue instanceof Literal)) {
+ // Now instantiatedValue is a valid value
+ throw Error("Instantiated value is not a proper value.");
+ }
+ for (const constraintNode of constraintNodes) {
+ // remove rightOperandRefrence triple from constraint
+ odrlPolicyStore.removeQuad(constraintNode, ODRL.terms.rightOperandReference, operandReferenceNode)
+ // add materialized rightOperand
+ odrlPolicyStore.addQuad(constraintNode, ODRL.terms.rightOperand, instantiatedValue)
+ }
+
+ // remove OperandReference triples from Policy store
+ const operandReferenceQuads = CBDLens.execute({id:operandReferenceNode, quads: dynamicPolicy})
+ odrlPolicyStore.removeQuads(operandReferenceQuads)
+ }
+ return odrlPolicyStore.getQuads(null, null, null, null)
+ }
+
+
\ No newline at end of file
diff --git a/src/evaluator/Evaluate.ts b/src/evaluator/Evaluate.ts
index 8ebf411..78a5646 100644
--- a/src/evaluator/Evaluate.ts
+++ b/src/evaluator/Evaluate.ts
@@ -1,6 +1,7 @@
import type { Quad } from '@rdfjs/types';
import { ODRLEngine, Engine} from './Engine';
import { RDFValidator, TripleTermValidator, SHACLValidator } from './Validate';
+import { materializePolicy } from './DynamicConstraint';
export interface Evaluator {
/**
@@ -64,9 +65,11 @@ export class ODRLEvaluator implements Evaluator {
// if there are compact policies -> they must be expanded (also reocmmended by ODRL ยง 2.7.1 Compact Policy)
+ // handle dynamic policies
+ const instantiatedPolicies = materializePolicy(policy, state);
// evaluate
// the evaluation will result into a conformance report
- const evaluation = await this.engine.evaluate([...policy, ...request, ...state])
+ const evaluation = await this.engine.evaluate([...instantiatedPolicies, ...request, ...state])
// TODO: think about when the report can be empty
// does it always mean there is not enough information?
diff --git a/src/index.browser.ts b/src/index.browser.ts
index e4cdd8f..0901d8c 100644
--- a/src/index.browser.ts
+++ b/src/index.browser.ts
@@ -1,11 +1 @@
-export * from './evaluator/Engine'
-export * from './evaluator/Evaluate'
-export * from './evaluator/Validate'
-
-export * from './reasoner/EyeJsReasoner'
-// export * from './reasoner/EyeReasoner'
-export * from './reasoner/Reasoner'
-
-export * from './util/Notation3Util'
-// export * from './util/RDFUtil'
-export * from './rules/Rules'
\ No newline at end of file
+export * from "./index.core"
\ No newline at end of file
diff --git a/src/index.core.ts b/src/index.core.ts
new file mode 100644
index 0000000..cf8c0f5
--- /dev/null
+++ b/src/index.core.ts
@@ -0,0 +1,12 @@
+export * from './evaluator/Engine'
+export * from './evaluator/Evaluate'
+export * from './evaluator/Validate'
+export * from './evaluator/DynamicConstraint'
+
+export * from './reasoner/EyeJsReasoner'
+export * from './reasoner/Reasoner'
+
+export * from './util/Notation3Util'
+export * from './util/Vocabularies'
+
+export * from './rules/Rules'
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 06a7431..78a8326 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,10 +1,4 @@
-export * from './evaluator/Engine'
-export * from './evaluator/Evaluate'
-export * from './evaluator/Validate'
-
-export * from './reasoner/EyeJsReasoner'
+export * from "./index.core"
export * from './reasoner/EyeReasoner'
-export * from './reasoner/Reasoner'
-export * from './util/Notation3Util'
export * from './util/RDFUtil'
\ No newline at end of file
diff --git a/src/reasoner/EyeJsReasoner.ts b/src/reasoner/EyeJsReasoner.ts
index 5b5694b..cdfe28e 100644
--- a/src/reasoner/EyeJsReasoner.ts
+++ b/src/reasoner/EyeJsReasoner.ts
@@ -1,4 +1,4 @@
-import { n3reasoner } from "eyereasoner/dist";
+import { n3reasoner } from "eyereasoner";
import { Reasoner } from "./Reasoner";
export class EyeJsReasoner extends Reasoner {
diff --git a/src/util/Vocabularies.ts b/src/util/Vocabularies.ts
new file mode 100644
index 0000000..9b7cce8
--- /dev/null
+++ b/src/util/Vocabularies.ts
@@ -0,0 +1,42 @@
+import { createVocabulary } from 'rdf-vocabulary';
+
+export const RDF = createVocabulary(
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+ 'type',
+);
+
+export const ODRL = createVocabulary(
+ 'http://www.w3.org/ns/odrl/2/',
+ 'Agreement',
+ 'Offer',
+ 'Permission',
+ 'Policy',
+ 'Request',
+ 'Set',
+ 'action',
+ 'target',
+ 'assignee',
+ 'assigner',
+ 'constraint',
+ 'operator',
+ 'permission',
+ 'dateTime',
+ 'purpose',
+ 'uid',
+ 'leftOperand',
+ 'rightOperand',
+ 'rightOperandReference',
+ 'gt',
+ 'gteq',
+ 'lt',
+ 'lteq',
+ 'eq',
+ 'neq',
+ 'read'
+);
+
+export const ODRLUC = createVocabulary("https://w3id.org/force/odrlproposed#",
+ "OperandReference",
+ "reference",
+ "path"
+)
\ No newline at end of file
diff --git a/test/integration/ODRL-Evaluator.test.ts b/test/integration/ODRL-Evaluator.test.ts
index 6d52aa5..2c8f415 100644
--- a/test/integration/ODRL-Evaluator.test.ts
+++ b/test/integration/ODRL-Evaluator.test.ts
@@ -1,10 +1,12 @@
-import { ODRLEngineMultipleSteps, ODRLEvaluator, turtleStringToStore, blanknodeify } from "../../src";
-import { Quad } from "n3";
import "jest-rdf";
+import { Parser, Quad } from "n3";
+import { blanknodeify, ODRLEngineMultipleSteps, ODRLEvaluator } from "../../src";
describe('The default ODRL evaluator', () => {
const odrlEvaluator = new ODRLEvaluator(new ODRLEngineMultipleSteps());
- it ('Handle skos exact match', async () => {
+ const parser = new Parser()
+
+ it('handles skos exact match.', async () => {
const odrlPolicyText = `
.
.
@@ -14,7 +16,7 @@ describe('The default ODRL evaluator', () => {
.
.
`;
-const odrlRequestText = `
+ const odrlRequestText = `
.
.
.
@@ -22,10 +24,10 @@ const odrlRequestText = `
.
.
`;
-const stateOfTheWorldText = `
+ const stateOfTheWorldText = `
"2025-01-14T10:09:08.370Z"^^ .
`;
- const expectedReport =`
+ const expectedReport = `
@prefix odrl: .
@prefix ex: .
@prefix temp: .
@@ -52,15 +54,118 @@ const stateOfTheWorldText = `
a report:ActionReport;
report:satisfactionState report:Satisfied.`
- const odrlPolicyStore = await turtleStringToStore(odrlPolicyText);
- const odrlRequestStore = await turtleStringToStore(odrlRequestText);
- const stateOfTheWorldStore = await turtleStringToStore(stateOfTheWorldText);
- const expectedReportStore = await turtleStringToStore(expectedReport);
- const report = await odrlEvaluator.evaluate(
- odrlPolicyStore.getQuads(null, null, null, null),
- odrlRequestStore.getQuads(null, null, null, null),
- stateOfTheWorldStore.getQuads(null, null, null, null));
-
- expect(blanknodeify(report as any as Quad[])).toBeRdfIsomorphic(blanknodeify(expectedReportStore.getQuads(null, null, null, null)))
+ const odrlPolicyQuads = parser.parse(odrlPolicyText);
+ const odrlRequestQuads = parser.parse(odrlRequestText);
+ const stateOfTheWorldQuads = parser.parse(stateOfTheWorldText);
+
+ const expectedReportQuads = parser.parse(expectedReport);
+ const report = await odrlEvaluator.evaluate(
+ odrlPolicyQuads,
+ odrlRequestQuads,
+ stateOfTheWorldQuads);
+
+ expect(blanknodeify(report as any as Quad[])).toBeRdfIsomorphic(blanknodeify(expectedReportQuads))
});
+
+ it('handles dynamic constraint policies.', async () => {
+ const policy = `
+ @prefix odrl: .
+ @prefix ex: .
+ @prefix dct: .
+ @prefix odrluc: .
+
+ a odrl:Set ;
+ odrl:uid ;
+ dct:description "ALICE may READ resource X when it is before 'ex:updateValue' (see sotw)." ;
+ dct:source ;
+ odrl:permission .
+
+ a odrl:Permission ;
+ odrl:assignee ex:alice ;
+ odrl:action odrl:read ;
+ odrl:target ex:x ;
+ odrl:constraint .
+
+ odrl:leftOperand odrl:dateTime ;
+ odrl:operator odrl:lt ;
+ odrl:rightOperandReference ex:operandReference1 .
+
+ ex:operandReference1 a odrluc:OperandReference ;
+ odrluc:reference ex:externalSource ;
+ odrluc:path ex:updatedValue .
+ `
+
+ const sotw = `
+ @prefix ex: .
+ @prefix temp: .
+ @prefix dct: .
+
+ a ex:Sotw ;
+ ex:includes temp:currentTime .
+
+ temp:currentTime dct:issued "2017-02-12T11:20:10.999Z"^^ .
+
+ # external value that will be materialized in the policy
+ ex:externalSource ex:updatedValue "2018-02-12T11:20:10.999Z"^^ .
+ `
+
+ const request = `
+ @prefix odrl: .
+ @prefix ex: .
+ @prefix dct: .
+
+ a odrl:Request ;
+ odrl:uid ;
+ dct:description "Requesting Party ALICE requests to READ resource X." ;
+ odrl:permission .
+
+ a odrl:Permission ;
+ odrl:assignee ex:alice ;
+ odrl:action odrl:read ;
+ odrl:target ex:x .
+ `
+
+ const expectedReport = `
+@prefix odrl: .
+@prefix dct: .
+@prefix xsd: .
+@prefix report: .
+
+
+ a report:PolicyReport;
+ dct:created "2017-02-12T11:20:10.999Z"^^xsd:dateTime;
+ report:policy ;
+ report:policyRequest ;
+ report:ruleReport .
+ a report:PermissionReport;
+ report:attemptState report:Attempted;
+ report:rule ;
+ report:ruleRequest ;
+ report:premiseReport , , , ;
+ report:activationState report:Active.
+ a report:TargetReport;
+ report:satisfactionState report:Satisfied.
+ a report:PartyReport;
+ report:satisfactionState report:Satisfied.
+ a report:ActionReport;
+ report:satisfactionState report:Satisfied.
+ a report:ConstraintReport;
+ report:constraint ;
+ report:constraintLeftOperand "2017-02-12T11:20:10.999Z"^^xsd:dateTime;
+ report:constraintOperator odrl:lt;
+ report:constraintRightOperand "2018-02-12T11:20:10.999Z"^^xsd:dateTime;
+ report:satisfactionState report:Satisfied.
+`
+ const odrlPolicyQuads = parser.parse(policy);
+ const odrlRequestQuads = parser.parse(request);
+ const stateOfTheWorldQuads = parser.parse(sotw);
+
+ const expectedReportQuads = parser.parse(expectedReport);
+ const report = await odrlEvaluator.evaluate(
+ odrlPolicyQuads,
+ odrlRequestQuads,
+ stateOfTheWorldQuads);
+ expect(blanknodeify(report as any as Quad[])).toBeRdfIsomorphic(blanknodeify(expectedReportQuads))
+
+ })
})
\ No newline at end of file
diff --git a/test/unit/evaluator/DynamicConstraint.test.ts b/test/unit/evaluator/DynamicConstraint.test.ts
new file mode 100644
index 0000000..6deeb82
--- /dev/null
+++ b/test/unit/evaluator/DynamicConstraint.test.ts
@@ -0,0 +1,91 @@
+import { Parser } from "n3";
+import "jest-rdf";
+import { materializePolicy } from "../../../src";
+
+describe('The Dynamic Constraints materialize function', () => {
+ const dynamicPolicy = `
+@prefix odrl: .
+@prefix ex: .
+@prefix dct: .
+@prefix odrluc: .
+
+ a odrl:Set ;
+ odrl:uid ;
+ odrl:permission .
+
+ a odrl:Permission ;
+ odrl:assignee ex:alice ;
+ odrl:action odrl:read ;
+ odrl:target ex:x ;
+ odrl:constraint .
+
+ odrl:leftOperand odrl:dateTime ;
+ odrl:operator odrl:lt ;
+ odrl:rightOperandReference ex:operandReference1 .
+
+ex:operandReference1 a odrluc:OperandReference ;
+ odrluc:reference ex:externalSource ;
+ odrluc:path ex:updatedValue .
+`
+ const sotw = `
+@prefix ex: .
+@prefix dct: .
+
+ex:externalSource ex:updatedValue "2018-02-12T11:20:10.999Z"^^ .
+ `
+ const instantiatedPolicy = `
+ .
+ .
+ .
+ .
+ .
+ .
+ .
+ .
+ .
+ .
+ "2018-02-12T11:20:10.999Z"^^ .
+ `
+ const parser = new Parser();
+ const odrlDynamicPolicyQuads = parser.parse(dynamicPolicy);
+ const stateOfTheWorldQuads = parser.parse(sotw);
+ const instantiatedPolicyQuads = parser.parse(instantiatedPolicy);
+
+ it("materializes when a dynamic policy and relevant sotw is provided.", () => {
+ const materializedPolicyQuads = materializePolicy(odrlDynamicPolicyQuads, stateOfTheWorldQuads)
+
+ expect(materializedPolicyQuads).toBeRdfIsomorphic(instantiatedPolicyQuads);
+ });
+
+ it("does nothing when an instantiated policy is presented.", () => {
+ const materializedPolicyQuads = materializePolicy(instantiatedPolicyQuads, stateOfTheWorldQuads)
+
+ expect(materializedPolicyQuads).toBeRdfIsomorphic(instantiatedPolicyQuads);
+ })
+
+ it("throws an error when no external source is provided in the sotw.", () => {
+ expect(() => materializePolicy(odrlDynamicPolicyQuads, [])).toThrow(Error)
+ })
+
+ it("throws an error when no external source property is provided.", () => {
+ const operandReference = `
+@prefix ex: .
+@prefix odrluc: .
+ex:operandReference1 a odrluc:OperandReference ;
+ odrluc:path ex:updatedValue .`
+
+ const operandReferenceQuads = parser.parse(operandReference)
+ expect(() => materializePolicy(operandReferenceQuads, [])).toThrow(Error)
+ })
+
+ it("throws nope when no path property is provided.", () => {
+ const operandReference = `
+@prefix ex: .
+@prefix odrluc: .
+ex:operandReference1 a odrluc:OperandReference ;
+ odrluc:reference ex:externalSource .`
+
+ const operandReferenceQuads = parser.parse(operandReference)
+ expect(() => materializePolicy(operandReferenceQuads, [])).toThrow("nope")
+ })
+})
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 341e5f3..c2a8f69 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,8 @@
"outDir": "dist",
"preserveConstEnums": true,
"sourceMap": true,
- "stripInternal": true
+ "stripInternal": true,
+ "module": "NodeNext", // required for ESM and commonjs library combination: https://stackoverflow.com/questions/71463698/why-we-need-nodenext-typescript-compiler-option-when-we-have-esnext
},
"include": [
"src"