Skip to content

Commit

Permalink
update validate config
Browse files Browse the repository at this point in the history
  • Loading branch information
gladwindos committed Feb 16, 2024
1 parent a6a5987 commit c0da611
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 55 deletions.
2 changes: 1 addition & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ endpoints:
url: "https://api.spotify.com/v1/artists/4Z8W4fKeB5YxbusRsdQVPb"
headers:
- key: "Authorization"
value: "Bearer ${SPOTIFY_API_KEY}"
value: "Bearer ${SPOTIFY_TOKEN}"
6 changes: 0 additions & 6 deletions src/middleware/error-handler.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import express from "express";
import { parseConfig } from "./utils/parse-config";
import { createProxies } from "./utils/create-proxies";

// TODO: prevent the app from crashing if the config is invalid
const config = parseConfig("config.yml");

const router = express.Router();
Expand Down
2 changes: 1 addition & 1 deletion src/types/endpoints.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface KeyValue {
export interface KeyValue {
key: string;
value: string;
}
Expand Down
21 changes: 1 addition & 20 deletions src/utils/parse-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,7 @@ export const parseConfig = (filePath: string): Config => {

const config = YAML.parse(file);

// TODO: validate config (move all to validateConfig)
const valid = validateConfig(config);

if (!valid) {
throw new Error(
`Invalid config: ${JSON.stringify(validateConfig.errors, null, 2)}`,
);
}

const endpointNames = new Set<string>();
config.endpoints.forEach((endpoint) => {
if (endpointNames.has(endpoint.name)) {
throw new Error(
`Invalid config: Duplicate endpoint name "${endpoint.name}"`,
);
}
endpointNames.add(endpoint.name);
});

// TODO: validate destination url
validateConfig(config);

return config;
};
100 changes: 73 additions & 27 deletions src/utils/validate-config.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,32 @@
import Ajv, { JSONSchemaType } from "ajv";
import { Destination, Endpoint } from "../types/endpoints";
import { Destination, Endpoint, KeyValue } from "../types/endpoints";
import { Config } from "../types/config";

const ajv = new Ajv();

const keyValueSchema: JSONSchemaType<KeyValue> = {
type: "object",
properties: {
key: { type: "string" },
value: { type: "string" },
},
required: ["key", "value"],
};

const destinationSchema: JSONSchemaType<Destination> = {
type: "object",
properties: {
url: { type: "string" },
headers: {
type: "array",
items: {
type: "object",
properties: {
key: { type: "string" },
value: { type: "string" },
},
required: ["key", "value"],
},
nullable: true,
},
params: {
type: "array",
items: {
type: "object",
properties: {
key: { type: "string" },
value: { type: "string" },
},
required: ["key", "value"],
},
nullable: true,
},
headers: { type: "array", items: keyValueSchema, nullable: true },
params: { type: "array", items: keyValueSchema, nullable: true },
},
required: ["url"],
};

const endpointSchema: JSONSchemaType<Endpoint> = {
type: "object",
properties: {
name: { type: "string" }, // make unique
name: { type: "string" },
path: { type: "string" },
method: { type: "string", enum: ["GET", "POST", "PUT", "DELETE"] },
destination: destinationSchema,
Expand All @@ -58,4 +45,63 @@ const configSchema: JSONSchemaType<Config> = {
required: ["endpoints"],
};

export const validateConfig = ajv.compile(configSchema);
const validateEndpointUniqueness = (endpoints: Endpoint[]): string[] => {
const endpointNames = new Set<string>();
const endpointPaths = new Set<string>();
const errors: string[] = [];

endpoints.forEach((endpoint) => {
const nameKey = endpoint.name;
const pathKey = `${endpoint.method} ${endpoint.path}`;

if (endpointNames.has(nameKey)) {
errors.push(`Config Error: Duplicate endpoint name "${nameKey}"`);
} else {
endpointNames.add(nameKey);
}

if (endpointPaths.has(pathKey)) {
errors.push(`Config Error: Duplicate endpoint path "${pathKey}"`);
} else {
endpointPaths.add(pathKey);
}
});

return errors;
};

const validateDestinationUrl = (url: string) => {
try {
new URL(url);
} catch (error) {
throw new Error(`Config Error: Invalid destination URL "${url}"`);
}
};

export const validateConfig = (config: Config) => {
const validate = ajv.compile(configSchema);
const validationErrors: string[] = [];

if (!validate(config)) {
validationErrors.push(JSON.stringify(validate.errors, null, 2));
}

const { endpoints } = config;

const uniquenessErrors = validateEndpointUniqueness(endpoints);
if (uniquenessErrors.length > 0) {
validationErrors.push(...uniquenessErrors);
}

endpoints.forEach((endpoint) => {
try {
validateDestinationUrl(endpoint.destination.url);
} catch (error) {
validationErrors.push((error as Error).message);
}
});

if (validationErrors.length > 0) {
throw new Error(`Invalid config: \n${validationErrors.join("\n")}`);
}
};

0 comments on commit c0da611

Please sign in to comment.