Automod schemas: factor out WithTrigger to fix typescript discarding half the types

This commit is contained in:
Rory& committed 2026-11-02 17:28:08 +01:00
1 parent 0d43fbc054
commit 107f503b7f
7 files changed
+37 -26

No files matched your search

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
@@ -107,6 +107,7 @@ public class AutomodTests(ITestOutputHelper testOutputHelper, TestFixture fixtur
automodRule.Name = Guid.NewGuid().ToString();
_testOutputHelper.WriteLine(automodRule.ToJson());
var r = await Assert.HttpSuccess(await Client.ApiHttpClient.PostAsJsonAsync(
$"guilds/{Guild.Id}/auto-moderation/rules",
automodRule,
+9 -1
View File
@@ -20,6 +20,7 @@ import { NextFunction, Request, Response } from "express";
import { HTTPError } from "lambert-server/HTTPError";
import { ApiError, FieldError, FieldErrors } from "@spacebar/util";
import { StringLengthOutOfBoundsException } from "@spacebar/extensions";
import { getInvalidSchemaValues } from "@spacebar/schemas";
const EntityNotFoundErrorRegex = /"(\w+)"/;
export function ErrorHandler(error: Error & { type?: string }, req: Request, res: Response, next: NextFunction) {
@@ -75,7 +76,14 @@ export function ErrorHandler(error: Error & { type?: string }, req: Request, res
if (httpcode > 511) httpcode = 400;
res.status(httpcode).json({ code, message, errors, _ajvErrors, request: `${req.method} ${req.url}` });
res.status(httpcode).json({
code,
message,
errors,
_ajvErrors,
_ajvResolved: _ajvErrors == undefined ? undefined : getInvalidSchemaValues(req.body, _ajvErrors),
request: `${req.method} ${req.url}`,
});
} catch (error) {
console.error(`[Internal Server Error] 500`, error);
return res.status(500).json({ code: 500, message: `Internal server error while handling error`, request: `${req.method} ${req.url}` });
+4 -1
View File
@@ -177,7 +177,10 @@ export function route(opts: RouteOptions) {
message: x.message || "",
}),
);
if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors);
if (process.env.LOG_VALIDATION_ERRORS) {
console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors);
if (process.env.LOG_VALIDATION_ERROR_SCHEMAS) console.log(validate.schema);
}
throw FieldErrors(fields, validate.errors!);
}
}
+16 -11
View File
@@ -52,7 +52,7 @@ export const ajv = (function () {
strict: true,
strictRequired: true,
allowUnionTypes: true,
discriminator: true
discriminator: true,
});
addFormats(_ajv);
@@ -70,23 +70,28 @@ export function validateSchema<G extends object>(schema: string, data: G): G {
return data;
}
export function getInvalidSchemaValues(data: object, errors: typeof ajv.errors) {
export function getInvalidSchemaValues(data: object, errors: typeof ajv.errors): { instancePath: string; value: unknown; keyword: string }[] {
if (errors == null) return [];
return errors.map((e) => {
let instancePath = e.instancePath;
if (e.keyword == "additionalProperties") instancePath += `/${e.params.additionalProperty}`;
return errors
.map((e) => {
let instancePath = e.instancePath;
if (e.keyword == "additionalProperties") instancePath += `/${e.params.additionalProperty}`;
else if (e.keyword == "required") return { instancePath: instancePath + `/${e.params.missingProperty}`, value: "<<undefined>>", keyword: e.keyword };
else if (e.keyword == "anyOf") return { instancePath: instancePath, value: "<<irrelevant>>", keyword: e.keyword };
console.log("Resolving:", { instancePath, eInstancePath: e.instancePath, eKeyword: e.keyword, eParams: e.params });
return getInvalidSchemaValue(data, instancePath);
});
console.log("Resolving:", { instancePath, eInstancePath: e.instancePath, eKeyword: e.keyword, eParams: e.params });
return { ...getInvalidSchemaValue(data, instancePath), keyword: e.keyword };
})
.filter((x) => x != undefined);
}
function getInvalidSchemaValue(data: object, instancePath: string) {
function getInvalidSchemaValue(data: object, instancePath: string): { instancePath: string; value: unknown } {
const actualInstancePath = instancePath.startsWith("/") ? instancePath.slice(1) : instancePath;
if (actualInstancePath.includes("/")) {
if (actualInstancePath.trim().length > 0) {
// includes("/")
const nextProp = actualInstancePath.split("/")[0];
const nextPath = actualInstancePath.split("/").slice(1).join("/");
return getInvalidSchemaValue((<Record<string, object>>data)[nextProp], nextPath);
return { instancePath, value: getInvalidSchemaValue((<Record<string, object>>data)[nextProp], nextPath).value };
}
return { instancePath, value: data };
}
+7 -13
View File
@@ -27,19 +27,13 @@ interface BaseAutomodRuleSchema {
actions: AutomodAction[];
}
type WithTrigger<T, M> = BaseAutomodRuleSchema & {
trigger_type: T;
} & M extends never
? { trigger_metadata?: never }
: { trigger_metadata: M };
type KeywordRule = WithTrigger<AutomodRuleTriggerType.KEYWORD, AutomodCustomWordsRuleMetadata>;
type HarmfulLinkRule = WithTrigger<AutomodRuleTriggerType.HARMFUL_LINK, never>;
type SpamRule = WithTrigger<AutomodRuleTriggerType.SPAM, never>;
type KeywordPresetRule = WithTrigger<AutomodRuleTriggerType.KEYWORD_PRESET, AutomodKeywordPresetRuleMetadata>;
type MentionSpamRule = WithTrigger<AutomodRuleTriggerType.MENTION_SPAM, AutomodMentionSpamRuleMetadata>;
type UserProfileRule = WithTrigger<AutomodRuleTriggerType.USER_PROFILE, AutomodUserProfileRuleMetadata>;
type GuildPolicyRule = WithTrigger<AutomodRuleTriggerType.GUILD_POLICY, never>;
type KeywordRule = BaseAutomodRuleSchema & { trigger_type: AutomodRuleTriggerType.KEYWORD; trigger_metadata: AutomodCustomWordsRuleMetadata };
type KeywordPresetRule = BaseAutomodRuleSchema & { trigger_type: AutomodRuleTriggerType.KEYWORD_PRESET; trigger_metadata: AutomodKeywordPresetRuleMetadata };
type MentionSpamRule = BaseAutomodRuleSchema & { trigger_type: AutomodRuleTriggerType.MENTION_SPAM; trigger_metadata: AutomodMentionSpamRuleMetadata };
type UserProfileRule = BaseAutomodRuleSchema & { trigger_type: AutomodRuleTriggerType.USER_PROFILE; trigger_metadata: AutomodUserProfileRuleMetadata };
type HarmfulLinkRule = BaseAutomodRuleSchema & { trigger_type: AutomodRuleTriggerType.HARMFUL_LINK; trigger_metadata?: never };
type GuildPolicyRule = BaseAutomodRuleSchema & { trigger_type: AutomodRuleTriggerType.GUILD_POLICY; trigger_metadata?: never };
type SpamRule = BaseAutomodRuleSchema & { trigger_type: AutomodRuleTriggerType.SPAM; trigger_metadata?: never };
export type AutomodRuleSchema = KeywordRule | HarmfulLinkRule | SpamRule | KeywordPresetRule | MentionSpamRule | UserProfileRule | GuildPolicyRule;