mirror of https://github.com/Seich/Beau.git
Added a schema validator. (#13)
* Added a schema validator. This allows you to check if the given beau config is valid. Will be used to improve the CLI and remove schema validation-type errors from the actual code. * Added a validate command to the CLI. This command is a way to test the config file. Eventually it'll probably be removed and the schema validation will run on every other command. I have to determine how useful this is and how performance might be affected.
This commit is contained in:
parent
4fba235bad
commit
90761acaa4
|
|
@ -6,13 +6,17 @@ const { Command, flags } = require('@oclif/command');
|
|||
const Beau = require('../../src/beau');
|
||||
|
||||
class Base extends Command {
|
||||
loadConfig(configFile) {
|
||||
openConfigFile(configFile) {
|
||||
if (!fs.existsSync(configFile)) {
|
||||
this.error(`The config file, ${configFile} was not found.`);
|
||||
this.exit(1);
|
||||
}
|
||||
|
||||
const config = yaml.safeLoad(fs.readFileSync(configFile, 'utf-8'));
|
||||
return yaml.safeLoad(fs.readFileSync(configFile, 'utf-8'));
|
||||
}
|
||||
|
||||
loadConfig(configFile) {
|
||||
const config = this.openConfigFile(configFile);
|
||||
const env = dotenv.config().parsed || {};
|
||||
|
||||
return new Beau(config, env);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
const clc = require('cli-color');
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
const { flags } = require('@oclif/command');
|
||||
|
||||
const Base = require('../base');
|
||||
const { validate } = require('../../../src/schema.js');
|
||||
|
||||
class ValidateCommand extends Base {
|
||||
async run() {
|
||||
const { flags, args } = this.parse(ValidateCommand);
|
||||
const configFile = args.alias || flags.config;
|
||||
|
||||
const config = this.openConfigFile(configFile);
|
||||
|
||||
let result = await validate(config);
|
||||
if (result.valid) {
|
||||
this.log(`${configFile} is valid.`);
|
||||
} else {
|
||||
this.error(result.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValidateCommand.description = `Validates the given configuration file against Beau's configuration schema.`;
|
||||
ValidateCommand.flags = { ...Base.flags };
|
||||
ValidateCommand.args = [
|
||||
{
|
||||
name: 'alias',
|
||||
required: false,
|
||||
description: `The configuration file to validate.`
|
||||
}
|
||||
];
|
||||
module.exports = ValidateCommand;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
version: 1
|
||||
endpoint: https://httpbin.org/
|
||||
|
||||
cookiejar: true
|
||||
|
||||
GET /anything:
|
||||
alias: anything
|
||||
form:
|
||||
name: David
|
||||
params:
|
||||
hello: World
|
||||
|
||||
GET /cookies/set:
|
||||
alias: set-cookies
|
||||
params:
|
||||
hello: World
|
||||
|
||||
GET /status/418:
|
||||
alias: teapot
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
endpoint: http://localhost:10080
|
||||
|
||||
plugins:
|
||||
- beau-jwt:
|
||||
- jwt:
|
||||
data:
|
||||
userId: 12
|
||||
name: Sergio
|
||||
|
|
|
|||
|
|
@ -3934,6 +3934,21 @@
|
|||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"isemail": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.2.tgz",
|
||||
"integrity": "sha512-zfRhJn9rFSGhzU5tGZqepRSAj3+g6oTOHxMGGriWNJZzyLPUK8H7VHpqKntegnW8KLyGA9zwuNaCoopl40LTpg==",
|
||||
"requires": {
|
||||
"punycode": "2.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"punycode": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz",
|
||||
"integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0="
|
||||
}
|
||||
}
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
|
|
@ -4787,6 +4802,23 @@
|
|||
"merge-stream": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"joi": {
|
||||
"version": "13.2.0",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-13.2.0.tgz",
|
||||
"integrity": "sha512-VUzQwyCrmT2lIpxBCYq26dcK9veCQzDh84gQnCtaxCa8ePohX8JZVVsIb+E66kCUUcIvzeIpifa6eZuzqTZ3NA==",
|
||||
"requires": {
|
||||
"hoek": "5.x.x",
|
||||
"isemail": "3.x.x",
|
||||
"topo": "3.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"hoek": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz",
|
||||
"integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
||||
|
|
@ -7181,6 +7213,21 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"topo": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz",
|
||||
"integrity": "sha512-Tlu1fGlR90iCdIPURqPiufqAlCZYzLjHYVVbcFWDMcX7+tK8hdZWAfsMrD/pBul9jqHHwFjNdf1WaxA9vTRRhw==",
|
||||
"requires": {
|
||||
"hoek": "5.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"hoek": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz",
|
||||
"integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"clui": "^0.3.1",
|
||||
"deepmerge": "^2.1.0",
|
||||
"dotenv": "^5.0.1",
|
||||
"joi": "^13.2.0",
|
||||
"globby": "^8.0.1",
|
||||
"is-plain-object": "^2.0.4",
|
||||
"js-yaml": "^3.11.0",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
exports[`Beau's config Loader. should create a request list 1`] = `
|
||||
Beau {
|
||||
"config": Config {
|
||||
"CACHE": false,
|
||||
"COOKIEJAR": false,
|
||||
"DEFAULTS": Object {
|
||||
"headers": Object {
|
||||
|
|
@ -45,7 +44,6 @@ Beau {
|
|||
"VERSION": 1,
|
||||
"configKeys": Array [
|
||||
"VERSION",
|
||||
"CACHE",
|
||||
"ENDPOINT",
|
||||
"PLUGINS",
|
||||
"DEFAULTS",
|
||||
|
|
@ -54,7 +52,6 @@ Beau {
|
|||
"COOKIEJAR",
|
||||
],
|
||||
"defaultConfigValues": Object {
|
||||
"CACHE": false,
|
||||
"COOKIEJAR": false,
|
||||
"DEFAULTS": Object {},
|
||||
"ENDPOINT": "",
|
||||
|
|
@ -108,7 +105,6 @@ Beau {
|
|||
},
|
||||
},
|
||||
"config": Config {
|
||||
"CACHE": false,
|
||||
"COOKIEJAR": false,
|
||||
"DEFAULTS": Object {
|
||||
"headers": Object {
|
||||
|
|
@ -150,7 +146,6 @@ Beau {
|
|||
"VERSION": 1,
|
||||
"configKeys": Array [
|
||||
"VERSION",
|
||||
"CACHE",
|
||||
"ENDPOINT",
|
||||
"PLUGINS",
|
||||
"DEFAULTS",
|
||||
|
|
@ -159,7 +154,6 @@ Beau {
|
|||
"COOKIEJAR",
|
||||
],
|
||||
"defaultConfigValues": Object {
|
||||
"CACHE": false,
|
||||
"COOKIEJAR": false,
|
||||
"DEFAULTS": Object {},
|
||||
"ENDPOINT": "",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
exports[`Config should load multiple hosts 1`] = `
|
||||
Config {
|
||||
"CACHE": false,
|
||||
"COOKIEJAR": false,
|
||||
"DEFAULTS": Object {
|
||||
"HEADERS": Object {
|
||||
|
|
@ -113,7 +112,6 @@ Config {
|
|||
"VERSION": 1,
|
||||
"configKeys": Array [
|
||||
"VERSION",
|
||||
"CACHE",
|
||||
"ENDPOINT",
|
||||
"PLUGINS",
|
||||
"DEFAULTS",
|
||||
|
|
@ -122,7 +120,6 @@ Config {
|
|||
"COOKIEJAR",
|
||||
],
|
||||
"defaultConfigValues": Object {
|
||||
"CACHE": false,
|
||||
"COOKIEJAR": false,
|
||||
"DEFAULTS": Object {},
|
||||
"ENDPOINT": "",
|
||||
|
|
@ -176,7 +173,6 @@ Config {
|
|||
|
||||
exports[`Config should set up defaults for all requests 1`] = `
|
||||
Config {
|
||||
"CACHE": false,
|
||||
"COOKIEJAR": false,
|
||||
"DEFAULTS": Object {
|
||||
"HEADERS": Object {
|
||||
|
|
@ -218,7 +214,6 @@ Config {
|
|||
"VERSION": 1,
|
||||
"configKeys": Array [
|
||||
"VERSION",
|
||||
"CACHE",
|
||||
"ENDPOINT",
|
||||
"PLUGINS",
|
||||
"DEFAULTS",
|
||||
|
|
@ -227,7 +222,6 @@ Config {
|
|||
"COOKIEJAR",
|
||||
],
|
||||
"defaultConfigValues": Object {
|
||||
"CACHE": false,
|
||||
"COOKIEJAR": false,
|
||||
"DEFAULTS": Object {},
|
||||
"ENDPOINT": "",
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@ describe('Config', () => {
|
|||
const doc = yaml.safeLoad(`
|
||||
version: 1
|
||||
endpoint: http://martianwabbit.com
|
||||
cache: false
|
||||
shouldntBeAdded: true
|
||||
`);
|
||||
|
||||
const config = new Config(doc);
|
||||
expect(config.ENDPOINT).toBe(doc.endpoint);
|
||||
expect(config.CACHE).toBe(doc.cache);
|
||||
expect(config.VERSION).toBe(doc.version);
|
||||
expect(config.shouldntBeAdded).toBeUndefined();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ class Config {
|
|||
constructor(doc, env = {}) {
|
||||
this.defaultConfigValues = {
|
||||
VERSION: 1,
|
||||
CACHE: false,
|
||||
ENDPOINT: '',
|
||||
PLUGINS: [],
|
||||
DEFAULTS: {},
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class RequestCache {
|
|||
let result = this.$cache;
|
||||
path.split('.').forEach(part => {
|
||||
if (result[part] === undefined) {
|
||||
throw new Error(`${path} not found in cache: `, path);
|
||||
throw new Error(`${path} not found in cache.`);
|
||||
}
|
||||
|
||||
result = result[part];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
const Joi = require('joi');
|
||||
const { requestRegex } = require('./shared.js');
|
||||
|
||||
const pluginSchema = [
|
||||
Joi.string(),
|
||||
Joi.object()
|
||||
.keys(null)
|
||||
.max(1)
|
||||
];
|
||||
|
||||
const requestSchema = [
|
||||
Joi.object()
|
||||
.keys({
|
||||
HEADERS: Joi.object().keys(null),
|
||||
PAYLOAD: [Joi.object().keys(null), Joi.string()],
|
||||
PARAMS: Joi.object().keys(null),
|
||||
FORM: Joi.object().keys(null),
|
||||
ALIAS: Joi.string().required(),
|
||||
FORMDATA: Joi.object().keys(null)
|
||||
})
|
||||
.without('FORM', ['PAYLOAD', 'FORMDATA'])
|
||||
.without('PAYLOAD', ['FORM', 'FORMDATA'])
|
||||
.without('FORMDATA', ['FORM', 'PAYLOAD'])
|
||||
.rename(/headers/i, 'HEADERS', { override: true })
|
||||
.rename(/payload/i, 'PAYLOAD', { override: true })
|
||||
.rename(/params/i, 'PARAMS', { override: true })
|
||||
.rename(/form/i, 'FORM', { override: true })
|
||||
.rename(/alias/i, 'ALIAS', { override: true }),
|
||||
|
||||
Joi.string()
|
||||
];
|
||||
|
||||
const hostSchema = Joi.object()
|
||||
.keys({
|
||||
HOST: Joi.string().required(),
|
||||
ENDPOINT: Joi.string(),
|
||||
DEFAULTS: Joi.object().keys(null)
|
||||
})
|
||||
.pattern(requestRegex, requestSchema)
|
||||
.rename(/host/i, 'HOST', { override: true })
|
||||
.rename(/defaults/i, 'DEFAULTS', { override: true })
|
||||
.rename(/endpoint/i, 'ENDPOINT', { override: true });
|
||||
|
||||
const schema = Joi.object()
|
||||
.keys({
|
||||
VERSION: Joi.number().integer(),
|
||||
ENDPOINT: Joi.string().uri(),
|
||||
PLUGINS: Joi.array().items(pluginSchema),
|
||||
DEFAULTS: Joi.object(),
|
||||
ENVIRONMENT: Joi.object(),
|
||||
HOSTS: Joi.array().items(hostSchema),
|
||||
COOKIEJAR: Joi.boolean()
|
||||
})
|
||||
.pattern(requestRegex, requestSchema)
|
||||
.rename(/version/i, 'VERSION', { override: true })
|
||||
.rename(/endpoint/i, 'ENDPOINT', { override: true })
|
||||
.rename(/hosts/i, 'HOSTS', { override: true })
|
||||
.rename(/plugins/i, 'PLUGINS', { override: true })
|
||||
.rename(/defaults/i, 'DEFAULTS', { override: true })
|
||||
.rename(/environment/i, 'ENVIRONMENT', { override: true })
|
||||
.rename(/cookiejar/i, 'COOKIEJAR', { override: true });
|
||||
|
||||
const validate = async function(config) {
|
||||
try {
|
||||
let results = await Joi.validate(config, schema, {
|
||||
allowUnknown: true
|
||||
});
|
||||
return { valid: true };
|
||||
} catch ({ name, details }) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `${name}: \n ${details
|
||||
.map(d => d.message + ' @ ' + d.path)
|
||||
.join(' \n ')}`
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { schema, validate };
|
||||
Loading…
Reference in New Issue