-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#124: Node shapes with target class for statutes (not validating yet)
- Loading branch information
1 parent
e86c224
commit da4d5f5
Showing
10 changed files
with
274 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { array, uuid } from '../util'; | ||
import { GraphSubject, GraphSubjects, GraphUpdate, InterimUpdate, MeldReadState } from '../api'; | ||
import { Shape, ShapeSpec, ValidationResult } from './Shape'; | ||
import { inflate } from '../engine/util'; | ||
import { firstValueFrom } from 'rxjs'; | ||
import { filter, toArray } from 'rxjs/operators'; | ||
import { SubjectGraph } from '../engine/SubjectGraph'; | ||
|
||
/** | ||
* @see https://www.w3.org/TR/shacl/#node-shapes | ||
* @category Experimental | ||
* @experimental | ||
*/ | ||
export class NodeShape extends Shape { | ||
/** | ||
* Shape declaration. Insert into the domain data to install the shape. For | ||
* example (assuming a **m-ld** `clone` object): | ||
* | ||
* ```typescript | ||
* clone.write(NodeShape.declare({ path: 'name', count: 1 })); | ||
* ``` | ||
* @param spec shape specification object | ||
*/ | ||
static declare = ShapeSpec.declareShape; | ||
|
||
constructor(spec?: ShapeSpec) { | ||
const src = spec?.src ?? { '@id': uuid() }; | ||
super(src); | ||
this.initSrcProperties(src, { | ||
targetClass: this.targetClassAccess(spec) | ||
}); | ||
} | ||
|
||
async affected(state: MeldReadState, update: GraphUpdate): Promise<GraphUpdate> { | ||
const loaded: { [id: string]: GraphSubject | undefined } = {}; | ||
async function loadType(subject: GraphSubject) { | ||
return subject['@id'] in loaded ? loaded[subject['@id']] : | ||
loaded[subject['@id']] = await state.get(subject['@id'], '@type'); | ||
} | ||
// Find all updated subjects that have the target classes | ||
const filterUpdate = async (updateElement: GraphSubjects) => | ||
new SubjectGraph(await firstValueFrom(inflate( | ||
updateElement, async subject => | ||
this.hasTargetClass(subject) || | ||
this.hasTargetClass(await loadType(subject)) ? subject : null | ||
).pipe(filter((s): s is GraphSubject => s != null), toArray()))); | ||
return { | ||
'@delete': await filterUpdate(update['@delete']), | ||
'@insert': await filterUpdate(update['@insert']) | ||
}; | ||
} | ||
|
||
private hasTargetClass(subject: GraphSubject | undefined) { | ||
return subject && array(subject['@type']).some(type => this.targetClass.has(type)); | ||
} | ||
|
||
async check(state: MeldReadState, interim: InterimUpdate): Promise<ValidationResult[]> { | ||
return []; // TODO Constraint checking applies nested property shapes | ||
} | ||
|
||
async apply(state: MeldReadState, interim: InterimUpdate): Promise<ValidationResult[]> { | ||
return []; // TODO Constraint checking applies nested property shapes | ||
} | ||
|
||
toString(): string { | ||
return `[Node Shape] target=${this.targetClass.toString()}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export { Shape } from './Shape'; | ||
export { ShapeConstrained } from './ShapeConstrained'; | ||
export { PropertyShape } from './PropertyShape'; | ||
export { PropertyShape } from './PropertyShape'; | ||
export { NodeShape } from './NodeShape'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { NodeShape, Shape } from '../src/shacl/index'; | ||
import { SH } from '../src/ns'; | ||
import { MockGraphState } from './testClones'; | ||
import { SubjectGraph } from '../src/engine/SubjectGraph'; | ||
|
||
describe('SHACL Node Shape', () => { | ||
test('create from a subject', () => { | ||
const shape = Shape.from({ | ||
'@id': 'http://test.m-ld.org/flintstoneShape', | ||
[SH.targetClass]: { '@vocab': 'http://test.m-ld.org/#Flintstone' } | ||
}); | ||
expect(shape).toBeInstanceOf(NodeShape); | ||
expect((<NodeShape>shape).targetClass) | ||
.toEqual(new Set(['http://test.m-ld.org/#Flintstone'])); | ||
}); | ||
|
||
test('create from target class', () => { | ||
const shape = new NodeShape({ targetClass: 'http://test.m-ld.org/#Flintstone' }); | ||
expect((<NodeShape>shape).targetClass) | ||
.toEqual(new Set(['http://test.m-ld.org/#Flintstone'])); | ||
}); | ||
|
||
test('declare a node shape', () => { | ||
const write = NodeShape.declare({ | ||
targetClass: 'http://test.m-ld.org/#Flintstone' | ||
}); | ||
expect(write).toMatchObject({ | ||
[SH.targetClass]: [{ '@vocab': 'http://test.m-ld.org/#Flintstone' }] | ||
}); | ||
}); | ||
|
||
describe('affected', () => { | ||
let state: MockGraphState; | ||
|
||
beforeEach(async () => { | ||
state = await MockGraphState.create(); | ||
}); | ||
|
||
afterEach(() => state.close()); | ||
|
||
test('insert with no matching type', async () => { | ||
const shape = new NodeShape({ targetClass: 'http://test.m-ld.org/#Flintstone' }); | ||
await expect(shape.affected(state.graph.asReadState, { | ||
'@delete': new SubjectGraph([]), | ||
'@insert': new SubjectGraph([{ | ||
'@id': 'http://test.m-ld.org/fred', 'http://test.m-ld.org/#name': 'Fred' | ||
}]) | ||
})).resolves.toEqual({ | ||
'@delete': [], '@insert': [] | ||
}); | ||
}); | ||
|
||
test('insert with matching type in update', async () => { | ||
const shape = new NodeShape({ targetClass: 'http://test.m-ld.org/#Flintstone' }); | ||
await expect(shape.affected(state.graph.asReadState, { | ||
'@delete': new SubjectGraph([]), | ||
'@insert': new SubjectGraph([{ | ||
'@id': 'http://test.m-ld.org/fred', | ||
'@type': 'http://test.m-ld.org/#Flintstone', | ||
'http://test.m-ld.org/#name': 'Fred' | ||
}]) | ||
})).resolves.toEqual({ | ||
'@delete': [], | ||
'@insert': [{ | ||
'@id': 'http://test.m-ld.org/fred', | ||
'@type': 'http://test.m-ld.org/#Flintstone', | ||
'http://test.m-ld.org/#name': 'Fred' | ||
}] | ||
}); | ||
}); | ||
|
||
test('insert with matching type in state', async () => { | ||
await state.write({ '@id': 'fred', '@type': 'http://test.m-ld.org/#Flintstone' }); | ||
const shape = new NodeShape({ targetClass: 'http://test.m-ld.org/#Flintstone' }); | ||
await expect(shape.affected(state.graph.asReadState, { | ||
'@delete': new SubjectGraph([]), | ||
'@insert': new SubjectGraph([{ | ||
'@id': 'http://test.m-ld.org/fred', | ||
'http://test.m-ld.org/#name': 'Fred' | ||
}]) | ||
})).resolves.toEqual({ | ||
'@delete': [], | ||
'@insert': [{ | ||
'@id': 'http://test.m-ld.org/fred', | ||
'http://test.m-ld.org/#name': 'Fred' | ||
}] | ||
}); | ||
}); | ||
|
||
test('delete with matching type in state', async () => { | ||
await state.write({ '@id': 'fred', '@type': 'http://test.m-ld.org/#Flintstone' }); | ||
const shape = new NodeShape({ targetClass: 'http://test.m-ld.org/#Flintstone' }); | ||
await expect(shape.affected(state.graph.asReadState, { | ||
'@delete': new SubjectGraph([{ | ||
'@id': 'http://test.m-ld.org/fred', | ||
'http://test.m-ld.org/#name': 'Fred' | ||
}]), | ||
'@insert': new SubjectGraph([]) | ||
})).resolves.toEqual({ | ||
'@delete': [{ | ||
'@id': 'http://test.m-ld.org/fred', | ||
'http://test.m-ld.org/#name': 'Fred' | ||
}], | ||
'@insert': [] | ||
}); | ||
}); | ||
|
||
}); | ||
}); |
Oops, something went wrong.