Skip to content

Commit 220e922

Browse files
committed
🎉 feat: named parser
1 parent 18d4676 commit 220e922

File tree

10 files changed

+502
-121
lines changed

10 files changed

+502
-121
lines changed

example/a.ts

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,52 @@
1-
new Elysia()
2-
.macro({
3-
auth(enabled: boolean) {
4-
return {
5-
async resolve() {
6-
return {
7-
user: 'saltyaom'
8-
}
9-
}
10-
}
11-
}
1+
import { Elysia, t } from '../src'
2+
3+
const app = new Elysia()
4+
.onParse('custom', ({ contentType, request }) => {
5+
if (contentType.startsWith('application/x-elysia-1'))
6+
return { name: 'Eden' }
127
})
13-
.get('/', ({ user }) => {}, {
14-
auth: true
8+
.onParse('custom2', ({ contentType, request }) => {
9+
if (contentType.startsWith('application/x-elysia-2'))
10+
return { name: 'Pardofelis' }
1511
})
12+
.post('/json', ({ body }) => body, {
13+
parse: ['custom']
14+
})
15+
16+
const response = await Promise.all([
17+
app
18+
.handle(
19+
new Request('http://localhost:3000/json', {
20+
method: 'POST',
21+
headers: {
22+
'content-type': 'application/json'
23+
},
24+
body: JSON.stringify({ name: 'Aru' })
25+
})
26+
)
27+
.then((x) => x.json()),
28+
app
29+
.handle(
30+
new Request('http://localhost:3000/json', {
31+
method: 'POST',
32+
headers: {
33+
'content-type': 'application/x-elysia-1'
34+
},
35+
body: JSON.stringify({ name: 'Aru' })
36+
})
37+
)
38+
.then((x) => x.text()),
39+
app
40+
.handle(
41+
new Request('http://localhost:3000/json', {
42+
method: 'POST',
43+
headers: {
44+
'content-type': 'application/x-elysia-2'
45+
},
46+
body: JSON.stringify({ name: 'Aru' })
47+
})
48+
)
49+
.then((x) => x.text())
50+
])
51+
52+
console.log(response)

example/app.ts

Whitespace-only changes.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "elysia",
33
"description": "Ergonomic Framework for Human",
4-
"version": "1.2.0-exp.50",
4+
"version": "1.2.0-exp.51",
55
"author": {
66
"name": "saltyAom",
77
"url": "https://github.com/SaltyAom",

src/compose.ts

Lines changed: 152 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ const isOptional = (validator?: TypeCheck<any>) => {
5656
return !!schema && TypeBoxSymbol.optional in schema
5757
}
5858

59+
const defaultParsers = [
60+
'json',
61+
'text',
62+
'urlencoded',
63+
'arrayBuffer',
64+
'formdata',
65+
'application/json',
66+
'text/plain',
67+
'application/x-www-form-urlencoded',
68+
'application/octet-stream',
69+
'multipart/form-data'
70+
]
71+
5972
export const hasAdditionalProperties = (
6073
_schema: TAnySchema | TypeCheck<any>
6174
) => {
@@ -481,7 +494,7 @@ export const composeHandler = ({
481494
'get:function(){return getServer()}' +
482495
'})\n'
483496

484-
if (inference.body) fnLiteral += `let isParsing = false\n`
497+
if (inference.body) fnLiteral += `let isParsing=false\n`
485498

486499
validator.createBody?.()
487500
validator.createQuery?.()
@@ -699,7 +712,8 @@ export const composeHandler = ({
699712
'}'
700713
} else {
701714
fnLiteral +=
702-
'if(c.qi!==-1){' + `let url = '&' + decodeURIComponent(c.url.slice(c.qi + 1))\n`
715+
'if(c.qi!==-1){' +
716+
`let url = '&' + decodeURIComponent(c.url.slice(c.qi + 1))\n`
703717

704718
let index = 0
705719
for (const {
@@ -883,9 +897,23 @@ export const composeHandler = ({
883897

884898
if (adapter.parser.declare) fnLiteral += adapter.parser.declare
885899

886-
fnLiteral += 'isParsing=true\n'
887-
if (hooks.type && !hooks.parse.length) {
888-
switch (hooks.type) {
900+
const parser =
901+
typeof hooks.parse === 'string'
902+
? hooks.parse
903+
: Array.isArray(hooks.parse) && hooks.parse.length === 1
904+
? typeof hooks.parse[0] === 'string'
905+
? hooks.parse[0]
906+
: typeof hooks.parse[0].fn === 'string'
907+
? hooks.parse[0].fn
908+
: undefined
909+
: undefined
910+
911+
if (parser && parser in defaultParsers) {
912+
const reporter = report('parse', {
913+
total: hooks.parse.length
914+
})
915+
916+
switch (parser) {
889917
case 'json':
890918
case 'application/json':
891919
fnLiteral += adapter.parser.json(isOptionalBody)
@@ -913,7 +941,32 @@ export const composeHandler = ({
913941
case 'multipart/form-data':
914942
fnLiteral += adapter.parser.formData(isOptionalBody)
915943
break
944+
945+
default:
946+
if ((parser[0] as string) in app.parser) {
947+
fnLiteral += hasHeaders
948+
? `let contentType = c.headers['content-type']`
949+
: `let contentType = c.request.headers.get('content-type')`
950+
951+
fnLiteral +=
952+
`\nif(contentType){` +
953+
`const index=contentType.indexOf(';')\n` +
954+
`if(index!==-1)contentType=contentType.substring(0, index)}\n` +
955+
`else{contentType=''}` +
956+
`c.contentType=contentType\n`
957+
958+
fnLiteral +=
959+
`let result=parser['${parser}'](c, contentType)\n` +
960+
`if(result instanceof Promise)result=await result\n` +
961+
`if(result instanceof ElysiaCustomStatusResponse)throw result\n` +
962+
`if(result!==undefined)c.body=result\n` +
963+
'delete c.contentType\n'
964+
}
965+
966+
break
916967
}
968+
969+
reporter.resolve()
917970
} else if (hasBodyInference) {
918971
fnLiteral += '\n'
919972
fnLiteral += hasHeaders
@@ -923,74 +976,103 @@ export const composeHandler = ({
923976
fnLiteral +=
924977
`\nif(contentType){` +
925978
`const index=contentType.indexOf(';')\n` +
926-
`if(index!==-1)contentType=contentType.substring(0, index)\n` +
979+
`if(index!==-1)contentType=contentType.substring(0, index)}\n` +
980+
`else{contentType=''}` +
927981
`c.contentType=contentType\n`
928982

929-
if (hooks.parse.length) {
930-
fnLiteral += `let used=false\n`
983+
if (hooks.parse.length) fnLiteral += `let used=false\n`
931984

932-
const reporter = report('parse', {
933-
total: hooks.parse.length
934-
})
985+
const reporter = report('parse', {
986+
total: hooks.parse.length
987+
})
988+
989+
let hasDefaultParser = false
990+
for (let i = 0; i < hooks.parse.length; i++) {
991+
const name = `bo${i}`
992+
if (i !== 0) fnLiteral += `\nif(!used){`
935993

936-
for (let i = 0; i < hooks.parse.length; i++) {
994+
if (typeof hooks.parse[i].fn === 'string') {
937995
const endUnit = reporter.resolveChild(
938-
hooks.parse[i].fn.name
996+
hooks.parse[i].fn as unknown as string
939997
)
940998

941-
const name = `bo${i}`
999+
switch (hooks.parse[i].fn as unknown as string) {
1000+
case 'json':
1001+
case 'application/json':
1002+
hasDefaultParser = true
1003+
fnLiteral += adapter.parser.json(isOptionalBody)
9421004

943-
if (i !== 0) fnLiteral += `if(!used){`
1005+
break
9441006

945-
fnLiteral +=
946-
`let ${name}=parse[${i}](c,contentType)\n` +
947-
`if(${name} instanceof Promise)${name}=await ${name}\n` +
948-
`if(${name}!==undefined){c.body=${name};used=true}`
1007+
case 'text':
1008+
case 'text/plain':
1009+
hasDefaultParser = true
1010+
fnLiteral += adapter.parser.text(isOptionalBody)
9491011

950-
endUnit()
1012+
break
9511013

952-
if (i !== 0) fnLiteral += `}`
953-
}
1014+
case 'urlencoded':
1015+
case 'application/x-www-form-urlencoded':
1016+
hasDefaultParser = true
1017+
fnLiteral +=
1018+
adapter.parser.urlencoded(isOptionalBody)
9541019

955-
reporter.resolve()
956-
}
1020+
break
1021+
1022+
case 'arrayBuffer':
1023+
case 'application/octet-stream':
1024+
hasDefaultParser = true
1025+
fnLiteral +=
1026+
adapter.parser.arrayBuffer(isOptionalBody)
9571027

958-
fnLiteral += '\ndelete c.contentType\n'
1028+
break
9591029

960-
if (hooks.parse.length) fnLiteral += `if(!used){`
1030+
case 'formdata':
1031+
case 'multipart/form-data':
1032+
hasDefaultParser = true
1033+
fnLiteral += adapter.parser.formData(isOptionalBody)
9611034

962-
if (hooks.type && !Array.isArray(hooks.type)) {
963-
switch (hooks.type) {
964-
case 'json':
965-
case 'application/json':
966-
fnLiteral += adapter.parser.json(isOptionalBody)
967-
break
1035+
break
9681036

969-
case 'text':
970-
case 'text/plain':
971-
fnLiteral += adapter.parser.text(isOptionalBody)
972-
break
1037+
default:
1038+
fnLiteral +=
1039+
`${name}=parser['${hooks.parse[i].fn}'](c,contentType)\n` +
1040+
`if(${name} instanceof Promise)${name}=await ${name}\n` +
1041+
`if(${name}!==undefined){c.body=${name};used=true}\n`
1042+
}
9731043

974-
case 'urlencoded':
975-
case 'application/x-www-form-urlencoded':
976-
fnLiteral += adapter.parser.urlencoded(isOptionalBody)
977-
break
1044+
endUnit()
1045+
} else {
1046+
const endUnit = reporter.resolveChild(
1047+
hooks.parse[i].fn.name
1048+
)
9781049

979-
case 'arrayBuffer':
980-
case 'application/octet-stream':
981-
fnLiteral += adapter.parser.arrayBuffer(isOptionalBody)
982-
break
1050+
fnLiteral +=
1051+
`let ${name}=parse[${i}]\n` +
1052+
`${name}=${name}(c,contentType)\n` +
1053+
`if(${name} instanceof Promise)${name}=await ${name}\n` +
1054+
`if(${name}!==undefined){c.body=${name};used=true}`
9831055

984-
case 'formdata':
985-
case 'multipart/form-data':
986-
fnLiteral += adapter.parser.formData(isOptionalBody)
987-
break
1056+
endUnit()
9881057
}
989-
} else {
990-
fnLiteral +=
991-
`switch(contentType){` + `case 'application/json':\n`
1058+
1059+
if (i !== 0) fnLiteral += `}`
1060+
1061+
if (hasDefaultParser) break
1062+
}
1063+
1064+
reporter.resolve()
1065+
1066+
if (!hasDefaultParser) {
1067+
if (hooks.parse.length)
1068+
fnLiteral +=
1069+
`\nif(!used){\n` +
1070+
`if(!contentType) throw new ParseError()\n`
1071+
1072+
fnLiteral += `switch(contentType){`
9921073

9931074
fnLiteral +=
1075+
`case 'application/json':\n` +
9941076
adapter.parser.json(isOptionalBody) +
9951077
`break\n` +
9961078
`case 'text/plain':` +
@@ -1008,14 +1090,27 @@ export const composeHandler = ({
10081090
`case 'multipart/form-data':` +
10091091
adapter.parser.formData(isOptionalBody) +
10101092
`break` +
1011-
`}`
1012-
}
1093+
'\n'
10131094

1014-
if (hooks.parse.length) fnLiteral += `}`
1095+
for (const key of Object.keys(app.parser))
1096+
fnLiteral +=
1097+
`case '${key}':` +
1098+
`let bo${key}=parser['${key}'](c,contentType)\n` +
1099+
`if(bo${key} instanceof Promise)bo${key}=await bo${key}\n` +
1100+
`if(bo${key} instanceof ElysiaCustomStatusResponse)throw result\n` +
1101+
`if(bo${key}!==undefined)c.body=bo${key}\n` +
1102+
`break` +
1103+
'\n'
1104+
1105+
if (hooks.parse.length) fnLiteral += '}'
1106+
1107+
fnLiteral += '}'
1108+
}
10151109

1016-
fnLiteral += '}'
1110+
// fnLiteral += '}'
10171111
}
10181112

1113+
fnLiteral += '\ndelete c.contentType'
10191114
fnLiteral += '\nisParsing=false\n'
10201115
}
10211116

@@ -1809,6 +1904,7 @@ export const composeHandler = ({
18091904
`ElysiaCustomStatusResponse,` +
18101905
`ELYSIA_TRACE,` +
18111906
`ELYSIA_REQUEST_ID,` +
1907+
'parser,' +
18121908
`getServer,` +
18131909
adapterVariables +
18141910
'TypeBoxError' +
@@ -1862,15 +1958,13 @@ export const composeHandler = ({
18621958
// @ts-expect-error private property
18631959
getServer: () => app.getServer(),
18641960
TypeBoxError,
1961+
parser: app.parser,
18651962
...adapter.inject
18661963
})
18671964
} catch (error) {
18681965
const debugHooks = lifeCycleToFn(hooks)
18691966

18701967
console.log('[Composer] failed to generate optimized handler')
1871-
console.log(
1872-
'Please report the following to Elysia maintainers privately as it may include sensitive information about your codebase:'
1873-
)
18741968
console.log('---')
18751969
console.log({
18761970
handler:

src/error.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class ParseError extends Error {
8383
status = 400
8484

8585
constructor() {
86-
super('Failed to parse body')
86+
super('Bad Request')
8787
}
8888
}
8989

0 commit comments

Comments
 (0)