Skip to content

Commit

Permalink
chore: refactored jobValidate method into separate module
Browse files Browse the repository at this point in the history
  • Loading branch information
naz committed Nov 26, 2020
1 parent 475a0e7 commit 931d2c8
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 283 deletions.
292 changes: 9 additions & 283 deletions src/index.js
@@ -1,10 +1,8 @@
const EventEmitter = require('events');
const fs = require('fs');
const { resolve, join } = require('path');

const { resolve } = require('path');
const pWaitFor = require('p-wait-for');
const combineErrors = require('combine-errors');
const cron = require('cron-validate');
const debug = require('debug')('bree');
const isSANB = require('is-string-and-not-blank');
const isValidPath = require('is-valid-path');
Expand All @@ -20,6 +18,7 @@ const {
getJobNames
} = require('./job-utils');
const buildJob = require('./job-builder');
const validateJob = require('./job-validator');

// bthreads requires us to do this for web workers (see bthreads docs for insight)
threads.Buffer = Buffer;
Expand Down Expand Up @@ -106,7 +105,6 @@ class Bree extends EventEmitter {
this.timeouts = {};
this.intervals = {};

this.validateJob = this.validateJob.bind(this);
this.isSchedule = isSchedule;
this.getWorkerMetadata = this.getWorkerMetadata.bind(this);
this.run = this.run.bind(this);
Expand All @@ -115,6 +113,7 @@ class Bree extends EventEmitter {
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);

this.validateJob = validateJob;
this.getName = getName;
this.getHumanToMs = getHumanToMs;
this.parseValue = parseValue;
Expand Down Expand Up @@ -184,7 +183,9 @@ class Bree extends EventEmitter {
try {
const names = getJobNames(this.config.jobs, i);

this.config.jobs[i] = this.validateJob(this.config.jobs[i], i, names);
validateJob(this.config.jobs[i], i, names, this.config);

this.config.jobs[i] = buildJob(this.config.jobs[i], this.config);
} catch (err) {
errors.push(err);
}
Expand All @@ -196,283 +197,6 @@ class Bree extends EventEmitter {
debug('this.config.jobs', this.config.jobs);
}

// eslint-disable-next-line complexity
validateJob(job, i, names = []) {
const errors = [];

const name = this.getName(job);
if (!name) errors.push(new Error(`Job #${i + 1} is missing a name`));

// throw an error if duplicate job names
if (names.includes(name)) {
errors.push(
new Error(
`Job #${i + 1} has a duplicate job name of ${this.getName(job)}`
)
);
}

if (errors.length > 0) throw combineErrors(errors);

// support a simple string which we will transform to have a path
if (isSANB(job)) {
// don't allow a job to have the `index` file name
if (['index', 'index.js', 'index.mjs'].includes(job))
throw new Error(
'You cannot use the reserved job name of "index", "index.js", nor "index.mjs"'
);

if (!this.config.root) {
errors.push(
new Error(
`Job #${
i + 1
} "${job}" requires root directory option to auto-populate path`
)
);
throw combineErrors(errors);
}

const path = join(
this.config.root,
job.endsWith('.js') || job.endsWith('.mjs')
? job
: `${job}.${this.config.defaultExtension}`
);

/* istanbul ignore next */
if (!threads.browser) {
const stats = fs.statSync(path);
if (!stats.isFile())
throw new Error(`Job #${i + 1} "${job}" path missing: ${path}`);
}

return buildJob(job, this.config);
}

// job is a function
if (typeof job === 'function') {
const path = `(${job.toString()})()`;
// can't be a built-in or bound function
if (path.includes('[native code]'))
errors.push(
new Error(`Job #${i + 1} can't be a bound or built-in function`)
);

if (errors.length > 0) throw combineErrors(errors);

return buildJob(job, this.config);
}

// use a prefix for errors
const prefix = `Job #${i + 1} named "${job.name}"`;

if (typeof job.path === 'function') {
const path = `(${job.path.toString()})()`;

// can't be a built-in or bound function
if (path.includes('[native code]'))
errors.push(
new Error(`Job #${i + 1} can't be a bound or built-in function`)
);
} else if (!isSANB(job.path) && !this.config.root) {
errors.push(
new Error(
`${prefix} requires root directory option to auto-populate path`
)
);
} else {
// validate path
const path = isSANB(job.path)
? job.path
: join(
this.config.root,
job.name.endsWith('.js') || job.name.endsWith('.mjs')
? job.name
: `${job.name}.${this.config.defaultExtension}`
);
if (isValidPath(path)) {
try {
/* istanbul ignore next */
if (!threads.browser) {
const stats = fs.statSync(path);
// eslint-disable-next-line max-depth
if (!stats.isFile())
throw new Error(`${prefix} path missing: ${path}`);
}
} catch (err) {
errors.push(err);
}
}
}

// don't allow users to mix interval AND cron
if (
typeof job.interval !== 'undefined' &&
typeof job.cron !== 'undefined'
) {
errors.push(
new Error(`${prefix} cannot have both interval and cron configuration`)
);
}

// don't allow users to mix timeout AND date
if (typeof job.timeout !== 'undefined' && typeof job.date !== 'undefined')
errors.push(new Error(`${prefix} cannot have both timeout and date`));

// don't allow a job to have the `index` file name
if (['index', 'index.js', 'index.mjs'].includes(job.name)) {
errors.push(
new Error(
'You cannot use the reserved job name of "index", "index.js", nor "index.mjs"'
)
);

throw combineErrors(errors);
}

// validate date
if (typeof job.date !== 'undefined' && !(job.date instanceof Date))
errors.push(new Error(`${prefix} had an invalid Date of ${job.date}`));

// validate timeout
if (typeof job.timeout !== 'undefined') {
try {
this.parseValue(job.timeout);
} catch (err) {
errors.push(
combineErrors([
new Error(`${prefix} had an invalid timeout of ${job.timeout}`),
err
])
);
}
}

// validate interval
if (typeof job.interval !== 'undefined') {
try {
this.parseValue(job.interval);
} catch (err) {
errors.push(
combineErrors([
new Error(`${prefix} had an invalid interval of ${job.interval}`),
err
])
);
}
}

// validate hasSeconds
if (
typeof job.hasSeconds !== 'undefined' &&
typeof job.hasSeconds !== 'boolean'
)
errors.push(
new Error(
`${prefix} had hasSeconds value of ${job.hasSeconds} (it must be a Boolean)`
)
);

// validate cronValidate
if (
typeof job.cronValidate !== 'undefined' &&
typeof job.cronValidate !== 'object'
)
errors.push(
new Error(
`${prefix} had cronValidate value set, but it must be an Object`
)
);

// if `hasSeconds` was `true` then set `cronValidate` and inherit any existing options
if (job.hasSeconds) {
const preset =
job.cronValidate && job.cronValidate.preset
? job.cronValidate.preset
: this.config.cronValidate && this.config.cronValidate.preset
? this.config.cronValidate.preset
: 'default';
const override = {
...(this.config.cronValidate && this.config.cronValidate.override
? this.config.cronValidate.override
: {}),
...(job.cronValidate && job.cronValidate.override
? job.cronValidate.override
: {}),
useSeconds: true
};
job.cronValidate = {
...this.config.cronValidate,
...job.cronValidate,
preset,
override
};
}

// validate cron
if (typeof job.cron !== 'undefined') {
if (!this.isSchedule(job.cron)) {
//
// validate cron pattern
// (must support patterns such as `* * L * *` and `0 0/5 14 * * ?` (and aliases too)
//
// <https://github.com/Airfooox/cron-validate/issues/67>
//
const result = cron(
job.cron,
typeof job.cronValidate === 'undefined'
? this.config.cronValidate
: job.cronValidate
);

if (!result.isValid()) {
// NOTE: it is always valid
// const schedule = later.schedule(
// later.parse.cron(
// job.cron,
// boolean(
// typeof job.hasSeconds === 'undefined'
// ? this.config.hasSeconds
// : job.hasSeconds
// )
// )
// );
// if (schedule.isValid()) {
// job.interval = schedule;
// } // else {
// errors.push(
// new Error(
// `${prefix} had an invalid cron schedule (see <https://crontab.guru> if you need help)`
// )
// );
// }

for (const message of result.getError()) {
errors.push(
new Error(`${prefix} had an invalid cron pattern: ${message}`)
);
}
}
}
}

// validate closeWorkerAfterMs
if (
typeof job.closeWorkerAfterMs !== 'undefined' &&
(!Number.isFinite(job.closeWorkerAfterMs) || job.closeWorkerAfterMs <= 0)
)
errors.push(
new Error(
`${prefix} had an invalid closeWorkersAfterMs value of ${job.closeWorkersAfterMs} (it must be a finite number > 0)`
)
);

if (errors.length > 0) throw combineErrors(errors);

return buildJob(job, this.config);
}

getWorkerMetadata(name, meta = {}) {
const job = this.config.jobs.find((j) => j.name === name);
if (!job) throw new Error(`Job "${name}" does not exist`);
Expand Down Expand Up @@ -756,7 +480,9 @@ class Bree extends EventEmitter {
...getJobNames(this.config.jobs)
];

const job = this.validateJob(job_, i, names);
validateJob(job_, i, names, this.config);
const job = buildJob(job_, this.config);

this.config.jobs.push(job);
} catch (err) {
errors.push(err);
Expand Down

0 comments on commit 931d2c8

Please sign in to comment.