From 0a70c46f2c080fd4b3b2905ec6da3e5b21aa4218 Mon Sep 17 00:00:00 2001 From: NongTham Date: Sat, 7 Mar 2026 23:11:19 +0700 Subject: [PATCH 1/2] fix(schema): correct hasElysiaMeta recursive logic to prevent short-circuiting --- src/schema.ts | 129 +++++++++++++++---------------- test/schema/schema-utils.test.ts | 32 ++++++++ 2 files changed, 93 insertions(+), 68 deletions(-) diff --git a/src/schema.ts b/src/schema.ts index fd5cdc551..27a1f2abf 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -50,20 +50,20 @@ export interface ElysiaTypeCheck Check(value: unknown): T extends TSchema ? boolean : - | { - value: UnwrapSchema - } - | { issues: unknown[] } + | { + value: UnwrapSchema + } + | { issues: unknown[] } Clean?(v: unknown): UnwrapSchema parse(v: unknown): UnwrapSchema safeParse(v: unknown): | { success: true; data: UnwrapSchema; error: null } | { - success: false - data: null - error: string | undefined - errors: MapValueError[] - } + success: false + data: null + error: string | undefined + errors: MapValueError[] + } hasAdditionalProperties: boolean '~hasAdditionalProperties'?: boolean hasDefault: boolean @@ -235,17 +235,10 @@ export const hasElysiaMeta = (meta: string, _schema: TAnySchema): boolean => { for (const key of Object.keys(properties)) { const property = properties[key] - if (property.type === 'object') { - if (hasElysiaMeta(meta, property)) return true - } else if (property.anyOf) { - for (let i = 0; i < property.anyOf.length; i++) - if (hasElysiaMeta(meta, property.anyOf[i])) return true - } - - return schema.elysiaMeta === meta + if (hasElysiaMeta(meta, property)) return true } - return false + return schema.elysiaMeta === meta } if (schema.type === 'array' && schema.items && !Array.isArray(schema.items)) @@ -401,7 +394,7 @@ const createCleaner = (schema: TAnySchema) => (value: unknown) => { if (typeof value === 'object') try { return Value.Clean(schema, value) - } catch {} + } catch { } return value } @@ -436,8 +429,8 @@ export const getSchemaValidator = ( ): IsAny extends true ? ElysiaTypeCheck : undefined extends T - ? undefined - : ElysiaTypeCheck>> => { + ? undefined + : ElysiaTypeCheck>> => { validators = validators?.filter((x) => x) if (!s) { @@ -674,7 +667,7 @@ export const getSchemaValidator = ( provider: 'standard', schema, references: '', - checkFunc: () => {}, + checkFunc: () => { }, code: '', // @ts-ignore Check, @@ -787,7 +780,7 @@ export const getSchemaValidator = ( schema, // @ts-ignore references: '', - checkFunc: () => {}, + checkFunc: () => { }, code: '', Check: (value: unknown) => Value.Check(schema, value), Errors: (value: unknown) => Value.Errors(schema, value), @@ -887,7 +880,7 @@ export const getSchemaValidator = ( provider: 'standard', schema, references: '', - checkFunc: () => {}, + checkFunc: () => { }, code: '', // @ts-ignore Check: (v) => schema['~standard'].validate(v), @@ -1150,13 +1143,13 @@ export const mergeObjectSchemas = ( if (schemas.length === 1) return schemas[0].type === 'object' ? { - schema: schemas[0] as TObject, - notObjects: [] - } + schema: schemas[0] as TObject, + notObjects: [] + } : { - schema: undefined, - notObjects: schemas - } + schema: undefined, + notObjects: schemas + } let newSchema: TObject const notObjects = [] @@ -1293,16 +1286,16 @@ export const getResponseSchemaValidator = ( record[+status] = Kind in schema || '~standard' in schema ? getSchemaValidator(schema as TSchema, { - modules, - models, - additionalProperties, - dynamic, - normalize, - coerce: false, - additionalCoerce: [], - validators: validators.map((x) => x![+status]), - sanitize - })! + modules, + models, + additionalProperties, + dynamic, + normalize, + coerce: false, + additionalCoerce: [], + validators: validators.map((x) => x![+status]), + sanitize + })! : (schema as ElysiaTypeCheck) } @@ -1313,16 +1306,16 @@ export const getResponseSchemaValidator = ( record[+status] = Kind in maybeNameOrSchema || '~standard' in maybeNameOrSchema ? getSchemaValidator(maybeNameOrSchema as TSchema, { - modules, - models, - additionalProperties, - dynamic, - normalize, - coerce: false, - additionalCoerce: [], - validators: validators.map((x) => x![+status]), - sanitize - }) + modules, + models, + additionalProperties, + dynamic, + normalize, + coerce: false, + additionalCoerce: [], + validators: validators.map((x) => x![+status]), + sanitize + }) : (maybeNameOrSchema as ElysiaTypeCheck) }) @@ -1341,11 +1334,11 @@ export const getCookieValidator = ({ sanitize }: { validator: - | TSchema - | StandardSchemaV1Like - | ElysiaTypeCheck - | string - | undefined + | TSchema + | StandardSchemaV1Like + | ElysiaTypeCheck + | string + | undefined modules: TModule defaultConfig: CookieOptions | undefined config: CookieOptions @@ -1360,17 +1353,17 @@ export const getCookieValidator = ({ validator?.provider ? (validator as ElysiaTypeCheck) : // @ts-ignore - getSchemaValidator(validator, { - modules, - dynamic, - models, - normalize, - additionalProperties: true, - coerce: true, - additionalCoerce: stringToStructureCoercions(), - validators, - sanitize - }) + getSchemaValidator(validator, { + modules, + dynamic, + models, + normalize, + additionalProperties: true, + coerce: true, + additionalCoerce: stringToStructureCoercions(), + validators, + sanitize + }) if (cookieValidator) cookieValidator.config = mergeCookie(cookieValidator.config, config) @@ -1431,7 +1424,7 @@ export const getCookieValidator = ({ export const unwrapImportSchema = (schema: TSchema): TSchema => schema && - schema[Kind] === 'Import' && - schema.$defs[schema.$ref][Kind] === 'Object' + schema[Kind] === 'Import' && + schema.$defs[schema.$ref][Kind] === 'Object' ? schema.$defs[schema.$ref] : schema diff --git a/test/schema/schema-utils.test.ts b/test/schema/schema-utils.test.ts index b6a9924b6..2f4f3c39a 100644 --- a/test/schema/schema-utils.test.ts +++ b/test/schema/schema-utils.test.ts @@ -171,3 +171,35 @@ describe('hasProperty', () => { expect(hasProperty('default', schema)).toBe(true) }) }) + +describe('hasElysiaMeta', () => { + const { hasElysiaMeta } = require('../../src/schema') + + it('finds elysiaMeta in Object properties even if placed after another property', () => { + const schema = t.Object({ + a: t.String(), + b: t.ObjectString({ c: t.String() }) + }) + + expect(hasElysiaMeta('ObjectString', schema)).toBe(true) + }) + + it('finds elysiaMeta in deeply nested objects', () => { + const schema = t.Object({ + outer: t.Object({ + inner: t.ObjectString({ c: t.String() }) + }) + }) + + expect(hasElysiaMeta('ObjectString', schema)).toBe(true) + }) + + it('returns false if elysiaMeta is not present', () => { + const schema = t.Object({ + a: t.String(), + b: t.Object({ c: t.String() }) + }) + + expect(hasElysiaMeta('ObjectString', schema)).toBe(false) + }) +}) From 7f7315ae28b59fddb800db2e62a62cd6948d7d0e Mon Sep 17 00:00:00 2001 From: NongTham Date: Sun, 8 Mar 2026 00:22:54 +0700 Subject: [PATCH 2/2] fix: correct hasAdditionalProperties traversal and safeParse ReferenceError in src/schema.ts --- src/schema.ts | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/schema.ts b/src/schema.ts index 27a1f2abf..fe8de451a 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -112,23 +112,14 @@ export const hasAdditionalProperties = ( if (schema.type === 'object') { const properties = schema.properties as Record - if ('additionalProperties' in schema) return schema.additionalProperties - if ('patternProperties' in schema) return false + if (properties) + for (const key of Object.keys(properties)) { + const property = properties[key] - for (const key of Object.keys(properties)) { - const property = properties[key] - - if (property.type === 'object') { if (hasAdditionalProperties(property)) return true - } else if (property.anyOf) { - for (let i = 0; i < property.anyOf.length; i++) - if (hasAdditionalProperties(property.anyOf[i])) return true } - return property.additionalProperties - } - - return false + return schema.additionalProperties === true } if (schema.type === 'array' && schema.items && !Array.isArray(schema.items)) @@ -701,7 +692,7 @@ export const getSchemaValidator = ( error: null } } catch (error) { - const errors = [...compiled.Errors(v)].map(mapValueError) + const errors = [...validator.Errors(v)].map(mapValueError) return { success: false, @@ -861,7 +852,7 @@ export const getSchemaValidator = ( error: null } } catch (error) { - const errors = [...compiled.Errors(v)].map(mapValueError) + const errors = [...validator.Errors(v)].map(mapValueError) return { success: false, @@ -934,7 +925,7 @@ export const getSchemaValidator = ( error: null } } catch (error) { - const errors = [...compiled.Errors(v)].map(mapValueError) + const errors = [...validator.Errors(v)].map(mapValueError) return { success: false,