aboutsummaryrefslogtreecommitdiffhomepage
path: root/lib/config/validation-helpers
diff options
context:
space:
mode:
Diffstat (limited to 'lib/config/validation-helpers')
-rw-r--r--lib/config/validation-helpers/utils.spec.ts17
-rw-r--r--lib/config/validation-helpers/utils.ts138
2 files changed, 155 insertions, 0 deletions
diff --git a/lib/config/validation-helpers/utils.spec.ts b/lib/config/validation-helpers/utils.spec.ts
new file mode 100644
index 00000000000..8344eed039e
--- /dev/null
+++ b/lib/config/validation-helpers/utils.spec.ts
@@ -0,0 +1,17 @@
+import { getParentName } from './utils';
+
+describe('config/validation-helpers/utils', () => {
+ describe('getParentName()', () => {
+ it('ignores encrypted in root', () => {
+ expect(getParentName('encrypted')).toBeEmptyString();
+ });
+
+ it('handles array types', () => {
+ expect(getParentName('hostRules[1]')).toBe('hostRules');
+ });
+
+ it('handles encrypted within array types', () => {
+ expect(getParentName('hostRules[0].encrypted')).toBe('hostRules');
+ });
+ });
+});
diff --git a/lib/config/validation-helpers/utils.ts b/lib/config/validation-helpers/utils.ts
new file mode 100644
index 00000000000..5d676bed324
--- /dev/null
+++ b/lib/config/validation-helpers/utils.ts
@@ -0,0 +1,138 @@
+import is from '@sindresorhus/is';
+import { logger } from '../../logger';
+import type {
+ RegexManagerConfig,
+ RegexManagerTemplates,
+} from '../../modules/manager/custom/regex/types';
+import { regEx } from '../../util/regex';
+import type { ValidationMessage } from '../types';
+
+export function getParentName(parentPath: string | undefined): string {
+ return parentPath
+ ? parentPath
+ .replace(regEx(/\.?encrypted$/), '')
+ .replace(regEx(/\[\d+\]$/), '')
+ .split('.')
+ .pop()!
+ : '.';
+}
+
+export function validatePlainObject(
+ val: Record<string, unknown>,
+): true | string {
+ for (const [key, value] of Object.entries(val)) {
+ if (!is.string(value)) {
+ return key;
+ }
+ }
+ return true;
+}
+
+export function validateNumber(
+ key: string,
+ val: unknown,
+ allowsNegative: boolean,
+ currentPath?: string,
+ subKey?: string,
+): ValidationMessage[] {
+ const errors: ValidationMessage[] = [];
+ const path = `${currentPath}${subKey ? '.' + subKey : ''}`;
+ if (is.number(val)) {
+ if (val < 0 && !allowsNegative) {
+ errors.push({
+ topic: 'Configuration Error',
+ message: `Configuration option \`${path}\` should be a positive integer. Found negative value instead.`,
+ });
+ }
+ } else {
+ errors.push({
+ topic: 'Configuration Error',
+ message: `Configuration option \`${path}\` should be an integer. Found: ${JSON.stringify(
+ val,
+ )} (${typeof val}).`,
+ });
+ }
+
+ return errors;
+}
+
+/** An option is a false global if it has the same name as a global only option
+ * but is actually just the field of a non global option or field an children of the non global option
+ * eg. token: it's global option used as the bot's token as well and
+ * also it can be the token used for a platform inside the hostRules configuration
+ */
+export function isFalseGlobal(
+ optionName: string,
+ parentPath?: string,
+): boolean {
+ if (parentPath?.includes('hostRules')) {
+ if (
+ optionName === 'token' ||
+ optionName === 'username' ||
+ optionName === 'password'
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function hasField(
+ customManager: Partial<RegexManagerConfig>,
+ field: string,
+): boolean {
+ const templateField = `${field}Template` as keyof RegexManagerTemplates;
+ return !!(
+ customManager[templateField] ??
+ customManager.matchStrings?.some((matchString) =>
+ matchString.includes(`(?<${field}>`),
+ )
+ );
+}
+
+export function validateRegexManagerFields(
+ customManager: Partial<RegexManagerConfig>,
+ currentPath: string,
+ errors: ValidationMessage[],
+): void {
+ if (is.nonEmptyArray(customManager.matchStrings)) {
+ for (const matchString of customManager.matchStrings) {
+ try {
+ regEx(matchString);
+ } catch (err) {
+ logger.debug(
+ { err },
+ 'customManager.matchStrings regEx validation error',
+ );
+ errors.push({
+ topic: 'Configuration Error',
+ message: `Invalid regExp for ${currentPath}: \`${matchString}\``,
+ });
+ }
+ }
+ } else {
+ errors.push({
+ topic: 'Configuration Error',
+ message: `Each Custom Manager must contain a non-empty matchStrings array`,
+ });
+ }
+
+ const mandatoryFields = ['currentValue', 'datasource'];
+ for (const field of mandatoryFields) {
+ if (!hasField(customManager, field)) {
+ errors.push({
+ topic: 'Configuration Error',
+ message: `Regex Managers must contain ${field}Template configuration or regex group named ${field}`,
+ });
+ }
+ }
+
+ const nameFields = ['depName', 'packageName'];
+ if (!nameFields.some((field) => hasField(customManager, field))) {
+ errors.push({
+ topic: 'Configuration Error',
+ message: `Regex Managers must contain depName or packageName regex groups or templates`,
+ });
+ }
+}