Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 59 additions & 22 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,39 @@ export const isOptional = (
return !!schema && OptionalKind in schema
}

export const hasPatternProperties = (
_schema: TAnySchema | TypeCheck<any> | ElysiaTypeCheck<any>
): boolean => {
if (!_schema) return false

// @ts-expect-error private property
const schema: TAnySchema = (_schema as TypeCheck<any>)?.schema ?? _schema

if ('patternProperties' in schema) return true

if (schema[Kind] === 'Import' && _schema.References)
return _schema.References().some(hasPatternProperties)

if (schema.anyOf) return schema.anyOf.some(hasPatternProperties)
if (schema.oneOf) return schema.oneOf.some(hasPatternProperties)
if (schema.someOf) return schema.someOf.some(hasPatternProperties)
if (schema.allOf) return schema.allOf.some(hasPatternProperties)
if (schema.not) return hasPatternProperties(schema.not)

if (schema.type === 'object' && schema.properties) {
const properties = schema.properties as Record<string, TAnySchema>

for (const key of Object.keys(properties)) {
if (hasPatternProperties(properties[key])) return true
}
}

if (schema.type === 'array' && schema.items && !Array.isArray(schema.items))
return hasPatternProperties(schema.items)

return false
}

export const hasAdditionalProperties = (
_schema: TAnySchema | TypeCheck<any> | ElysiaTypeCheck<any>
): boolean => {
Expand Down Expand Up @@ -507,7 +540,7 @@ export const getSchemaValidator = <
let mirror: Function
if (normalize === true || normalize === 'exactMirror')
try {
mirror = createMirror(schema as TSchema, {
mirror = createMirror(schema as TAnySchema, {
TypeCompiler,
sanitize: sanitize?.(),
modules
Expand Down Expand Up @@ -684,14 +717,15 @@ export const getSchemaValidator = <
})
])

if (schema.type === 'object' && hasAdditional)
if (schema.type === 'object' && hasAdditional && !('patternProperties' in schema))
schema.additionalProperties = false
}
} else {
if (
schema.type === 'object' &&
('additionalProperties' in schema === false ||
forceAdditionalProperties)
forceAdditionalProperties) &&
!('patternProperties' in schema)
)
schema.additionalProperties = additionalProperties
else
Expand All @@ -701,6 +735,7 @@ export const getSchemaValidator = <
to(schema) {
if (!schema.properties) return schema;
if ("additionalProperties" in schema) return schema;
if ("patternProperties" in schema) return schema;

return t.Object(schema.properties, {
...schema,
Expand Down Expand Up @@ -764,10 +799,10 @@ export const getSchemaValidator = <
if (validator?.schema?.config) delete validator.schema.config
}

if (normalize && schema.additionalProperties === false) {
if (normalize && !hasPatternProperties(schema)) {
if (normalize === true || normalize === 'exactMirror') {
try {
validator.Clean = createMirror(schema, {
validator.Clean = createMirror(schema as TAnySchema, {
TypeCompiler,
sanitize: sanitize?.(),
modules
Expand Down Expand Up @@ -903,25 +938,27 @@ export const getSchemaValidator = <
if (compiled?.schema?.config) delete compiled.schema.config
}

if (normalize === true || normalize === 'exactMirror') {
try {
compiled.Clean = createMirror(schema, {
TypeCompiler,
sanitize: sanitize?.(),
modules
})
} catch (error) {
console.warn(
'Failed to create exactMirror. Please report the following code to https://github.com/elysiajs/elysia/issues'
)
console.dir(schema, {
depth: null
})
if (normalize && !hasPatternProperties(schema)) {
if (normalize === true || normalize === 'exactMirror') {
try {
compiled.Clean = createMirror(schema as TAnySchema, {
TypeCompiler,
sanitize: sanitize?.(),
modules
})
} catch (error) {
console.warn(
'Failed to create exactMirror. Please report the following code to https://github.com/elysiajs/elysia/issues'
)
console.dir(schema, {
depth: null
})

compiled.Clean = createCleaner(schema)
}
} else if (normalize === 'typebox')
compiled.Clean = createCleaner(schema)
}
} else if (normalize === 'typebox')
compiled.Clean = createCleaner(schema)
}
} else {
compiled = {
provider: 'standard',
Expand Down
29 changes: 29 additions & 0 deletions test/validator/response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,4 +562,33 @@ describe('Response Validator', () => {

expect(result.join('')).toContain('data: {"name":"Name"}')
})

it('validate Record with nested objects', async () => {
const app = new Elysia().get('/', () => ({
list: [{
toto: { bar: 1 },
foo: { link: 'first' }
}],
one: {
toto: { bar: 0 },
foo: { link: 'second' }
}
}), {
response: {
200: t.Object({
list: t.Array(t.Object({
toto: t.Object({ bar: t.Integer() }),
foo: t.Record(t.String(), t.String())
})),
one: t.Object({
toto: t.Object({ bar: t.Integer() }),
foo: t.Record(t.String(), t.String())
})
})
}
})

const res = await app.handle(req('/'))
expect(res.status).toBe(200)
})
})
Loading