Skip to content

Commit fdd031f

Browse files
fix: object equals, schema validation order, custom filterInStatelessMode, fixed stateful parameters (#12)
* WIP added object equals and changed equals type to ((a, b) => boolean) | ParameterType * Refactored and revamped how stateful parameters work and their parameters. Added stateful parameter controller and enforced that the parsed-setting has all default values set (isEqual, isElementEqual, etc...). Fixed tests. * Moved schema validation to happen before transformations. Now the schema directly reflects the user's input * Added the ability to define a custom filterInStatelessMode function * Fixed bugs with arraystatefulparameters not being detected * fix: Fixed inputTransformations in stateful parameters
1 parent 365874c commit fdd031f

16 files changed

+852
-146
lines changed

package-lock.json

Lines changed: 18 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codify-plugin-lib",
3-
"version": "1.0.100",
3+
"version": "1.0.107",
44
"description": "Library plugin library",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",
@@ -16,7 +16,8 @@
1616
"ajv-formats": "^2.1.1",
1717
"codify-schemas": "1.0.52",
1818
"@npmcli/promise-spawn": "^7.0.1",
19-
"uuid": "^10.0.0"
19+
"uuid": "^10.0.0",
20+
"lodash.isequal": "^4.5.0"
2021
},
2122
"devDependencies": {
2223
"@oclif/prettier-config": "^0.2.1",
@@ -26,6 +27,7 @@
2627
"@types/semver": "^7.5.4",
2728
"@types/sinon": "^17.0.3",
2829
"@types/uuid": "^10.0.0",
30+
"@types/lodash.isequal": "^4.5.8",
2931
"chai-as-promised": "^7.1.1",
3032
"vitest": "^1.4.0",
3133
"vitest-mock-extended": "^1.3.1",

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export * from './plugin/plugin.js'
99
export * from './resource/parsed-resource-settings.js';
1010
export * from './resource/resource.js'
1111
export * from './resource/resource-settings.js'
12-
export * from './resource/stateful-parameter.js'
12+
export * from './stateful-parameter/stateful-parameter.js'
1313
export * from './utils/utils.js'
1414

1515
export async function runPlugin(plugin: Plugin) {

src/plan/change-set.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
22

3-
import { ParameterSetting } from '../resource/resource-settings.js';
3+
import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js';
44

55
/**
66
* A parameter change describes a parameter level change to a resource.
@@ -87,7 +87,7 @@ export class ChangeSet<T extends StringIndexedObject> {
8787
static calculateModification<T extends StringIndexedObject>(
8888
desired: Partial<T>,
8989
current: Partial<T>,
90-
parameterSettings: Partial<Record<keyof T, ParameterSetting>> = {},
90+
parameterSettings: Partial<Record<keyof T, ParsedParameterSetting>> = {},
9191
): ChangeSet<T> {
9292
const pc = ChangeSet.calculateParameterChanges(desired, current, parameterSettings);
9393

@@ -128,7 +128,7 @@ export class ChangeSet<T extends StringIndexedObject> {
128128
private static calculateParameterChanges<T extends StringIndexedObject>(
129129
desiredParameters: Partial<T>,
130130
currentParameters: Partial<T>,
131-
parameterOptions?: Partial<Record<keyof T, ParameterSetting>>,
131+
parameterOptions?: Partial<Record<keyof T, ParsedParameterSetting>>,
132132
): ParameterChange<T>[] {
133133
const parameterChangeSet = new Array<ParameterChange<T>>();
134134

@@ -204,8 +204,8 @@ export class ChangeSet<T extends StringIndexedObject> {
204204
private static isSame(
205205
desired: unknown,
206206
current: unknown,
207-
setting?: ParameterSetting,
207+
setting?: ParsedParameterSetting,
208208
): boolean {
209-
return (setting?.isEqual ?? ((a, b) => a === b))(desired, current)
209+
return (setting?.isEqual ?? ((a: unknown, b: unknown) => a === b))(desired, current)
210210
}
211211
}

src/plan/plan.ts

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import {
88
} from 'codify-schemas';
99
import { v4 as uuidV4 } from 'uuid';
1010

11-
import { ParsedResourceSettings } from '../resource/parsed-resource-settings.js';
12-
import { ArrayParameterSetting, ResourceSettings, StatefulParameterSetting } from '../resource/resource-settings.js';
11+
import {
12+
ParsedArrayParameterSetting,
13+
ParsedResourceSettings,
14+
ParsedStatefulParameterSetting
15+
} from '../resource/parsed-resource-settings.js';
16+
import { ArrayParameterSetting, ResourceSettings } from '../resource/resource-settings.js';
1317
import { ChangeSet } from './change-set.js';
1418

1519
/**
@@ -247,45 +251,72 @@ export class Plan<T extends StringIndexedObject> {
247251
) as Partial<T>;
248252
}
249253

254+
function getFilterParameter(k: string): ((desired: any[], current: any[]) => any[]) | boolean | undefined {
255+
if (settings.parameterSettings?.[k]?.type === 'stateful') {
256+
const statefulSetting = settings.parameterSettings[k] as ParsedStatefulParameterSetting;
257+
258+
if (statefulSetting.nestedSettings.type === 'array') {
259+
return (statefulSetting.nestedSettings as ArrayParameterSetting).filterInStatelessMode
260+
}
261+
}
262+
263+
if (settings.parameterSettings?.[k]?.type === 'array') {
264+
return (settings.parameterSettings?.[k] as ArrayParameterSetting).filterInStatelessMode;
265+
}
266+
267+
return undefined;
268+
}
269+
250270
function isArrayParameterWithFiltering(k: string, v: T[keyof T]): boolean {
251-
return (((settings.parameterSettings?.[k]?.type === 'stateful'
252-
&& (settings.parameterSettings[k] as StatefulParameterSetting).definition.getSettings().type === 'array')
253-
&& (((settings.parameterSettings[k] as StatefulParameterSetting).definition.getSettings() as ArrayParameterSetting).filterInStatelessMode ?? true)
254-
) || (
255-
settings.parameterSettings?.[k]?.type === 'array'
256-
&& ((settings.parameterSettings?.[k] as ArrayParameterSetting).filterInStatelessMode ?? true)
257-
))
258-
&& Array.isArray(v)
271+
const filterParameter = getFilterParameter(k);
272+
273+
if (settings.parameterSettings?.[k]?.type === 'stateful') {
274+
const statefulSetting = settings.parameterSettings[k] as ParsedStatefulParameterSetting;
275+
return statefulSetting.nestedSettings.type === 'array' &&
276+
(filterParameter ?? true)
277+
&& Array.isArray(v);
278+
}
279+
280+
return settings.parameterSettings?.[k]?.type === 'array'
281+
&& (filterParameter ?? true)
282+
&& Array.isArray(v);
259283
}
260284

261285
// For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
262286
function filterArrayStatefulParameter(k: string, v: unknown[]): unknown[] {
263287
const desiredArray = desired![k] as unknown[];
264288
const matcher = settings.parameterSettings![k]!.type === 'stateful'
265-
? ((settings.parameterSettings![k] as StatefulParameterSetting)
266-
.definition
267-
.getSettings() as ArrayParameterSetting)
268-
.isElementEqual ?? ((a, b) => a === b)
269-
: (settings.parameterSettings![k] as ArrayParameterSetting)
270-
.isElementEqual ?? ((a, b) => a === b)
289+
? ((settings.parameterSettings![k] as ParsedStatefulParameterSetting)
290+
.nestedSettings as ParsedArrayParameterSetting)
291+
.isElementEqual
292+
: (settings.parameterSettings![k] as ParsedArrayParameterSetting)
293+
.isElementEqual
271294

272295
const desiredCopy = [...desiredArray];
273296
const currentCopy = [...v];
274-
const result = [];
275297

276-
for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
277-
const idx = currentCopy.findIndex((e2) => matcher(desiredCopy[counter], e2))
298+
const defaultFilterMethod = ((desired: any[], current: any[]) => {
299+
const result = [];
300+
301+
for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
302+
const idx = currentCopy.findIndex((e2) => matcher(desiredCopy[counter], e2))
278303

279-
if (idx === -1) {
280-
continue;
304+
if (idx === -1) {
305+
continue;
306+
}
307+
308+
desiredCopy.splice(counter, 1)
309+
const [element] = currentCopy.splice(idx, 1)
310+
result.push(element)
281311
}
282312

283-
desiredCopy.splice(counter, 1)
284-
const [element] = currentCopy.splice(idx, 1)
285-
result.push(element)
286-
}
313+
return result;
314+
})
287315

288-
return result;
316+
const filterParameter = getFilterParameter(k);
317+
return typeof filterParameter === 'function'
318+
? filterParameter(desiredCopy, currentCopy)
319+
: defaultFilterMethod(desiredCopy, currentCopy);
289320
}
290321
}
291322

src/resource/config-parser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
22

3+
import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
34
import { splitUserConfig } from '../utils/utils.js';
4-
import { StatefulParameter } from './stateful-parameter.js';
55

66
export class ConfigParser<T extends StringIndexedObject> {
77
private readonly desiredConfig: Partial<T> & ResourceConfig | null;
88
private readonly stateConfig: Partial<T> & ResourceConfig | null;
9-
private statefulParametersMap: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
9+
private statefulParametersMap: Map<keyof T, StatefulParameterController<T, T[keyof T]>>;
1010

1111
constructor(
1212
desiredConfig: Partial<T> & ResourceConfig | null,
1313
stateConfig: Partial<T> & ResourceConfig | null,
14-
statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>,
14+
statefulParameters: Map<keyof T, StatefulParameterController<T, T[keyof T]>>,
1515
) {
1616
this.desiredConfig = desiredConfig;
1717
this.stateConfig = stateConfig

src/resource/parsed-resource-settings.ts

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
11
import { JSONSchemaType } from 'ajv';
22
import { StringIndexedObject } from 'codify-schemas';
33

4+
import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
45
import {
6+
ArrayParameterSetting,
7+
DefaultParameterSetting,
58
ParameterSetting,
69
resolveEqualsFn,
10+
resolveFnFromEqualsFnOrString,
711
resolveParameterTransformFn,
812
ResourceSettings,
913
StatefulParameterSetting
1014
} from './resource-settings.js';
11-
import { StatefulParameter as StatefulParameterImpl } from './stateful-parameter.js'
15+
16+
export interface ParsedStatefulParameterSetting extends DefaultParameterSetting {
17+
type: 'stateful',
18+
controller: StatefulParameterController<any, unknown>
19+
order?: number,
20+
nestedSettings: ParsedParameterSetting;
21+
}
22+
23+
export type ParsedArrayParameterSetting = {
24+
isElementEqual: (a: unknown, b: unknown) => boolean;
25+
isEqual: (a: unknown, b: unknown) => boolean;
26+
} & ArrayParameterSetting
27+
28+
export type ParsedParameterSetting =
29+
{
30+
isEqual: (desired: unknown, current: unknown) => boolean;
31+
} & (DefaultParameterSetting
32+
| ParsedArrayParameterSetting
33+
| ParsedStatefulParameterSetting)
1234

1335
export class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
1436
private cache = new Map<string, unknown>();
@@ -36,24 +58,49 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
3658
return this.id;
3759
}
3860

39-
get statefulParameters(): Map<keyof T, StatefulParameterImpl<T, T[keyof T]>> {
61+
get statefulParameters(): Map<keyof T, StatefulParameterController<T, T[keyof T]>> {
4062
return this.getFromCacheOrCreate('statefulParameters', () => {
4163

4264
const statefulParameters = Object.entries(this.settings.parameterSettings ?? {})
4365
.filter(([, p]) => p?.type === 'stateful')
44-
.map(([k, v]) => [k, (v as StatefulParameterSetting).definition] as const)
66+
.map(([k, v]) => [
67+
k,
68+
new StatefulParameterController((v as StatefulParameterSetting).definition)
69+
] as const)
4570

46-
return new Map(statefulParameters) as Map<keyof T, StatefulParameterImpl<T, T[keyof T]>>;
71+
return new Map(statefulParameters) as Map<keyof T, StatefulParameterController<T, T[keyof T]>>;
4772
})
4873
}
4974

50-
get parameterSettings(): Record<keyof T, ParameterSetting> {
75+
get parameterSettings(): Record<keyof T, ParsedParameterSetting> {
5176
return this.getFromCacheOrCreate('parameterSetting', () => {
5277

5378
const settings = Object.entries(this.settings.parameterSettings ?? {})
5479
.map(([k, v]) => [k, v!] as const)
5580
.map(([k, v]) => {
56-
v.isEqual = resolveEqualsFn(v, k);
81+
v.isEqual = resolveEqualsFn(v);
82+
83+
if (v.type === 'stateful') {
84+
const spController = this.statefulParameters.get(k);
85+
const parsed = {
86+
...v,
87+
controller: spController,
88+
nestedSettings: spController?.parsedSettings,
89+
definition: undefined,
90+
};
91+
92+
return [k, parsed as ParsedStatefulParameterSetting];
93+
}
94+
95+
if (v.type === 'array') {
96+
const parsed = {
97+
...v,
98+
isElementEqual: resolveFnFromEqualsFnOrString((v as ArrayParameterSetting).isElementEqual)
99+
?? ((a: unknown, b: unknown) => a === b),
100+
}
101+
102+
return [k, parsed as ParsedArrayParameterSetting];
103+
}
57104

58105
return [k, v];
59106
})
@@ -86,15 +133,15 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
86133
});
87134
}
88135

89-
get inputTransformations(): Partial<Record<keyof T, (a: unknown) => unknown>> {
136+
get inputTransformations(): Partial<Record<keyof T, (a: unknown, parameter: ParameterSetting) => unknown>> {
90137
return this.getFromCacheOrCreate('inputTransformations', () => {
91138
if (!this.settings.parameterSettings) {
92139
return {};
93140
}
94141

95142
return Object.fromEntries(
96143
Object.entries(this.settings.parameterSettings)
97-
.filter(([, v]) => resolveParameterTransformFn(v!) !== undefined)
144+
.filter(([_, v]) => resolveParameterTransformFn(v!) !== undefined)
98145
.map(([k, v]) => [k, resolveParameterTransformFn(v!)] as const)
99146
) as Record<keyof T, (a: unknown) => unknown>;
100147
});

0 commit comments

Comments
 (0)