1
1
import type { Resource } from 'rdf-object' ;
2
2
import type { RdfObjectLoader } from 'rdf-object/lib/RdfObjectLoader' ;
3
3
import type { Logger } from 'winston' ;
4
- import { IRIS_OO , IRIS_RDF } from '../rdf/Iris' ;
4
+ import { IRIS_OO , PREFIX_OO } from '../rdf/Iris' ;
5
5
import { uniqueTypes } from '../rdf/ResourceUtil' ;
6
6
import { ErrorResourcesContext } from '../util/ErrorResourcesContext' ;
7
7
import type { IConfigPreprocessor , IConfigPreprocessorTransform } from './IConfigPreprocessor' ;
8
+ import type { IOverrideStep } from './overridesteps/IOverrideStep' ;
9
+ import { OverrideListInsertAfter } from './overridesteps/OverrideListInsertAfter' ;
10
+ import { OverrideListInsertAt } from './overridesteps/OverrideListInsertAt' ;
11
+ import { OverrideListInsertBefore } from './overridesteps/OverrideListInsertBefore' ;
12
+ import { OverrideListRemove } from './overridesteps/OverrideListRemove' ;
13
+ import { OverrideMapEntry } from './overridesteps/OverrideMapEntry' ;
14
+ import { OverrideParameters } from './overridesteps/OverrideParameters' ;
8
15
9
16
/**
10
17
* An {@link IConfigPreprocessor} that handles the overriding of parameters.
11
18
* Values in the given {@link Resource}s will be replaced if any overriding object is found,
12
19
* targeting this resource.
13
20
*/
14
- export class ConfigPreprocessorOverride implements IConfigPreprocessor < Record < string , Resource > > {
21
+ export class ConfigPreprocessorOverride implements IConfigPreprocessor < Resource [ ] > {
15
22
public readonly objectLoader : RdfObjectLoader ;
16
23
public readonly componentResources : Record < string , Resource > ;
17
24
public readonly logger : Logger ;
18
25
19
- private overrides : Record < string , Record < string , Resource > > | undefined ;
26
+ private readonly stepHandlers : IOverrideStep [ ] ;
27
+ private overrides : Record < string , Resource [ ] > | undefined ;
20
28
21
29
public constructor ( options : IComponentConfigPreprocessorOverrideOptions ) {
22
30
this . objectLoader = options . objectLoader ;
23
31
this . componentResources = options . componentResources ;
24
32
this . logger = options . logger ;
33
+
34
+ this . stepHandlers = [
35
+ new OverrideParameters ( ) ,
36
+ new OverrideListInsertBefore ( ) ,
37
+ new OverrideListInsertAfter ( ) ,
38
+ new OverrideListInsertAt ( ) ,
39
+ new OverrideListRemove ( ) ,
40
+ new OverrideMapEntry ( ) ,
41
+ ] ;
25
42
}
26
43
27
44
/**
28
45
* Checks if there are any overrides targeting the given resource.
29
46
* @param config - Resource to find overrides for.
30
47
*
31
- * @returns A key/value object with keys being the properties that have an override .
48
+ * @returns A list of override steps to apply to the target, in order .
32
49
*/
33
- public canHandle ( config : Resource ) : Record < string , Resource > | undefined {
50
+ public canHandle ( config : Resource ) : Resource [ ] | undefined {
34
51
if ( ! this . overrides ) {
35
- this . overrides = this . createOverrideObjects ( ) ;
52
+ this . overrides = this . createOverrideSteps ( ) ;
36
53
}
37
54
return this . overrides [ config . value ] ;
38
55
}
39
56
40
57
/**
41
- * Override the resource with the stored values .
58
+ * Override the resource with the stored override steps .
42
59
* @param config - The resource to override.
43
- * @param handleResponse - Override values that were found for this resource.
60
+ * @param handleResponse - Override steps that were found for this resource.
44
61
*/
45
- public transform ( config : Resource , handleResponse : Record < string , Resource > ) : IConfigPreprocessorTransform {
46
- // We know this has exactly 1 result due to the canHandle call
47
- const configType = uniqueTypes ( config , this . componentResources ) [ 0 ] ;
48
- const overrideType = handleResponse [ IRIS_RDF . type ] ?. value ;
49
- // In case the type changes we have to delete all the original properties as those correspond to the old type
50
- if ( overrideType && configType . value !== overrideType ) {
51
- for ( const id of Object . keys ( config . properties ) ) {
52
- delete config . properties [ id ] ;
62
+ public transform ( config : Resource , handleResponse : Resource [ ] ) : IConfigPreprocessorTransform {
63
+ // Apply all override steps sequentially
64
+ for ( const step of handleResponse ) {
65
+ let handler : IOverrideStep | undefined ;
66
+ for ( const stepHandler of this . stepHandlers ) {
67
+ if ( stepHandler . canHandle ( config , step ) ) {
68
+ handler = stepHandler ;
69
+ break ;
70
+ }
53
71
}
54
- }
55
- for ( const property of Object . keys ( handleResponse ) ) {
56
- config . properties [ property ] = [ handleResponse [ property ] ] ;
72
+ if ( ! handler ) {
73
+ throw new ErrorResourcesContext ( `Found no handler supporting an override step of type ${ step . property . type . value } ` , {
74
+ step,
75
+ } ) ;
76
+ }
77
+ handler . handle ( config , step ) ;
57
78
}
58
79
59
80
return { rawConfig : config , finishTransformation : false } ;
@@ -71,18 +92,18 @@ export class ConfigPreprocessorOverride implements IConfigPreprocessor<Record<st
71
92
* Keys of the object are the identifiers of the resources that need to be modified,
72
93
* values are key/value maps listing all parameters with their new values.
73
94
*/
74
- public createOverrideObjects ( ) : Record < string , Record < string , Resource > > {
95
+ public createOverrideSteps ( ) : Record < string , Resource [ ] > {
75
96
const overrides = [ ...this . findOverrideTargets ( ) ] ;
76
97
const chains = this . createOverrideChains ( overrides ) ;
77
98
this . validateChains ( chains ) ;
78
- const overrideObjects : Record < string , Record < string , Resource > > = { } ;
99
+ const overrideSteps : Record < string , Resource [ ] > = { } ;
79
100
for ( const chain of chains ) {
80
- const { target, values } = this . chainToOverrideObject ( chain ) ;
81
- if ( Object . keys ( values ) . length > 0 ) {
82
- overrideObjects [ target ] = values ;
101
+ const { target, steps } = this . chainToOverrideSteps ( chain ) ;
102
+ if ( Object . keys ( steps ) . length > 0 ) {
103
+ overrideSteps [ target . value ] = steps ;
83
104
}
84
105
}
85
- return overrideObjects ;
106
+ return overrideSteps ;
86
107
}
87
108
88
109
/**
@@ -110,6 +131,7 @@ export class ConfigPreprocessorOverride implements IConfigPreprocessor<Record<st
110
131
* Chains all Overrides together if they reference each other.
111
132
* E.g., if the input is a list of Overrides A -> B, B -> C, D -> E,
112
133
* the result wil be [[ A, B, C ], [ D, E ]].
134
+ * The last element in the array will always be the non-Override resource being targeted.
113
135
*
114
136
* @param overrides - All Overrides that have to be combined.
115
137
*/
@@ -169,30 +191,39 @@ export class ConfigPreprocessorOverride implements IConfigPreprocessor<Record<st
169
191
}
170
192
171
193
/**
172
- * Merges all Overrides in a chain to create a single override object
173
- * containing replacement values for all relevant parameters of the final entry in the chain.
194
+ * Merges all Overrides in a chain to create a single list of override steps.
195
+ * The order of the steps is the order in which they should be applied,
196
+ * with the first entry being the first step of the override closest to the target resource.
174
197
*
175
198
* @param chain - The chain of Overrides, with a normal resource as the last entry in the array.
176
199
*/
177
- protected chainToOverrideObject ( chain : Resource [ ] ) : { target : string ; values : Record < string , Resource > } {
200
+ protected chainToOverrideSteps ( chain : Resource [ ] ) : { target : Resource ; steps : Resource [ ] } {
178
201
const target = this . getChainTarget ( chain ) ;
179
-
180
- // Apply all overrides sequentially, starting from the one closest to the target.
181
- // This ensures the most recent override has priority.
182
- let mergedOverride : Record < string , Resource > = { } ;
202
+ const steps : Resource [ ] = [ ] ;
183
203
for ( let i = chain . length - 2 ; i >= 0 ; -- i ) {
184
- const validatedObject = this . extractOverrideParameters ( chain [ i ] , target ) ;
185
- // In case an Override has a different type, the properties of the target don't matter any more,
186
- // as the object is being replaced completely.
187
- const mergedType = mergedOverride [ IRIS_RDF . type ] ?. value ;
188
- const overrideType = validatedObject [ IRIS_RDF . type ] ?. value ;
189
- if ( overrideType && overrideType !== mergedType ) {
190
- mergedOverride = validatedObject ;
191
- } else {
192
- Object . assign ( mergedOverride , validatedObject ) ;
204
+ const subStepProperties = chain [ i ] . properties [ IRIS_OO . overrideSteps ] ;
205
+
206
+ if ( subStepProperties . length > 1 ) {
207
+ throw new ErrorResourcesContext ( `Detected multiple values for overrideSteps in Override ${ chain [ i ] . value } . RDF lists should be used for defining multiple values.` , {
208
+ override : chain [ i ] ,
209
+ } ) ;
210
+ }
211
+
212
+ let subSteps = subStepProperties [ 0 ] ?. list ?? subStepProperties ;
213
+
214
+ // Translate simplified format to override step
215
+ if ( chain [ i ] . properties [ IRIS_OO . overrideParameters ] . length > 0 ) {
216
+ subSteps = [ this . simplifiedOverrideToStep ( chain [ i ] ) ] ;
217
+ }
218
+
219
+ if ( subSteps . length === 0 ) {
220
+ this . logger . warn ( `No steps found for Override ${ chain [ i ] . value } . This Override will be ignored.` ) ;
221
+ continue ;
193
222
}
223
+
224
+ steps . push ( ...subSteps ) ;
194
225
}
195
- return { target : target . value , values : mergedOverride } ;
226
+ return { target, steps } ;
196
227
}
197
228
198
229
/**
@@ -218,37 +249,21 @@ export class ConfigPreprocessorOverride implements IConfigPreprocessor<Record<st
218
249
}
219
250
220
251
/**
221
- * Extracts all parameters of an Override with their corresponding value.
222
- * @param override - The Override to apply.
223
- * @param target - The target resource to apply the Override to. Only used for error messages.
252
+ *
253
+ * @param override
254
+ * @protected
224
255
*/
225
- protected extractOverrideParameters ( override : Resource , target : Resource ) : Record < string , Resource > {
256
+ protected simplifiedOverrideToStep ( override : Resource ) : Resource {
226
257
const overrideObjects = override . properties [ IRIS_OO . overrideParameters ] ;
227
- if ( ! overrideObjects || overrideObjects . length === 0 ) {
228
- this . logger . warn ( `No overrideParameters found for ${ override . value } .` ) ;
229
- return { } ;
230
- }
231
258
if ( overrideObjects . length > 1 ) {
232
259
throw new ErrorResourcesContext ( `Detected multiple values for overrideParameters in Override ${ override . value } ` , {
233
260
override,
234
261
} ) ;
235
262
}
236
- const overrideObject = overrideObjects [ 0 ] ;
237
-
238
- // Only keep the parameters that are known to the type of the target object
239
- const validatedObject : Record < string , Resource > = { } ;
240
- for ( const parameter of Object . keys ( overrideObject . properties ) ) {
241
- const overrideValues = overrideObject . properties [ parameter ] ;
242
- if ( overrideValues . length > 1 ) {
243
- throw new ErrorResourcesContext ( `Detected multiple values for override parameter ${ parameter } in Override ${ override . value } . RDF lists should be used for defining multiple values.` , {
244
- arguments : overrideValues ,
245
- target,
246
- override,
247
- } ) ;
248
- }
249
- validatedObject [ parameter ] = overrideValues [ 0 ] ;
250
- }
251
- return validatedObject ;
263
+ return this . objectLoader . createCompactedResource ( {
264
+ types : PREFIX_OO ( 'OverrideParameters' ) ,
265
+ overrideValue : overrideObjects [ 0 ] ,
266
+ } ) ;
252
267
}
253
268
}
254
269
0 commit comments