From 107f503b7f0c782f162d2c106bc714c812494680 Mon Sep 17 00:00:00 2001 From: Rory& Date: Mon, 2 Nov 2026 17:28:08 +0100 Subject: [PATCH] Automod schemas: factor out WithTrigger to fix typescript discarding half the types --- assets/openapi.json | Bin 1134801 -> 1145399 bytes assets/schemas.json | Bin 566269 -> 575356 bytes .../Spacebar.Tests/Tests/AutomodTests.cs | 1 + src/api/middlewares/ErrorHandler.ts | 10 ++++++- src/api/middlewares/Route.ts | 5 +++- src/schemas/Validator.ts | 27 +++++++++++------- src/schemas/api/guilds/Automod.ts | 20 +++++-------- 7 files changed, 37 insertions(+), 26 deletions(-) diff --git a/assets/openapi.json b/assets/openapi.json index 4632691300d6905c73557360f20dfb80e3e000f9..49e27545834f27bdd7ca15d8c5b62883166fc05a 100644 GIT binary patch delta 1510 zcmcb(&~^J4*M=6x7N#xCD)%NY;Fsm}POU7@FG>k2ogSFNra%2d6Qcz`R4kwE&ew|~s6 zOS&f|kUfcF5yFegY@84;ZeQib$;veOzy}u2fTH}g%$(GqQu4eM8tfVb3`>7EPajv@ zUebL^j+exdy@X`Z^u%~h$LTKIFwboB047A#j7y$pJRF03-9mlheLVfVrx(s-ikoh5 zmpK)lbJ!921tqjmw1GogA%RnEy25S_R(P>No`c*&J$+o_1N?nFog+zb5Q;WL^snOJ zl!F#pL^($UJ^g~yE#7buK#nr7aOJrg;BmCYs^Ww_0mUPOdB)7CR;O#jfzs65@ifQfzjr&`98=?%9zVkdv_ zh@HNHgOz1^yfUZJWCh9a=@Tw6rcbXoV`OX3e9R2QEI`Z(#2`NV_RPl|Pr|0}ci?2- zZWYEkn;F76rUjD&0>rqyQ!C5!i&BD0bEY#sW|W)0z=u&7Bq0Em2q;P| zPAx%^p6*b?B*Bwcl9`_uT#%THDrc~lQHm>|C_gPTCly5oWGJ6UVo`2dX^u~3UN(y4 z^aDn0(wqVLIho0os1gdNn8n$kwkXPSNKW3c*k`(cJ0pAhiDk?{%mT!$K+LxN#4`48 zi?|W~NuBzDBYV33d}faA?`AV!VVc~)!arSiF*C>Xl;5mU(*x!(D+-4cWu~X67KKz6 zr0RLQMuz(bxlB*2VV0k+x0qRAy6+-pk?9xWn8c?yEMjJ#e$%Y=QZP zV>+)OhtKo`e-4rDKYch1nE8EEk&_J(o{>QIjIXO-h^N0_d~krHFD|dJPZwCitV+6n za9f1%jin^SH|*OD0ysV~PJZx$W%7Z?>@2_(ON3X%k-ZWc>>31&6n{5QpXmo>IOIVI zQxGY^O<2mTL%Jt$S`nDaVK_ZR4(5E#AYj_!N6m~x_y8r0JRF03-9mlheLVfVCp%`U z;K~o|h+KeE|8ztc3-qvo^EVqOtS}(eVeX-xJ}&VA{yv`0k>ofGSwE7)_DXa3a6rpD eBAg_E9NVA-OQcgU^dULHe>I!W_KoQrJ~jY6PXqk` delta 367 zcmey6rUlarOb<|JlArG2!@)j%swao)bcG}qmgx)X z8LOxB<#7m3&&=cC1&dAoAS^rm!7}#1=>}>nG9Z@e^a*M#Dj;R5NQPu&aPUq`2Wm1% zh3dp-8g?@RK}HgC2zITuWq7ns|8SgLV)BN?KJ5*Qn1PrDh*^P{ZF|Ea_P>j!8^l6F zL1MdJ1qTN+grAqm!MiP;!vMnFu2sPCi4iQbU95sb0W8Wr-MWT@Yx`X 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}` }); diff --git a/src/api/middlewares/Route.ts b/src/api/middlewares/Route.ts index 4b7ccb12e..5341f100b 100644 --- a/src/api/middlewares/Route.ts +++ b/src/api/middlewares/Route.ts @@ -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!); } } diff --git a/src/schemas/Validator.ts b/src/schemas/Validator.ts index c83e16436..73672c183 100644 --- a/src/schemas/Validator.ts +++ b/src/schemas/Validator.ts @@ -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(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: "<>", keyword: e.keyword }; + else if (e.keyword == "anyOf") return { instancePath: instancePath, value: "<>", 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((>data)[nextProp], nextPath); + return { instancePath, value: getInvalidSchemaValue((>data)[nextProp], nextPath).value }; } return { instancePath, value: data }; } diff --git a/src/schemas/api/guilds/Automod.ts b/src/schemas/api/guilds/Automod.ts index 7f990edeb..ff673810c 100644 --- a/src/schemas/api/guilds/Automod.ts +++ b/src/schemas/api/guilds/Automod.ts @@ -27,19 +27,13 @@ interface BaseAutomodRuleSchema { actions: AutomodAction[]; } -type WithTrigger = BaseAutomodRuleSchema & { - trigger_type: T; -} & M extends never - ? { trigger_metadata?: never } - : { trigger_metadata: M }; - -type KeywordRule = WithTrigger; -type HarmfulLinkRule = WithTrigger; -type SpamRule = WithTrigger; -type KeywordPresetRule = WithTrigger; -type MentionSpamRule = WithTrigger; -type UserProfileRule = WithTrigger; -type GuildPolicyRule = WithTrigger; +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;