Fixes and tweaks to new config validation

cshd
Dragory 2020-08-13 00:03:01 +03:00
parent f7b8a312f9
commit bd8dcc6129
No known key found for this signature in database
GPG Key ID: 5F387BA66DF8AAC1
4 changed files with 132 additions and 55 deletions

View File

@ -102,16 +102,6 @@ for (const [key, value] of Object.entries(config)) {
delete config[key];
}
// Cast boolean options (on, true, 1) (off, false, 0)
for (const [key, value] of Object.entries(config)) {
if (typeof value !== "string") continue;
if (["on", "true", "1"].includes(value)) {
config[key] = true;
} else if (["off", "false", "0"].includes(value)) {
config[key] = false;
}
}
if (! config['knex']) {
config.knex = {
client: 'sqlite',
@ -129,20 +119,6 @@ Object.assign(config['knex'], {
}
});
// Make sure mainGuildId is internally always an array
if (! Array.isArray(config['mainGuildId'])) {
config['mainGuildId'] = [config['mainGuildId']];
}
// Make sure inboxServerPermission is always an array
if (! Array.isArray(config['inboxServerPermission'])) {
if (config['inboxServerPermission'] == null) {
config['inboxServerPermission'] = [];
} else {
config['inboxServerPermission'] = [config['inboxServerPermission']];
}
}
// Move greetingMessage/greetingAttachment to the guildGreetings object internally
// Or, in other words, if greetingMessage and/or greetingAttachment is set, it is applied for all servers that don't
// already have something set up in guildGreetings. This retains backwards compatibility while allowing you to override
@ -163,15 +139,71 @@ if (config.newThreadCategoryId) {
delete config.newThreadCategoryId;
}
// Turn empty string options to null (i.e. "option=" without a value in config.ini)
// Delete empty string options (i.e. "option=" without a value in config.ini)
for (const [key, value] of Object.entries(config)) {
if (value === '') {
config[key] = null;
delete config[key];
}
}
// Validate config and assign defaults (if missing)
const ajv = new Ajv({ useDefaults: true, coerceTypes: "array" });
// https://github.com/ajv-validator/ajv/issues/141#issuecomment-270692820
const truthyValues = ["1", "true", "on"];
const falsyValues = ["0", "false", "off"];
ajv.addKeyword('coerceBoolean', {
compile(value) {
return (data, dataPath, parentData, parentKey) => {
if (! value) {
// Disabled -> no coercion
return true;
}
// https://github.com/ajv-validator/ajv/issues/141#issuecomment-270777250
// The "data" argument doesn't update within the same set of schemas inside "allOf",
// so we're referring to the original property instead.
// This also means we can't use { "type": "boolean" }, as it would test the un-updated data value.
const realData = parentData[parentKey];
if (typeof realData === "boolean") {
return true;
}
if (truthyValues.includes(realData)) {
parentData[parentKey] = true;
} else if (falsyValues.includes(realData)) {
parentData[parentKey] = false;
} else {
return false;
}
return true;
};
},
});
ajv.addKeyword('multilineString', {
compile(value) {
return (data, dataPath, parentData, parentKey) => {
if (! value) {
// Disabled -> no coercion
return true;
}
const realData = parentData[parentKey];
if (typeof realData === "string") {
return true;
}
parentData[parentKey] = realData.join("\n");
return true;
};
},
});
const validate = ajv.compile(schema);
const configIsValid = validate(config);
@ -186,5 +218,6 @@ if (! configIsValid) {
}
console.log("Configuration ok!");
process.exit(0);
module.exports = config;

View File

@ -1,21 +1,21 @@
/**
* @typedef {object} ModmailConfig
* @property {string} [token]
* @property {*} [mainGuildId]
* @property {array} [mainGuildId]
* @property {string} [mailGuildId]
* @property {string} [logChannelId]
* @property {string} [prefix="!"]
* @property {string} [snippetPrefix="!!"]
* @property {string} [snippetPrefixAnon="!!!"]
* @property {string} [status="Message me for help!"]
* @property {*} [responseMessage="Thank you for your message! Our mod team will reply to you here as soon as possible."]
* @property {*} [closeMessage]
* @property {string} [responseMessage="Thank you for your message! Our mod team will reply to you here as soon as possible."]
* @property {string} [closeMessage]
* @property {boolean} [allowUserClose=false]
* @property {string} [newThreadCategoryId]
* @property {string} [mentionRole="here"]
* @property {boolean} [pingOnBotMention=true]
* @property {*} [botMentionResponse]
* @property {*} [inboxServerPermission]
* @property {string} [botMentionResponse]
* @property {array} [inboxServerPermission]
* @property {boolean} [alwaysReply=false]
* @property {boolean} [alwaysReplyAnon=false]
* @property {boolean} [useNicknames=false]
@ -30,18 +30,18 @@
* @property {boolean} [allowStaffEdit=false]
* @property {boolean} [allowStaffDelete=false]
* @property {boolean} [enableGreeting=false]
* @property {*} [greetingMessage]
* @property {string} [greetingMessage]
* @property {string} [greetingAttachment]
* @property {*} [guildGreetings={}]
* @property {number} [requiredAccountAge] Required account age to message Modmail, in hours
* @property {*} [accountAgeDeniedMessage="Your Discord account is not old enough to contact modmail."]
* @property {string} [accountAgeDeniedMessage="Your Discord account is not old enough to contact modmail."]
* @property {number} [requiredTimeOnServer] Required time on server to message Modmail, in minutes
* @property {*} [timeOnServerDeniedMessage="You haven't been a member of the server for long enough to contact modmail."]
* @property {string} [timeOnServerDeniedMessage="You haven't been a member of the server for long enough to contact modmail."]
* @property {boolean} [relaySmallAttachmentsAsAttachments=false]
* @property {number} [smallAttachmentLimit=2097152] Max size of attachment to relay directly. Default is 2MB.
* @property {string} [attachmentStorage="local"]
* @property {string} [attachmentStorageChannelId]
* @property {*} [categoryAutomation]
* @property {*} [categoryAutomation={}]
* @property {boolean} [updateNotifications=true]
* @property {array} [plugins=[]]
* @property {*} [commandAliases]

View File

@ -10,7 +10,36 @@
}
},
"multilineString": {
"$ref": "#/definitions/stringArray"
"allOf": [
{
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
{
"$comment": "See definition of multilineString in cfg.js",
"multilineString": true
}
]
},
"customBoolean": {
"allOf": [
{
"type": ["boolean", "string"]
},
{
"$comment": "See definition of coerceBoolean in cfg.js",
"coerceBoolean": true
}
]
}
},
"properties": {
@ -51,7 +80,7 @@
"$ref": "#/definitions/multilineString"
},
"allowUserClose": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
@ -63,7 +92,7 @@
"default": "here"
},
"pingOnBotMention": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": true
},
"botMentionResponse": {
@ -74,60 +103,60 @@
"$ref": "#/definitions/stringArray"
},
"alwaysReply": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"alwaysReplyAnon": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"useNicknames": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"ignoreAccidentalThreads": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"threadTimestamps": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"allowMove": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"syncPermissionsOnMove": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": true
},
"typingProxy": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"typingProxyReverse": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"mentionUserInThreadHeader": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"rolesInThreadHeader": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"allowStaffEdit": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"allowStaffDelete": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"enableGreeting": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"greetingMessage": {
@ -164,7 +193,7 @@
},
"relaySmallAttachmentsAsAttachments": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": false
},
"smallAttachmentLimit": {
@ -194,7 +223,7 @@
},
"updateNotifications": {
"type": "boolean",
"$ref": "#/definitions/customBoolean",
"default": true
},
"plugins": {

View File

@ -4,5 +4,20 @@ const toJsdoc = require('json-schema-to-jsdoc');
const schema = require('./cfg.schema.json');
const target = path.join(__dirname, 'cfg.jsdoc.js');
const result = toJsdoc(schema);
// Fix up some custom types for the JSDoc conversion
const schemaCopy = JSON.parse(JSON.stringify(schema));
for (const propertyDef of Object.values(schemaCopy.properties)) {
if (propertyDef.$ref === "#/definitions/stringArray") {
propertyDef.type = "array";
delete propertyDef.$ref;
} else if (propertyDef.$ref === "#/definitions/customBoolean") {
propertyDef.type = "boolean";
delete propertyDef.$ref;
} else if (propertyDef.$ref === "#/definitions/multilineString") {
propertyDef.type = "string";
delete propertyDef.$ref;
}
}
const result = toJsdoc(schemaCopy);
fs.writeFileSync(target, result, { encoding: 'utf8' });