Skip to content

Commit

Permalink
[INTERNAL] graph#utils: Add workspace schema validation
Browse files Browse the repository at this point in the history
  • Loading branch information
RandomByte committed Jan 25, 2023
1 parent b5c0945 commit a8d996f
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 23 deletions.
68 changes: 53 additions & 15 deletions lib/graph/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import path from "node:path";
import projectGraphBuilder from "./projectGraphBuilder.js";
import ui5Framework from "./helpers/ui5Framework.js";
import Workspace from "./Workspace.js";
import {validateWorkspace} from "../validation/validator.js";
import {getLogger} from "@ui5/logger";
const log = getLogger("generateProjectGraph");

const DEFAULT_WORKSPACE_CONFIG_PATH = "ui5-workspace.yaml";

function resolveProjectPaths(cwd, project) {
if (!project.path) {
throw new Error(`Missing or empty attribute 'path' for project ${project.id}`);
Expand Down Expand Up @@ -58,7 +61,7 @@ function resolveProjectPaths(cwd, project) {
export async function graphFromPackageDependencies({
cwd, rootConfiguration, rootConfigPath,
versionOverride, resolveFrameworkDependencies = true,
activeWorkspace="default", workspaceConfiguration, workspaceConfigPath
activeWorkspace="default", workspaceConfiguration, workspaceConfigPath = DEFAULT_WORKSPACE_CONFIG_PATH
}) {
log.verbose(`Creating project graph using npm provider...`);
const {
Expand All @@ -69,7 +72,6 @@ export async function graphFromPackageDependencies({

let workspace;
if (workspaceConfiguration) {
// TODO 3.0: Schema validation
if (workspaceConfiguration.metadata.name !== activeWorkspace) {
log.warn(
`Provided workspace configuration name "${workspaceConfiguration.metadata.name}" ` +
Expand All @@ -82,11 +84,7 @@ export async function graphFromPackageDependencies({
});
}
} else {
const throwIfMissing = !!workspaceConfigPath;
if (!workspaceConfigPath) {
workspaceConfigPath = "ui5-workspace.yaml";
}
const workspaceConfigs = await utils.readWorkspaceConfigFile(cwd, workspaceConfigPath, throwIfMissing);
const workspaceConfigs = await utils.readWorkspaceConfigFile(cwd, workspaceConfigPath);
const workspaceConfiguration = workspaceConfigs.find((config) => {
return config.metadata.name === activeWorkspace;
});
Expand Down Expand Up @@ -229,7 +227,7 @@ const utils = {
}
return dependencyTree;
},
readWorkspaceConfigFile: async function(cwd, configPath, throwIfMissing = false) {
readWorkspaceConfigFile: async function(cwd, configPath) {
const {
default: fs
} = await import("graceful-fs");
Expand All @@ -242,26 +240,66 @@ const utils = {
filePath = path.join(cwd, configPath);
}

let fileContent;
try {
fileContent = await readFile(filePath, {encoding: "utf8"});
} catch (err) {
if (err.code === "ENOENT" && configPath === DEFAULT_WORKSPACE_CONFIG_PATH ) {
log.verbose(`No workspace configuration file provided at ${filePath}`);
return [];
}
throw new Error(
`Failed to load workspace configuration from path ${filePath}: ${err.message}`);
}
let configs;
try {
const contents = await readFile(filePath, {encoding: "utf-8"});
configs = jsyaml.loadAll(contents, undefined, {
configs = jsyaml.loadAll(fileContent, undefined, {
filename: filePath,
});
// TODO 3.0: Add schema validation
} catch (err) {
if (err.name === "YAMLException") {
throw new Error("Failed to parse configuration for project " +
`${this.getId()} at '${filePath}'\nError: ${err.message}`);
} else if (err.code === "ENOENT" && !throwIfMissing) {
log.verbose(`No workspace configuration file provided at ${filePath}`);
return [];
} else {
throw new Error(
`Failed to load workspace configuration from path ${filePath}: ${err.message}`);
`Failed to parse workspace configuration at ${filePath}: ${err.message}`);
}
}

if (!configs || !configs.length) {
// No configs found => exit here
log.verbose(`Found empty workspace configuration file at ${filePath}`);
return configs;
}

// Validate found configurations with schema
const validationResults = await Promise.all(
configs.map(async (config, documentIndex) => {
// Catch validation errors to ensure proper order of rejections within Promise.all
try {
await validateWorkspace({
config,
yaml: {
path: filePath,
source: fileContent,
documentIndex
},
schemaName: "ui5-workspace"
});
} catch (error) {
return error;
}
})
);

const validationErrors = validationResults.filter(($) => $);

if (validationErrors.length > 0) {
// Throw any validation errors
// For now just throw the error of the first invalid document
throw validationErrors[0];
}

return configs;
}
};
Expand Down
12 changes: 4 additions & 8 deletions test/lib/graph/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,6 @@ test.serial("graphFromPackageDependencies with workspace file", async (t) => {
"readWorkspaceConfigFile got called with correct first argument");
t.is(readWorkspaceConfigFileStub.getCall(0).args[1], "ui5-workspace.yaml",
"readWorkspaceConfigFile got called with correct second argument");
t.is(readWorkspaceConfigFileStub.getCall(0).args[2], false,
"readWorkspaceConfigFile got called with correct third argument");

t.is(workspaceConstructorStub.callCount, 1, "Workspace constructor got called once");
t.deepEqual(workspaceConstructorStub.getCall(0).args[0], {
Expand Down Expand Up @@ -292,8 +290,6 @@ test.serial("graphFromPackageDependencies with inactive workspace file at custom
"readWorkspaceConfigFile got called with correct first argument");
t.is(readWorkspaceConfigFileStub.getCall(0).args[1], "workspaceConfigPath",
"readWorkspaceConfigFile got called with correct second argument");
t.is(readWorkspaceConfigFileStub.getCall(0).args[2], true,
"readWorkspaceConfigFile got called with correct third argument");

t.is(workspaceConstructorStub.callCount, 0, "Workspace constructor is not called");

Expand Down Expand Up @@ -496,19 +492,19 @@ test.serial("utils: readWorkspaceConfigFile", async (t) => {
}], "Returned correct file content");
});

test.serial("utils: readWorkspaceConfigFile - throwIfMissing: false", async (t) => {
test.serial("utils: readWorkspaceConfigFile - Does not throw if default file is missing", async (t) => {
const {graphFromPackageDependencies} = t.context.graph;
const res = await graphFromPackageDependencies._utils.readWorkspaceConfigFile(
path.join(fixturesPath, "library.d"), "ui5-workspace.yaml");

t.deepEqual(res, [], "Returned empty array");
});

test.serial("utils: readWorkspaceConfigFile - throwIfMissing: true", async (t) => {
test.serial("utils: readWorkspaceConfigFile - Throws if non-default file is missing", async (t) => {
const {graphFromPackageDependencies} = t.context.graph;
const err = await t.throwsAsync(graphFromPackageDependencies._utils.readWorkspaceConfigFile(
path.join(fixturesPath, "library.d"), "ui5-workspace.yaml", true));
const filePath = path.join(fixturesPath, "library.d", "ui5-workspace.yaml");
path.join(fixturesPath, "library.d"), "other-ui5-workspace.yaml", true));
const filePath = path.join(fixturesPath, "library.d", "other-ui5-workspace.yaml");
t.is(err.message,
`Failed to load workspace configuration from path ${filePath}: ` +
`ENOENT: no such file or directory, open '${filePath}'`);
Expand Down

0 comments on commit a8d996f

Please sign in to comment.