Skip to content

Commit 99d04c8

Browse files
committed
Added first step of semantic analysis + minor refactoring
1 parent c68a8a4 commit 99d04c8

10 files changed

Lines changed: 81 additions & 44 deletions

File tree

codify-core/src/commands/plan/orchestrator.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { ConfigCompiler } from '../../config-compiler';
2+
import { ConfigSemanticAnalyzer } from '../../config-compiler/semantic-analysis/config-semantic-analyzer';
23
import { PluginCollection } from '../../plugins/plugin-collection';
34

45
export const PlanOrchestrator = {
56
async run(rootDirectory: string): Promise<void> {
67
const project = await ConfigCompiler.parseProject(rootDirectory);
78

8-
const pluginCollection = new PluginCollection();
9-
await pluginCollection.initializePlugins(project);
10-
9+
const pluginCollection = await PluginCollection.create(project);
1110
const resourceDefinitions = await pluginCollection.getAllResourceDefinitions();
12-
console.log(resourceDefinitions);
11+
12+
await ConfigSemanticAnalyzer.validate(project, resourceDefinitions);
1313

1414
await pluginCollection.destroy();
1515
},

codify-core/src/config-compiler/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import { ResourceDefinitions } from '../entities/resource-definition';
12
import { InternalError } from '../utils/errors';
23
import { ConfigBlockType } from './language-definition';
34
import { ConfigLoader } from './loader';
45
import { FileParser } from './parser';
56
import { ParsedModule, ParsedProject } from './parser/entities';
67
import { ProjectConfig } from './parser/entities/project';
78
import { JsonFileParser } from './parser/json/file-parser';
9+
import { ConfigSemanticAnalyzer } from './semantic-analysis/config-semantic-analyzer';
810

911
export class ConfigCompiler {
1012

@@ -38,4 +40,8 @@ export class ConfigCompiler {
3840
projectConfig,
3941
})
4042
}
43+
44+
static async analyzeProject(parsedProject: ParsedProject, resourceDefinitions: ResourceDefinitions): Promise<void> {
45+
ConfigSemanticAnalyzer.validate(parsedProject, resourceDefinitions);
46+
}
4147
}

codify-core/src/config-compiler/parser/entities/resource.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { RemoveMethods } from '../../../utils/types';
2-
import { validateNameString, validateStringEq, validateTypeRecordStringUnknown, } from '../../../utils/validator';
2+
import { validateNameString, validateTypeRecordStringUnknown, validateTypeString, } from '../../../utils/validator';
33
import { ConfigBlockType } from '../../language-definition';
44
import { ConfigBlock } from './index';
55

@@ -28,10 +28,10 @@ export class ResourceConfig implements ConfigBlock {
2828

2929
constructor(config: unknown) {
3030
if (this.validate(config)) {
31-
const { name, type, ...params } = config;
31+
const { name, type, ...parameters } = config;
3232
this.type = type;
3333
this.name = name;
34-
this.parameters = params ?? {};
34+
this.parameters = parameters ?? {};
3535

3636
return;
3737
}
@@ -44,8 +44,8 @@ export class ResourceConfig implements ConfigBlock {
4444
throw new Error('Config is not an object');
4545
}
4646

47-
if (!validateStringEq(config.type, 'resource')) {
48-
throw new Error('Config is not of type resource');
47+
if (!validateTypeString(config.type)) {
48+
throw new Error('Config type is not specified');
4949
}
5050

5151
if (config.name && !validateNameString(config.name)) {

codify-core/src/config-compiler/parser/json/config-block-factory.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { InvalidResourceError, SyntaxError } from '../../../utils/errors';
1+
import { SyntaxError } from '../../../utils/errors';
22
import { ConfigBlockType } from '../../language-definition';
33
import { ConfigBlock } from '../entities';
44
import { PluginConfig } from '../entities/plugin';
@@ -19,16 +19,6 @@ export const JsonConfigBlockFactory = {
1919
}
2020

2121
switch (unknownNode.type) {
22-
case ConfigBlockType.RESOURCE: {
23-
const { name, type, ...parameters } = unknownNode;
24-
25-
return new ResourceConfig({
26-
name: name?.toString(),
27-
parameters,
28-
type,
29-
});
30-
}
31-
3222
case ConfigBlockType.PLUGIN: {
3323
return new PluginConfig({
3424
parameters: unknownNode,
@@ -40,11 +30,7 @@ export const JsonConfigBlockFactory = {
4030
}
4131

4232
default: {
43-
throw new InvalidResourceError({
44-
fileName: errorInfo.fileName,
45-
message: 'Unknown resource type',
46-
resourceDefinition: JSON.stringify(unknownNode, null, 2),
47-
})
33+
return new ResourceConfig(unknownNode);
4834
}
4935
}
5036
},
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ResourceDefinitions } from '../../entities/resource-definition';
2+
import { ConfigBlockType } from '../language-definition';
3+
import { ParsedProject } from '../parser/entities';
4+
import { ResourceConfig } from '../parser/entities/resource';
5+
6+
export const ConfigSemanticAnalyzer = {
7+
8+
async validate(project: ParsedProject, resourceDefinitions: ResourceDefinitions): Promise<void> {
9+
const resourceConfigs = project
10+
.coreModule
11+
.configBlocks
12+
.filter((u) => u.configType === ConfigBlockType.RESOURCE) as ResourceConfig[];
13+
14+
this.validateResourceConfigs(resourceConfigs, resourceDefinitions);
15+
},
16+
17+
// TODO: Move this logic to ResourceConfig entity when plugin initialization is moved to be the first step
18+
validateResourceConfigs(blocks: ResourceConfig[], resourceDefinitions: ResourceDefinitions) {
19+
for (const configBlock of blocks) {
20+
const { parameters, type } = configBlock;
21+
const definition = resourceDefinitions.get(type);
22+
if (!definition) {
23+
throw new Error(`Invalid resource type specified ${type}. Type is not found in any plugins`);
24+
}
25+
26+
const { parameters: parameterDefinitions } = definition;
27+
for (const [key, value] of Object.entries(parameters)) {
28+
29+
if (!parameterDefinitions.has(key)) {
30+
throw new Error(`Invalid resource parameter: ${key}. Resource: ${type}`)
31+
}
32+
33+
const parameter = parameterDefinitions.get(key)!;
34+
// validate value here
35+
if (parameter.allowedValues && !(parameter.allowedValues as Array<unknown>).includes(value)) {
36+
throw new Error(`Invalid resource config ${type}. Allowed values are ${parameter.allowedValues} but ${value} was provided`)
37+
}
38+
}
39+
}
40+
41+
}
42+
43+
};

codify-core/src/entities/resource-definition.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { InternalError } from '../utils/errors';
12
import { RemoveMethods } from '../utils/types';
23
import { validateAllowedObjectKeys, validateNameString, validateTypeRecordStringUnknown, } from '../utils/validator';
34
import { ResourceParameterDefinition } from './resource-parameter';
45

56
type Name = string;
7+
export type ResourceDefinitions = Map<Name, ResourceDefinition>
68

79
/**
810
* Example json def:
@@ -17,7 +19,6 @@ type Name = string;
1719
* }
1820
* }
1921
*/
20-
2122
export class ResourceDefinition {
2223
name!: string;
2324
parameters!: Map<Name, ResourceParameterDefinition>;
@@ -26,7 +27,7 @@ export class ResourceDefinition {
2627
Object.assign(this, props);
2728
}
2829

29-
static fromJson(json: unknown): ResourceDefinition {
30+
static fromJson(pluginName: string, json: unknown): ResourceDefinition {
3031
if (this.validateDefinition(json)) {
3132
const entries = Object.entries(json.parameters).map(([name, value]) => {
3233
const resourceParameterDefinition = ResourceParameterDefinition.fromJson(name, value);
@@ -35,27 +36,27 @@ export class ResourceDefinition {
3536
})
3637

3738
const parametersMap = new Map(entries);
38-
return new ResourceDefinition({ name: json.name, parameters: parametersMap })
39+
return new ResourceDefinition({ name: `${pluginName}_${json.name}`, parameters: parametersMap })
3940
}
4041

41-
throw new Error('Unable to parse resource definition');
42+
throw new InternalError('Unable to parse resource definition');
4243
}
4344

4445
private static validateDefinition(json: unknown): json is { name: string; parameters: Record<string, unknown> } {
4546
if (!validateTypeRecordStringUnknown(json)) {
46-
return false;
47+
throw new Error(`Unable to validate definition is type object ${json}`);
4748
}
4849

4950
if (!validateAllowedObjectKeys(json, ['name', 'parameters'])) {
50-
return false;
51+
throw new Error(`Only keys name and parameters is allowed. ${json}`);
5152
}
5253

5354
if (!validateNameString(json.name)) {
54-
return false;
55+
throw new Error(`Unable find definition name. ${json}`);
5556
}
5657

5758
if (!validateTypeRecordStringUnknown(json.parameters)) {
58-
return false;
59+
throw new Error(`Unable to parse parameters. ${json}`);
5960
}
6061

6162
return true;

codify-core/src/plugins/entities/plugin-data.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ export class PluginData {
1515
Object.assign(this, props);
1616
}
1717

18-
static create(name: string, directory: string, resourceDefinitions: unknown): PluginData {
18+
static create(directory: string, name: string, resourceDefinitions: unknown): PluginData {
1919
if (this.validate(resourceDefinitions)) {
2020
const entries = resourceDefinitions.map((u) => {
21-
const resourceDefinition = ResourceDefinition.fromJson(u);
21+
const resourceDefinition = ResourceDefinition.fromJson(name, u);
2222

2323
// Append the plugin name to all resources to prevent conflicts across plugins
24-
return [`${this.name}_${resourceDefinition.name}`, resourceDefinition] as const;
24+
return [resourceDefinition.name, resourceDefinition] as const;
2525
})
2626

2727
return new PluginData({ directory, name, resourceDefinitions: new Map(entries) });

codify-core/src/plugins/ipc-bridge.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ type Reject = (reason?: Error) => void;
1010
const resultFunctionName = (cmd: string) => `${cmd}Result`;
1111

1212
export class PluginIpcBridge {
13-
1413
process: ChildProcess;
1514

1615
constructor(process: ChildProcess) {

codify-core/src/plugins/plugin-collection.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ const DEFAULT_PLUGINS = {
1212

1313
export class PluginCollection {
1414

15-
private plugins: Map<PluginName, Plugin> = new Map();
15+
private plugins: Map<PluginName, Plugin>
1616

17-
async initializePlugins(project: ParsedProject): Promise<void> {
17+
constructor(plugins: Map<PluginName, Plugin>) {
18+
this.plugins = plugins;
19+
}
20+
21+
static async create(project: ParsedProject): Promise<PluginCollection> {
1822
const pluginDefinitions = {
1923
...DEFAULT_PLUGINS,
2024
...project.projectConfig.plugins,
@@ -24,12 +28,10 @@ export class PluginCollection {
2428
PluginResolver.resolve(name, version)
2529
));
2630

27-
for (const plugin of plugins) {
28-
this.plugins.set(plugin.data.name, plugin);
29-
}
31+
return new PluginCollection(new Map(plugins.map((plugin) => [plugin.data.name, plugin])))
3032
}
3133

32-
async getAllResourceDefinitions(): Promise<Map<string, ResourceDefinition>> {
34+
getAllResourceDefinitions(): Map<string, ResourceDefinition> {
3335
const result = new Map<string, ResourceDefinition>();
3436
for (const plugin of this.plugins.values()) {
3537
const { resourceDefinitions } = plugin.data;

plugins/homebrew/resource-definitions/homebrew_installation.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "homebrew_installation",
2+
"name": "installation",
33
"parameters": {
44
"directory": "string"
55
}

0 commit comments

Comments
 (0)