Skip to content

Commit

Permalink
Merge pull request #450 from ZeppelinBot/iots_to_zod
Browse files Browse the repository at this point in the history
io-ts -> zod
  • Loading branch information
Dragory authored Jan 27, 2024
2 parents cfadfe2 + 873bf7e commit d70a56a
Show file tree
Hide file tree
Showing 200 changed files with 2,931 additions and 4,398 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,6 @@ npm-audit.txt
*.debug.js

.vscode/

config-errors.txt
/config-schema.json
3,124 changes: 1,052 additions & 2,072 deletions backend/package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@zeppelin/backend",
"name": "@zeppelinbot/backend",
"version": "0.0.1",
"description": "",
"private": true,
Expand All @@ -24,6 +24,8 @@
"migrate-rollback": "npm run typeorm -- migration:revert -d dist/backend/src/data/dataSource.js",
"migrate-rollback-prod": "cross-env NODE_ENV=production npm run migrate",
"migrate-rollback-dev": "cross-env NODE_ENV=development npm run build && npm run migrate",
"validate-active-configs": "cross-env NODE_ENV=development node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --enable-source-maps dist/backend/src/validateActiveConfigs.js > ../config-errors.txt",
"export-config-json-schema": "cross-env NODE_ENV=development node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --enable-source-maps dist/backend/src/exportSchemas.js > ../config-schema.json",
"test": "npm run build && npm run run-tests",
"run-tests": "ava",
"test-watch": "tsc-watch --onSuccess \"npx ava\""
Expand All @@ -42,7 +44,6 @@
"express": "^4.17.0",
"fp-ts": "^2.0.1",
"humanize-duration": "^3.15.0",
"io-ts": "^2.0.0",
"js-yaml": "^3.13.1",
"knub": "^32.0.0-next.16",
"knub-command-manager": "^9.1.0",
Expand Down Expand Up @@ -96,7 +97,8 @@
"@types/uuid": "^9.0.2",
"ava": "^5.3.1",
"rimraf": "^2.6.2",
"source-map-support": "^0.5.16"
"source-map-support": "^0.5.16",
"zod-to-json-schema": "^3.22.3"
},
"ava": {
"files": [
Expand Down
107 changes: 86 additions & 21 deletions backend/src/api/docs.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,99 @@
import express from "express";
import z from "zod";
import { guildPlugins } from "../plugins/availablePlugins";
import { indentLines } from "../utils";
import { notFound } from "./responses";

function formatConfigSchema(schema) {
if (schema._tag === "InterfaceType" || schema._tag === "PartialType") {
function isZodObject(schema: z.ZodTypeAny): schema is z.ZodObject<any> {
return schema._def.typeName === "ZodObject";
}

function isZodRecord(schema: z.ZodTypeAny): schema is z.ZodRecord<any> {
return schema._def.typeName === "ZodRecord";
}

function isZodEffects(schema: z.ZodTypeAny): schema is z.ZodEffects<any, any> {
return schema._def.typeName === "ZodEffects";
}

function isZodOptional(schema: z.ZodTypeAny): schema is z.ZodOptional<any> {
return schema._def.typeName === "ZodOptional";
}

function isZodArray(schema: z.ZodTypeAny): schema is z.ZodArray<any> {
return schema._def.typeName === "ZodArray";
}

function isZodUnion(schema: z.ZodTypeAny): schema is z.ZodUnion<any> {
return schema._def.typeName === "ZodUnion";
}

function isZodNullable(schema: z.ZodTypeAny): schema is z.ZodNullable<any> {
return schema._def.typeName === "ZodNullable";
}

function isZodDefault(schema: z.ZodTypeAny): schema is z.ZodDefault<any> {
return schema._def.typeName === "ZodDefault";
}

function isZodLiteral(schema: z.ZodTypeAny): schema is z.ZodLiteral<any> {
return schema._def.typeName === "ZodLiteral";
}

function isZodIntersection(schema: z.ZodTypeAny): schema is z.ZodIntersection<any, any> {
return schema._def.typeName === "ZodIntersection";
}

function formatZodConfigSchema(schema: z.ZodTypeAny) {
if (isZodObject(schema)) {
return (
`{\n` +
Object.entries(schema.props)
.map(([k, value]) => indentLines(`${k}: ${formatConfigSchema(value)}`, 2))
Object.entries(schema._def.shape())
.map(([k, value]) => indentLines(`${k}: ${formatZodConfigSchema(value as z.ZodTypeAny)}`, 2))
.join("\n") +
"\n}"
);
} else if (schema._tag === "DictionaryType") {
return "{\n" + indentLines(`[string]: ${formatConfigSchema(schema.codomain)}`, 2) + "\n}";
} else if (schema._tag === "ArrayType") {
return `Array<${formatConfigSchema(schema.type)}>`;
} else if (schema._tag === "UnionType") {
if (schema.name.startsWith("Nullable<")) {
return `Nullable<${formatConfigSchema(schema.types[0])}>`;
} else if (schema.name.startsWith("Optional<")) {
return `Optional<${formatConfigSchema(schema.types[0])}>`;
} else {
return schema.types.map((t) => formatConfigSchema(t)).join(" | ");
}
} else if (schema._tag === "IntersectionType") {
return schema.types.map((t) => formatConfigSchema(t)).join(" & ");
} else {
return schema.name;
}
if (isZodRecord(schema)) {
return "{\n" + indentLines(`[string]: ${formatZodConfigSchema(schema._def.valueType)}`, 2) + "\n}";
}
if (isZodEffects(schema)) {
return formatZodConfigSchema(schema._def.schema);
}
if (isZodOptional(schema)) {
return `Optional<${formatZodConfigSchema(schema._def.innerType)}>`;
}
if (isZodArray(schema)) {
return `Array<${formatZodConfigSchema(schema._def.type)}>`;
}
if (isZodUnion(schema)) {
return schema._def.options.map((t) => formatZodConfigSchema(t)).join(" | ");
}
if (isZodNullable(schema)) {
return `Nullable<${formatZodConfigSchema(schema._def.innerType)}>`;
}
if (isZodDefault(schema)) {
return formatZodConfigSchema(schema._def.innerType);
}
if (isZodLiteral(schema)) {
return schema._def.value;
}
if (isZodIntersection(schema)) {
return [formatZodConfigSchema(schema._def.left), formatZodConfigSchema(schema._def.right)].join(" & ");
}
if (schema._def.typeName === "ZodString") {
return "string";
}
if (schema._def.typeName === "ZodNumber") {
return "number";
}
if (schema._def.typeName === "ZodBoolean") {
return "boolean";
}
if (schema._def.typeName === "ZodNever") {
return "never";
}
return "unknown";
}

export function initDocs(app: express.Express) {
Expand Down Expand Up @@ -67,7 +132,7 @@ export function initDocs(app: express.Express) {
}));

const defaultOptions = plugin.defaultOptions || {};
const configSchema = plugin.info?.configSchema && formatConfigSchema(plugin.info.configSchema);
const configSchema = plugin.info?.configSchema && formatZodConfigSchema(plugin.info.configSchema);

res.json({
name,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/commandTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { createTypeHelper } from "knub-command-manager";
import {
channelMentionRegex,
convertDelayStringToMS,
inputPatternToRegExp,
isValidSnowflake,
resolveMember,
resolveUser,
Expand All @@ -26,7 +27,6 @@ import {
} from "./utils";
import { isValidTimezone } from "./utils/isValidTimezone";
import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget";
import { inputPatternToRegExp } from "./validatorUtils";

export const commandTypes = {
...messageCommandBaseTypeConverters,
Expand Down
16 changes: 11 additions & 5 deletions backend/src/configValidator.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { ConfigValidationError, PluginConfigManager } from "knub";
import moment from "moment-timezone";
import { ZodError } from "zod";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
import { guildPlugins } from "./plugins/availablePlugins";
import { PartialZeppelinGuildConfigSchema, ZeppelinGuildConfig } from "./types";
import { StrictValidationError, decodeAndValidateStrict } from "./validatorUtils";
import { ZeppelinGuildConfig, zZeppelinGuildConfig } from "./types";
import { formatZodIssue } from "./utils/formatZodIssue";

const pluginNameToPlugin = new Map<string, ZeppelinPlugin>();
for (const plugin of guildPlugins) {
pluginNameToPlugin.set(plugin.name, plugin);
}

export async function validateGuildConfig(config: any): Promise<string | null> {
const validationResult = decodeAndValidateStrict(PartialZeppelinGuildConfigSchema, config);
if (validationResult instanceof StrictValidationError) return validationResult.getErrors();
const validationResult = zZeppelinGuildConfig.safeParse(config);
if (!validationResult.success) {
return validationResult.error.issues.map(formatZodIssue).join("\n");
}

const guildConfig = config as ZeppelinGuildConfig;

Expand Down Expand Up @@ -41,7 +44,10 @@ export async function validateGuildConfig(config: any): Promise<string | null> {
try {
await configManager.init();
} catch (err) {
if (err instanceof ConfigValidationError || err instanceof StrictValidationError) {
if (err instanceof ZodError) {
return `${pluginName}: ${err.issues.map(formatZodIssue).join("\n")}`;
}
if (err instanceof ConfigValidationError) {
return `${pluginName}: ${err.message}`;
}

Expand Down
6 changes: 6 additions & 0 deletions backend/src/data/Configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export class Configs extends BaseRepository {
this.configs = dataSource.getRepository(Config);
}

getActive() {
return this.configs.find({
where: { is_active: true },
});
}

getActiveByKey(key) {
return this.configs.findOne({
where: {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/data/GuildArchives.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Guild, Snowflake } from "discord.js";
import moment from "moment-timezone";
import { isDefaultSticker } from "src/utils/isDefaultSticker";
import { Repository } from "typeorm";
import { TemplateSafeValueContainer, renderTemplate } from "../templateFormatter";
import { renderUsername, trimLines } from "../utils";
import { decrypt, encrypt } from "../utils/crypt";
import { isDefaultSticker } from "../utils/isDefaultSticker";
import { channelToTemplateSafeChannel, guildToTemplateSafeGuild } from "../utils/templateSafeObjects";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { dataSource } from "./dataSource";
Expand Down
10 changes: 5 additions & 5 deletions backend/src/data/GuildLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LogType } from "./LogType";
const guildInstances: Map<string, GuildLogs> = new Map();

interface IIgnoredLog {
type: LogType;
type: keyof typeof LogType;
ignoreId: any;
}

Expand All @@ -27,7 +27,7 @@ export class GuildLogs extends events.EventEmitter {
guildInstances.set(guildId, this);
}

log(type: LogType, data: any, ignoreId?: string) {
log(type: keyof typeof LogType, data: any, ignoreId?: string) {
if (ignoreId && this.isLogIgnored(type, ignoreId)) {
this.clearIgnoredLog(type, ignoreId);
return;
Expand All @@ -36,7 +36,7 @@ export class GuildLogs extends events.EventEmitter {
this.emit("log", { type, data });
}

ignoreLog(type: LogType, ignoreId: any, timeout?: number) {
ignoreLog(type: keyof typeof LogType, ignoreId: any, timeout?: number) {
this.ignoredLogs.push({ type, ignoreId });

// Clear after expiry (15sec by default)
Expand All @@ -45,11 +45,11 @@ export class GuildLogs extends events.EventEmitter {
}, timeout || 1000 * 15);
}

isLogIgnored(type: LogType, ignoreId: any) {
isLogIgnored(type: keyof typeof LogType, ignoreId: any) {
return this.ignoredLogs.some((info) => type === info.type && ignoreId === info.ignoreId);
}

clearIgnoredLog(type: LogType, ignoreId: any) {
clearIgnoredLog(type: keyof typeof LogType, ignoreId: any) {
this.ignoredLogs.splice(
this.ignoredLogs.findIndex((info) => type === info.type && ignoreId === info.ignoreId),
1,
Expand Down
Loading

0 comments on commit d70a56a

Please sign in to comment.