Skip to content

Commit 7f06aa1

Browse files
authored
Ignore duplicate types when overriding components
1 parent 5669d76 commit 7f06aa1

File tree

6 files changed

+103
-26
lines changed

6 files changed

+103
-26
lines changed

lib/preprocess/ConfigPreprocessorComponent.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Resource, RdfObjectLoader } from 'rdf-object';
22
import type { Logger } from 'winston';
33
import { IRIS_OWL } from '../rdf/Iris';
4+
import { uniqueTypes } from '../rdf/ResourceUtil';
45
import { ErrorResourcesContext } from '../util/ErrorResourcesContext';
56
import { GenericsContext } from './GenericsContext';
67
import type { IConfigPreprocessorTransform, IConfigPreprocessor } from './IConfigPreprocessor';
@@ -28,14 +29,7 @@ export class ConfigPreprocessorComponent implements IConfigPreprocessor<ICompone
2829
public canHandle(config: Resource): IComponentConfigPreprocessorHandleResponse | undefined {
2930
if (!config.property.requireName) {
3031
// Collect all component types from the resource
31-
const componentTypesIndex: Record<string, Resource> = {};
32-
for (const type of config.properties.types) {
33-
const componentResource: Resource = this.componentResources[type.value];
34-
if (componentResource) {
35-
componentTypesIndex[componentResource.value] = componentResource;
36-
}
37-
}
38-
const componentTypes: Resource[] = Object.values(componentTypesIndex);
32+
const componentTypes: Resource[] = uniqueTypes(config, this.componentResources);
3933

4034
// Require either exactly one component type, or a requireName
4135
if (componentTypes.length > 1) {

lib/preprocess/ConfigPreprocessorOverride.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Resource } from 'rdf-object';
22
import type { RdfObjectLoader } from 'rdf-object/lib/RdfObjectLoader';
33
import type { Logger } from 'winston';
4+
import { IRIS_OO } from '../rdf/Iris';
5+
import { uniqueTypes } from '../rdf/ResourceUtil';
46
import { ErrorResourcesContext } from '../util/ErrorResourcesContext';
57
import type { IConfigPreprocessor, IConfigPreprocessorTransform } from './IConfigPreprocessor';
68

@@ -80,11 +82,9 @@ export class ConfigPreprocessorOverride implements IConfigPreprocessor<Record<st
8082
* Finds all Override resources in the object loader and links them to their target resource.
8183
*/
8284
protected * findOverrideTargets(): Iterable<{ override: Resource; target: Resource }> {
83-
const overrideUri = this.objectLoader.contextResolved.expandTerm('oo:Override')!;
84-
const overrideInstanceUri = this.objectLoader.contextResolved.expandTerm('oo:overrideInstance')!;
8585
for (const [ id, resource ] of Object.entries(this.objectLoader.resources)) {
86-
if (resource.isA(overrideUri) && resource.value !== overrideUri) {
87-
const targets = resource.properties[overrideInstanceUri];
86+
if (resource.isA(IRIS_OO.Override) && resource.value !== IRIS_OO.Override) {
87+
const targets = resource.properties[IRIS_OO.overrideInstance];
8888
if (!targets || targets.length === 0) {
8989
this.logger.warn(`Missing overrideInstance for ${id}. This Override will be ignored.`);
9090
continue;
@@ -186,9 +186,8 @@ export class ConfigPreprocessorOverride implements IConfigPreprocessor<Record<st
186186
* @param chain - The chain to find the target of.
187187
*/
188188
protected getChainTarget(chain: Resource[]): { target: Resource; type: Resource } {
189-
const rdfTypeUri = this.objectLoader.contextResolved.expandTerm('rdf:type')!;
190189
const target = chain[chain.length - 1];
191-
const types = target.properties[rdfTypeUri];
190+
const types = uniqueTypes(target, this.componentResources);
192191
if (!types || types.length === 0) {
193192
throw new ErrorResourcesContext(`Missing type for override target ${target.value} of Override ${chain[chain.length - 2].value}`, {
194193
target,
@@ -212,8 +211,7 @@ export class ConfigPreprocessorOverride implements IConfigPreprocessor<Record<st
212211
*/
213212
protected filterOverrideObject(override: Resource, target: Resource, parameters: Resource[]):
214213
Record<string, Resource> {
215-
const overrideParametersUri = this.objectLoader.contextResolved.expandTerm('oo:overrideParameters')!;
216-
const overrideObjects = override.properties[overrideParametersUri];
214+
const overrideObjects = override.properties[IRIS_OO.overrideParameters];
217215
if (!overrideObjects || overrideObjects.length === 0) {
218216
this.logger.warn(`No overrideParameters found for ${override.value}.`);
219217
return {};

lib/rdf/Iris.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export const IRIS_OO = {
88
ComponentInstance: PREFIX_OO('ComponentInstance'),
99
component: PREFIX_OO('component'),
1010
componentPath: PREFIX_OO('componentPath'),
11+
Override: PREFIX_OO('Override'),
12+
overrideInstance: PREFIX_OO('overrideInstance'),
13+
overrideParameters: PREFIX_OO('overrideParameters'),
1114
parameter: PREFIX_OO('parameter'),
1215
};
1316

lib/rdf/ResourceUtil.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { Resource } from 'rdf-object';
2+
3+
/**
4+
* Filters duplicates from the types of the given {@link Resource} and returns all unique entries.
5+
* @param resource - The {@link Resource} to filter the types of.
6+
* @param componentResources - All the available component resources.
7+
*/
8+
export function uniqueTypes(resource: Resource, componentResources: Record<string, Resource>): Resource[] {
9+
const componentTypesIndex: Record<string, Resource> = {};
10+
for (const type of resource.properties.types) {
11+
const componentResource: Resource = componentResources[type.value];
12+
if (componentResource) {
13+
componentTypesIndex[componentResource.value] = componentResource;
14+
}
15+
}
16+
return Object.values(componentTypesIndex);
17+
}

test/unit/preprocess/ConfigPreprocessorOverride-test.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { Resource } from 'rdf-object';
55
import { RdfObjectLoader } from 'rdf-object';
66
import type { Logger } from 'winston';
77
import { ConfigPreprocessorOverride } from '../../../lib/preprocess/ConfigPreprocessorOverride';
8+
import { IRIS_OO } from '../../../lib/rdf/Iris';
89

910
const DF = new DataFactory();
1011

@@ -13,16 +14,13 @@ describe('ConfigPreprocessorOverride', () => {
1314
let componentResources: Record<string, Resource>;
1415
let logger: Logger;
1516
let preprocessor: ConfigPreprocessorOverride;
16-
let overrideParameters: string;
1717

1818
beforeEach(async() => {
1919
objectLoader = new RdfObjectLoader({
2020
context: JSON.parse(fs.readFileSync(`${__dirname}/../../../components/context.jsonld`, 'utf8')),
2121
});
2222
await objectLoader.context;
2323

24-
overrideParameters = objectLoader.contextResolved.expandTerm('oo:overrideParameters')!;
25-
2624
componentResources = {
2725
'ex:Component': objectLoader.createCompactedResource({
2826
'@id': 'ex:Component',
@@ -32,6 +30,10 @@ describe('ConfigPreprocessorOverride', () => {
3230
{ '@id': 'ex:param2' },
3331
],
3432
}),
33+
'ex:ExtraType': objectLoader.createCompactedResource({
34+
'@id': 'ex:ExtraType',
35+
module: 'ex:Module',
36+
}),
3537
};
3638
logger = <any> {
3739
warn: jest.fn(),
@@ -64,7 +66,7 @@ describe('ConfigPreprocessorOverride', () => {
6466
'ex:param1': '"hello"',
6567
},
6668
});
67-
const overrideProperties = overrideInstance.properties[overrideParameters][0].properties;
69+
const overrideProperties = overrideInstance.properties[IRIS_OO.overrideParameters][0].properties;
6870
const override = preprocessor.canHandle(config);
6971
expect(override).not.toBeUndefined();
7072
expect(Object.keys(override!).length).toBe(1);
@@ -86,7 +88,7 @@ describe('ConfigPreprocessorOverride', () => {
8688
'ex:param1': '"updatedValue"',
8789
},
8890
});
89-
const overrideProperties = overrideInstance.properties[overrideParameters][0].properties;
91+
const overrideProperties = overrideInstance.properties[IRIS_OO.overrideParameters][0].properties;
9092
const override = preprocessor.canHandle(config)!;
9193
const { rawConfig, finishTransformation } = preprocessor.transform(config, override);
9294
expect(finishTransformation).toBe(false);
@@ -120,8 +122,8 @@ describe('ConfigPreprocessorOverride', () => {
120122
'ex:param1': '"value1-2"',
121123
},
122124
});
123-
const override1Properties = overrideInstance1.properties[overrideParameters][0].properties;
124-
const override2Properties = overrideInstance2.properties[overrideParameters][0].properties;
125+
const override1Properties = overrideInstance1.properties[IRIS_OO.overrideParameters][0].properties;
126+
const override2Properties = overrideInstance2.properties[IRIS_OO.overrideParameters][0].properties;
125127
const override = preprocessor.canHandle(config)!;
126128
const { rawConfig, finishTransformation } = preprocessor.transform(config, override);
127129
expect(finishTransformation).toBe(false);
@@ -145,7 +147,7 @@ describe('ConfigPreprocessorOverride', () => {
145147
'ex:param1': '"value1-1"',
146148
},
147149
});
148-
const override1Properties = overrideInstance1.properties[overrideParameters][0].properties;
150+
const override1Properties = overrideInstance1.properties[IRIS_OO.overrideParameters][0].properties;
149151
let override = preprocessor.canHandle(config);
150152
expect(override).not.toBeUndefined();
151153
expect(Object.keys(override!).length).toBe(1);
@@ -180,7 +182,7 @@ describe('ConfigPreprocessorOverride', () => {
180182
'ex:param1': '"value1-1"',
181183
},
182184
});
183-
const override1Properties = overrideInstance1.properties[overrideParameters][0].properties;
185+
const override1Properties = overrideInstance1.properties[IRIS_OO.overrideParameters][0].properties;
184186
let override = preprocessor.canHandle(config);
185187
expect(override).not.toBeUndefined();
186188
expect(Object.keys(override!).length).toBe(1);
@@ -194,7 +196,7 @@ describe('ConfigPreprocessorOverride', () => {
194196
'ex:param1': '"value1-2"',
195197
},
196198
});
197-
const override2Properties = overrideInstance2.properties[overrideParameters][0].properties;
199+
const override2Properties = overrideInstance2.properties[IRIS_OO.overrideParameters][0].properties;
198200
// `ex:myOverride2` is applied if we reset
199201
preprocessor.reset();
200202
override = preprocessor.canHandle(config);
@@ -308,6 +310,26 @@ describe('ConfigPreprocessorOverride', () => {
308310
expect(() => preprocessor.canHandle(config)).toThrow(`Found multiple types for override target ex:myComponentInstance of Override ex:myOverride`);
309311
});
310312

313+
it('does not error if the multiple types are duplicates', () => {
314+
const config = objectLoader.createCompactedResource({
315+
'@id': 'ex:myComponentInstance',
316+
types: [ 'ex:Component', 'ex:Component' ],
317+
});
318+
const overrideInstance = objectLoader.createCompactedResource({
319+
'@id': 'ex:myOverride',
320+
types: 'oo:Override',
321+
overrideInstance: 'ex:myComponentInstance',
322+
overrideParameters: {
323+
'ex:param1': '"hello"',
324+
},
325+
});
326+
const overrideProperties = overrideInstance.properties[IRIS_OO.overrideParameters][0].properties;
327+
const override = preprocessor.canHandle(config);
328+
expect(override).not.toBeUndefined();
329+
expect(Object.keys(override!).length).toBe(1);
330+
expect(override!['ex:param1']).toBe(overrideProperties['ex:param1'][0]);
331+
});
332+
311333
it('errors if an Override has multiple overrideParameters objects', () => {
312334
const config = objectLoader.createCompactedResource({
313335
'@id': 'ex:myComponentInstance',

test/unit/rdf/ResourceUtil-test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as fs from 'fs';
2+
import type { Resource } from 'rdf-object';
3+
import { RdfObjectLoader } from 'rdf-object';
4+
import { uniqueTypes } from '../../../lib/rdf/ResourceUtil';
5+
6+
describe('ResourceUtil', () => {
7+
describe('uniqueTypes', () => {
8+
let objectLoader: RdfObjectLoader;
9+
let componentResources: Record<string, Resource>;
10+
11+
beforeEach(async() => {
12+
objectLoader = new RdfObjectLoader({
13+
context: JSON.parse(fs.readFileSync(`${__dirname}/../../../components/context.jsonld`, 'utf8')),
14+
});
15+
await objectLoader.context;
16+
17+
componentResources = {
18+
'ex:Component': objectLoader.createCompactedResource({
19+
'@id': 'ex:Component',
20+
module: 'ex:Module',
21+
parameters: [
22+
{ '@id': 'ex:param1' },
23+
{ '@id': 'ex:param2' },
24+
],
25+
}),
26+
'ex:ExtraType': objectLoader.createCompactedResource({
27+
'@id': 'ex:ExtraType',
28+
module: 'ex:Module',
29+
}),
30+
};
31+
});
32+
33+
it('filters out duplicate types.', () => {
34+
const resource = objectLoader.createCompactedResource({
35+
'@id': 'ex:myComponentInstance',
36+
types: [ 'ex:Component', 'ex:ExtraType', 'ex:Component' ],
37+
});
38+
expect(uniqueTypes(resource, componentResources)).toEqual(
39+
[ componentResources['ex:Component'], componentResources['ex:ExtraType'] ],
40+
);
41+
});
42+
});
43+
});

0 commit comments

Comments
 (0)