Skip to content

Commit 44743dd

Browse files
[CODE-43] Refactor resource options (#3)
* Added entity to contain resource options and all of it's operations. WIP * Separated stateful parameter refresh into a new method. Added a config parser. WIP * Update the term "configuration" to "option" to avoid confusion between user-defined configs and developer set resource or parameter options. Cleaned up plan and fixed tests * Re-worked the type the resource options. Added ordering to stateful parameters. Added tests * Added ordering for transform parameters * Added schema validation to resource options * Changed schema type to unknown and changed super method to validateResource * Transform parameters bug fix and added test * Fixed default values not resolving. Default values from now on will only resolve for desired and not current.
1 parent f4b3cc0 commit 44743dd

14 files changed

Lines changed: 821 additions & 261 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codify-plugin-lib",
3-
"version": "1.0.50",
3+
"version": "1.0.55",
44
"description": "",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",

src/entities/change-set.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
2-
import { ParameterConfiguration } from './plan-types.js';
2+
import { ParameterOptions } from './plan-types.js';
33

44
export interface ParameterChange<T extends StringIndexedObject> {
55
name: keyof T & string;
@@ -61,12 +61,12 @@ export class ChangeSet<T extends StringIndexedObject> {
6161
static calculateParameterChangeSet<T extends StringIndexedObject>(
6262
desired: T | null,
6363
current: T | null,
64-
options: { statefulMode: boolean, parameterConfigurations?: Record<keyof T, ParameterConfiguration> },
64+
options: { statefulMode: boolean, parameterOptions?: Record<keyof T, ParameterOptions> },
6565
): ParameterChange<T>[] {
6666
if (options.statefulMode) {
67-
return ChangeSet.calculateStatefulModeChangeSet(desired, current, options.parameterConfigurations);
67+
return ChangeSet.calculateStatefulModeChangeSet(desired, current, options.parameterOptions);
6868
} else {
69-
return ChangeSet.calculateStatelessModeChangeSet(desired, current, options.parameterConfigurations);
69+
return ChangeSet.calculateStatelessModeChangeSet(desired, current, options.parameterOptions);
7070
}
7171
}
7272

@@ -88,10 +88,10 @@ export class ChangeSet<T extends StringIndexedObject> {
8888
static isSame(
8989
desired: unknown,
9090
current: unknown,
91-
configuration?: ParameterConfiguration,
91+
options?: ParameterOptions,
9292
): boolean {
93-
if (configuration?.isEqual) {
94-
return configuration.isEqual(desired, current);
93+
if (options?.isEqual) {
94+
return options.isEqual(desired, current);
9595
}
9696

9797
if (Array.isArray(desired) && Array.isArray(current)) {
@@ -102,9 +102,9 @@ export class ChangeSet<T extends StringIndexedObject> {
102102
return false;
103103
}
104104

105-
if (configuration?.isElementEqual) {
105+
if (options?.isElementEqual) {
106106
return sortedDesired.every((value, index) =>
107-
configuration.isElementEqual!(value, sortedCurrent[index])
107+
options.isElementEqual!(value, sortedCurrent[index])
108108
);
109109
}
110110

@@ -119,13 +119,15 @@ export class ChangeSet<T extends StringIndexedObject> {
119119
private static calculateStatefulModeChangeSet<T extends StringIndexedObject>(
120120
desired: T | null,
121121
current: T | null,
122-
parameterConfigurations?: Record<keyof T, ParameterConfiguration>,
122+
parameterOptions?: Record<keyof T, ParameterOptions>,
123123
): ParameterChange<T>[] {
124124
const parameterChangeSet = new Array<ParameterChange<T>>();
125125

126126
const _desired = { ...desired };
127127
const _current = { ...current };
128128

129+
this.addDefaultValues(_desired, parameterOptions);
130+
129131
for (const [k, v] of Object.entries(_current)) {
130132
if (_desired[k] == null) {
131133
parameterChangeSet.push({
@@ -139,7 +141,7 @@ export class ChangeSet<T extends StringIndexedObject> {
139141
continue;
140142
}
141143

142-
if (!ChangeSet.isSame(_desired[k], _current[k], parameterConfigurations?.[k])) {
144+
if (!ChangeSet.isSame(_desired[k], _current[k], parameterOptions?.[k])) {
143145
parameterChangeSet.push({
144146
name: k,
145147
previousValue: v,
@@ -184,13 +186,15 @@ export class ChangeSet<T extends StringIndexedObject> {
184186
private static calculateStatelessModeChangeSet<T extends StringIndexedObject>(
185187
desired: T | null,
186188
current: T | null,
187-
parameterConfigurations?: Record<keyof T, ParameterConfiguration>,
189+
parameterOptions?: Record<keyof T, ParameterOptions>,
188190
): ParameterChange<T>[] {
189191
const parameterChangeSet = new Array<ParameterChange<T>>();
190192

191193
const _desired = { ...desired };
192194
const _current = { ...current };
193195

196+
this.addDefaultValues(_desired, parameterOptions);
197+
194198
for (const [k, v] of Object.entries(_desired)) {
195199
if (_current[k] == null) {
196200
parameterChangeSet.push({
@@ -203,7 +207,7 @@ export class ChangeSet<T extends StringIndexedObject> {
203207
continue;
204208
}
205209

206-
if (!ChangeSet.isSame(_desired[k], _current[k], parameterConfigurations?.[k])) {
210+
if (!ChangeSet.isSame(_desired[k], _current[k], parameterOptions?.[k])) {
207211
parameterChangeSet.push({
208212
name: k,
209213
previousValue: _current[k],
@@ -224,5 +228,16 @@ export class ChangeSet<T extends StringIndexedObject> {
224228

225229
return parameterChangeSet;
226230
}
231+
232+
private static addDefaultValues<T extends StringIndexedObject>(obj: Record<string, unknown>, options?: Record<keyof T, ParameterOptions>) {
233+
Object.entries(options ?? {})
234+
.filter(([, option]) => option.default !== undefined)
235+
.map(([name, option]) => [name, option.default] as const)
236+
.forEach(([key, defaultValue]) => {
237+
if (obj[key] === undefined) {
238+
obj[key] = defaultValue;
239+
}
240+
})
241+
}
227242

228243
}

src/entities/plan-types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ResourceOperation } from 'codify-schemas';
33
/**
44
* Customize properties for specific parameters. This will alter the way the library process changes to the parameter.
55
*/
6-
export interface ParameterConfiguration {
6+
export interface ParameterOptions {
77
/**
88
* Chose if the resource should be re-created or modified if this parameter is changed. Defaults to re-create.
99
*/
@@ -17,10 +17,12 @@ export interface ParameterConfiguration {
1717

1818
isElementEqual?: (desired: any, current: any) => boolean;
1919

20+
default?: unknown,
21+
2022
isStatefulParameter?: boolean;
2123
}
2224

23-
export interface PlanConfiguration<T> {
25+
export interface PlanOptions<T> {
2426
statefulMode: boolean;
25-
parameterConfigurations?: Record<keyof T, ParameterConfiguration>;
27+
parameterOptions?: Record<keyof T, ParameterOptions>;
2628
}

src/entities/plan.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,8 @@ function createResource(): Resource<any> {
140140
constructor() {
141141
super({
142142
type: 'type',
143-
parameterConfigurations: {
144-
propA: {
145-
defaultValue: 'defaultA'
146-
}
143+
parameterOptions: {
144+
propA: { default: 'defaultA' }
147145
}
148146
});
149147
}

src/entities/plan.ts

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
StringIndexedObject,
99
} from 'codify-schemas';
1010
import { randomUUID } from 'crypto';
11-
import { ParameterConfiguration, PlanConfiguration } from './plan-types.js';
11+
import { ParameterOptions, PlanOptions } from './plan-types.js';
1212

1313
export class Plan<T extends StringIndexedObject> {
1414
id: string;
@@ -25,11 +25,11 @@ export class Plan<T extends StringIndexedObject> {
2525
desiredParameters: Partial<T> | null,
2626
currentParameters: Partial<T> | null,
2727
resourceMetadata: ResourceConfig,
28-
configuration: PlanConfiguration<T>
28+
options: PlanOptions<T>
2929
): Plan<T> {
30-
const parameterConfigurations = configuration.parameterConfigurations ?? {} as Record<keyof T, ParameterConfiguration>;
30+
const parameterOptions = options.parameterOptions ?? {} as Record<keyof T, ParameterOptions>;
3131
const statefulParameterNames = new Set(
32-
[...Object.entries(parameterConfigurations)]
32+
[...Object.entries(parameterOptions)]
3333
.filter(([k, v]) => v.isStatefulParameter)
3434
.map(([k, v]) => k)
3535
);
@@ -40,7 +40,7 @@ export class Plan<T extends StringIndexedObject> {
4040
const parameterChangeSet = ChangeSet.calculateParameterChangeSet(
4141
desiredParameters,
4242
currentParameters,
43-
{ statefulMode: configuration.statefulMode, parameterConfigurations }
43+
{ statefulMode: options.statefulMode, parameterOptions }
4444
);
4545

4646
let resourceOperation: ResourceOperation;
@@ -55,8 +55,8 @@ export class Plan<T extends StringIndexedObject> {
5555
let newOperation: ResourceOperation;
5656
if (statefulParameterNames.has(curr.name)) {
5757
newOperation = ResourceOperation.MODIFY // All stateful parameters are modify only
58-
} else if (parameterConfigurations[curr.name]?.planOperation) {
59-
newOperation = parameterConfigurations[curr.name].planOperation!;
58+
} else if (parameterOptions[curr.name]?.planOperation) {
59+
newOperation = parameterOptions[curr.name].planOperation!;
6060
} else {
6161
newOperation = ResourceOperation.RECREATE; // Default to Re-create. Should handle the majority of use cases
6262
}
@@ -97,43 +97,46 @@ export class Plan<T extends StringIndexedObject> {
9797
function addDefaultValues(): void {
9898
Object.entries(defaultValues)
9999
.forEach(([key, defaultValue]) => {
100-
const configValueExists = data
101-
?.parameters
102-
.find((p) => p.name === key) !== undefined;
103-
104-
if (!configValueExists) {
105-
switch (data?.operation) {
106-
case ResourceOperation.CREATE: {
107-
data?.parameters.push({
108-
name: key,
109-
operation: ParameterOperation.ADD,
110-
previousValue: null,
111-
newValue: defaultValue,
112-
});
113-
break;
114-
}
115-
116-
case ResourceOperation.DESTROY: {
117-
data?.parameters.push({
118-
name: key,
119-
operation: ParameterOperation.REMOVE,
120-
previousValue: defaultValue,
121-
newValue: null,
122-
});
123-
break;
124-
}
125-
126-
case ResourceOperation.MODIFY:
127-
case ResourceOperation.RECREATE:
128-
case ResourceOperation.NOOP: {
129-
data?.parameters.push({
130-
name: key,
131-
operation: ParameterOperation.NOOP,
132-
previousValue: defaultValue,
133-
newValue: defaultValue,
134-
});
135-
break;
136-
}
100+
const configValueExists = data!
101+
.parameters
102+
.some((p) => p.name === key);
103+
104+
// Only set default values if the value does not exist in the config
105+
if (configValueExists) {
106+
return;
107+
}
108+
109+
switch (data!.operation) {
110+
case ResourceOperation.CREATE: {
111+
data!.parameters.push({
112+
name: key,
113+
operation: ParameterOperation.ADD,
114+
previousValue: null,
115+
newValue: defaultValue,
116+
});
117+
break;
118+
}
119+
120+
case ResourceOperation.DESTROY: {
121+
data!.parameters.push({
122+
name: key,
123+
operation: ParameterOperation.REMOVE,
124+
previousValue: defaultValue,
125+
newValue: null,
126+
});
127+
break;
128+
}
129+
130+
case ResourceOperation.MODIFY:
131+
case ResourceOperation.RECREATE:
132+
case ResourceOperation.NOOP: {
133+
data!.parameters.push({
134+
name: key,
135+
operation: ParameterOperation.NOOP,
136+
previousValue: defaultValue,
137+
newValue: defaultValue,
138+
});
139+
break;
137140
}
138141
}
139142
});

src/entities/plugin.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class Plugin {
4242
}
4343

4444
const { parameters } = splitUserConfig(config);
45-
const validateResult = await this.resources.get(config.type)!.validate(parameters);
45+
const validateResult = await this.resources.get(config.type)!.validateResource(parameters);
4646

4747
validationResults.push({
4848
...validateResult,
@@ -95,11 +95,11 @@ export class Plugin {
9595
}
9696

9797
if (!planRequest?.resourceType || !this.resources.has(planRequest.resourceType)) {
98-
throw new Error('Malformed plan. Resource type must be supplied');
98+
throw new Error('Malformed plan. Resource type must be supplied or resource type was not found');
9999
}
100100

101-
const resource = this.resources.get(planRequest.resourceType);
102-
return Plan.fromResponse(data.plan, resource?.defaultValues!);
101+
const resource = this.resources.get(planRequest.resourceType)!;
102+
return Plan.fromResponse(data.plan, resource.defaultValues);
103103
}
104104

105105
protected async crossValidateResources(configs: ResourceConfig[]): Promise<void> {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { ResourceOptions, ResourceOptionsParser } from './resource-options.js';
3+
import { TestConfig } from './resource.test.js';
4+
5+
describe('Resource options parser tests', () => {
6+
it('Parses default values from options', () => {
7+
const option: ResourceOptions<TestConfig> = {
8+
type: 'typeId',
9+
parameterOptions: {
10+
propA: { default: 'propA' },
11+
propB: { default: 'propB' },
12+
propC: { isEqual: () => true },
13+
propD: { },
14+
}
15+
}
16+
17+
const result = new ResourceOptionsParser(option);
18+
expect(result.defaultValues).to.deep.eq({
19+
propA: 'propA',
20+
propB: 'propB'
21+
})
22+
})
23+
})

0 commit comments

Comments
 (0)