Skip to content

Commit

Permalink
🔧 fix: unable to reference schema
Browse files Browse the repository at this point in the history
  • Loading branch information
SaltyAom committed Dec 27, 2024
1 parent 46bb868 commit 57b24c1
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 46 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 1.2.7 - 27 Dec 2024
Bug fix:
- macro doesn't work with guard
- [#981](https://github.com/elysiajs/elysia/issues/981) unable to deference schema, create default, and coerce value

# 1.2.6 - 25 Dec 2024
Bug fix:
Expand Down
41 changes: 18 additions & 23 deletions example/a.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
import { Elysia } from '../src'
import { req } from '../test/utils'
import { Elysia, t } from '../src'
import { post, req } from '../test/utils'

const plugin = new Elysia()
.macro({
account: (a: boolean) => ({
resolve: ({ error }) => ({
account: 'A'
})
})
const app = new Elysia()
.onError(({ error }) => {
console.log({ error })
})
.guard({
account: true
.model({
session: t.Cookie({ token: t.Number() }),
optionalSession: t.Optional(t.Ref('session'))
})
.get('/local', ({ account }) => {
console.log(account)
.get('/', () => 'Hello Elysia', {
cookie: 'optionalSession'
})

const parent = new Elysia().use(plugin).get('/plugin', (context) => {
console.log(context.account)
})

const app = new Elysia().use(parent).get('/global', (context) => {
console.log(context.account)
})

await Promise.all(
['/local', '/plugin', '/global'].map((path) => app.handle(req(path)))
const correct = await app.handle(
new Request('http://localhost/', {
headers: {
cookie: 'token=1'
}
})
)

console.log(correct)
4 changes: 4 additions & 0 deletions src/adapter/bun/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,16 @@ export const BunAdapter: ElysiaAdapter = {
const { parse, body, response, ...rest } = options

const validateMessage = getSchemaValidator(body, {
// @ts-expect-error private property
modules: app.definitions.typebox,
// @ts-expect-error private property
models: app.definitions.type as Record<string, TSchema>,
normalize: app.config.normalize
})

const validateResponse = getSchemaValidator(response as any, {
// @ts-expect-error private property
modules: app.definitions.typebox,
// @ts-expect-error private property
models: app.definitions.type as Record<string, TSchema>,
normalize: app.config.normalize
Expand Down
45 changes: 33 additions & 12 deletions src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ const isOptional = (validator?: TypeCheck<any>) => {
// @ts-expect-error
const schema = validator?.schema

if (schema?.[TypeBoxSymbol.kind] === 'Import')
return validator.References().some(isOptional as any)

return !!schema && TypeBoxSymbol.optional in schema
}

Expand All @@ -63,9 +66,13 @@ const defaultParsers = [
'arrayBuffer',
'formdata',
'application/json',
// eslint-disable-next-line sonarjs/no-duplicate-string
'text/plain',
// eslint-disable-next-line sonarjs/no-duplicate-string
'application/x-www-form-urlencoded',
// eslint-disable-next-line sonarjs/no-duplicate-string
'application/octet-stream',
// eslint-disable-next-line sonarjs/no-duplicate-string
'multipart/form-data'
]

Expand All @@ -77,6 +84,11 @@ export const hasAdditionalProperties = (
// @ts-expect-error private property
const schema: TAnySchema = (_schema as TypeCheck<any>)?.schema ?? _schema

// @ts-expect-error private property
if (schema[TypeBoxSymbol.kind] === 'Import' && _schema.References()) {
return _schema.References().some(hasAdditionalProperties)
}

if (schema.anyOf) return schema.anyOf.some(hasAdditionalProperties)
if (schema.someOf) return schema.someOf.some(hasAdditionalProperties)
if (schema.allOf) return schema.allOf.some(hasAdditionalProperties)
Expand Down Expand Up @@ -287,8 +299,19 @@ export const hasType = (type: string, schema: TAnySchema) => {
)
}

export const hasProperty = (expectedProperty: string, schema: TAnySchema) => {
if (!schema) return
export const hasProperty = (
expectedProperty: string,
_schema: TAnySchema | TypeCheck<any>
) => {
if (!_schema) return

// @ts-expect-error private property
const schema = _schema.schema ?? _schema

if (schema[TypeBoxSymbol.kind] === 'Import')
return _schema
.References()
.some((schema: TAnySchema) => hasProperty(expectedProperty, schema))

if (schema.type === 'object') {
const properties = schema.properties as Record<string, TAnySchema>
Expand Down Expand Up @@ -526,6 +549,8 @@ export const composeHandler = ({

const cookieValidator = hasCookie
? getCookieValidator({
// @ts-expect-error private property
modules: app.definitions.typebox,
validator: validator.cookie as any,
defaultConfig: app.config.cookie,
dynamic: !!app.config.aot,
Expand Down Expand Up @@ -1168,8 +1193,7 @@ export const composeHandler = ({
)
fnLiteral += 'c.headers=validator.headers.Clean(c.headers);\n'

// @ts-ignore
if (hasProperty('default', validator.headers.schema))
if (hasProperty('default', validator.headers))
for (const [key, value] of Object.entries(
Value.Default(
// @ts-ignore
Expand Down Expand Up @@ -1204,8 +1228,7 @@ export const composeHandler = ({
}

if (validator.params) {
// @ts-ignore
if (hasProperty('default', validator.params.schema))
if (hasProperty('default', validator.params))
for (const [key, value] of Object.entries(
Value.Default(
// @ts-ignore
Expand Down Expand Up @@ -1242,8 +1265,7 @@ export const composeHandler = ({
)
fnLiteral += 'c.query=validator.query.Clean(c.query)\n'

// @ts-ignore
if (hasProperty('default', validator.query.schema))
if (hasProperty('default', validator.query))
for (const [key, value] of Object.entries(
Value.Default(
// @ts-ignore
Expand Down Expand Up @@ -1291,8 +1313,7 @@ export const composeHandler = ({
if (doesHaveTransform || isOptional(validator.body))
fnLiteral += `const isNotEmptyObject=c.body&&(typeof c.body==="object"&&isNotEmpty(c.body))\n`

// @ts-ignore
if (hasProperty('default', validator.body.schema)) {
if (hasProperty('default', validator.body)) {
const value = Value.Default(
// @ts-expect-error private property
validator.body.schema,
Expand Down Expand Up @@ -1343,6 +1364,7 @@ export const composeHandler = ({
}

if (
cookieValidator &&
isNotEmpty(
// @ts-ignore
cookieValidator?.schema?.properties ??
Expand All @@ -1356,8 +1378,7 @@ export const composeHandler = ({
`for(const [key,value] of Object.entries(c.cookie))` +
`cookieValue[key]=value.value\n`

// @ts-ignore
if (hasProperty('default', cookieValidator.schema))
if (hasProperty('default', cookieValidator))
for (const [key, value] of Object.entries(
Value.Default(
// @ts-ignore
Expand Down
46 changes: 39 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import {
PromiseGroup,
promoteEvent,
stringToStructureCoercions,
isNotEmpty
isNotEmpty,
replaceSchemaType
} from './utils'

import {
Expand Down Expand Up @@ -351,6 +352,7 @@ export default class Elysia<

env(model: TObject<any>, _env = env) {
const validator = getSchemaValidator(model, {
modules: this.definitions.typebox,
dynamic: true,
additionalProperties: true,
coerce: true
Expand Down Expand Up @@ -453,9 +455,10 @@ export default class Elysia<
} {
const models: Record<string, TypeCheck<TSchema>> = {}

for (const [name, schema] of Object.entries(this.definitions.type))
for (const name of Object.keys(this.definitions.type))
models[name] = getSchemaValidator(
schema as any
// @ts-expect-error
this.definitions.typebox.Import(name)
) as TypeCheck<TSchema>

// @ts-expect-error
Expand Down Expand Up @@ -525,6 +528,7 @@ export default class Elysia<
const cookieValidator = () =>
cloned.cookie
? getCookieValidator({
modules,
validator: cloned.cookie,
defaultConfig: this.config.cookie,
config: cloned.cookie?.config ?? {},
Expand All @@ -534,32 +538,37 @@ export default class Elysia<
: undefined

const normalize = this.config.normalize
const modules = this.definitions.typebox

const validator =
this.config.precompile === true ||
(typeof this.config.precompile === 'object' &&
this.config.precompile.schema === true)
? {
body: getSchemaValidator(cloned.body, {
modules,
dynamic,
models,
normalize,
additionalCoerce: coercePrimitiveRoot()
}),
headers: getSchemaValidator(cloned.headers, {
modules,
dynamic,
models,
additionalProperties: !this.config.normalize,
coerce: true,
additionalCoerce: stringToStructureCoercions()
}),
params: getSchemaValidator(cloned.params, {
modules,
dynamic,
models,
coerce: true,
additionalCoerce: stringToStructureCoercions()
}),
query: getSchemaValidator(cloned.query, {
modules,
dynamic,
models,
normalize,
Expand All @@ -568,6 +577,7 @@ export default class Elysia<
}),
cookie: cookieValidator(),
response: getResponseSchemaValidator(cloned.response, {
modules,
dynamic,
models,
normalize
Expand All @@ -580,6 +590,7 @@ export default class Elysia<
return (this.body = getSchemaValidator(
cloned.body,
{
modules,
dynamic,
models,
normalize,
Expand All @@ -593,6 +604,7 @@ export default class Elysia<
return (this.headers = getSchemaValidator(
cloned.headers,
{
modules,
dynamic,
models,
additionalProperties: !normalize,
Expand All @@ -608,6 +620,7 @@ export default class Elysia<
return (this.params = getSchemaValidator(
cloned.params,
{
modules,
dynamic,
models,
coerce: true,
Expand All @@ -622,6 +635,7 @@ export default class Elysia<
return (this.query = getSchemaValidator(
cloned.query,
{
modules,
dynamic,
models,
coerce: true,
Expand All @@ -641,6 +655,7 @@ export default class Elysia<
return (this.response = getResponseSchemaValidator(
cloned.response,
{
modules,
dynamic,
models,
normalize
Expand Down Expand Up @@ -5626,24 +5641,41 @@ export default class Elysia<
>

model(name: string | Record<string, TSchema> | Function, model?: TSchema) {
const coerce = (schema: TSchema) =>
replaceSchemaType(schema, [
{
from: t.Number(),
to: (options) => t.Numeric(options),
untilObjectFound: true
},
{
from: t.Boolean(),
to: (options) => t.BooleanString(options),
untilObjectFound: true
}
])

switch (typeof name) {
case 'object':
const parsedSchemas = {} as Record<string, TSchema>

Object.entries(name).forEach(([key, value]) => {
if (!(key in this.definitions.type))
this.definitions.type[key] = value as TSchema
parsedSchemas[key] = this.definitions.type[key] =
coerce(value) as TSchema
})
// @ts-expect-error
this.definitions.typebox = t.Module({
...(this.definitions.typebox['$defs'] as TModule<{}>),
...name
...parsedSchemas
} as any)

return this

case 'function':
const result = name(this.definitions.type)
const result = coerce(name(this.definitions.type))
this.definitions.type = result
this.definitions.typebox = t.Module(result)
this.definitions.typebox = t.Module(result as any)

return this as any
}
Expand Down
Loading

0 comments on commit 57b24c1

Please sign in to comment.