Repository: elysiajs/elysia (TypeScript)

Issue:
Nested t.Integer() validation too loose
### What version of Elysia is running?

1.2.10

### What platform is your computer?

Darwin 24.1.0 arm64 arm

### What steps can reproduce the bug?

wrote some tests for `test/validator/body.test.ts` to investigate `t.Integer()` validation that was passing through in my project. Found that it wasn't validating my integers at all and was only really checking there were numbers.

```typescript
	it('rejects malformed integer from array object', async () => {
		const app = new Elysia().post('/', ({ body }) => body, {
			body: t.Array(
				t.Object({
					name: t.String(),
					job: t.String(),
					trait: t.Optional(t.String()),
					age: t.Integer(),
					rank: t.Integer()
				})
			)
		})
		const res = await app.handle(
			post('/', [
				{
					name: 'sucrose',
					job: 'alchemist',
					age: 16.4,
					rank: 4
				}
			])
		)

		expect(res.status).toBe(422)
	})

	it('rejects malformed integer directly in array', async () => {
		const app = new Elysia().post('/', ({ body }) => body, {
			body: t.Array(t.Integer())
		})
		const res = await app.handle(post('/', [1, 2, 3, 4.2]))

		expect(res.status).toBe(422)
	})

```

found that they fail and pass through a 200. Seems like integer support is new #953 and this may have been an oversight?

### What is the expected behavior?

Elysia should validate the nested integer and return a 422

### What do you see instead?

Elysia passes through the non-integer to deeper layers, unvalidated.

### Additional information

_No response_

### Have you try removing the `node_modules` and `bun.lockb` and try again yet?

yes


Discussion:
I think it's due to using `t.Number()` as union in the pull request mentioned.

https://github.com/elysiajs/elysia/pull/953/files#r1893933305
It should be fixed by changing the union type to `Type.Integer` as the comment said.
I've passed error function inside `t.Number()` and it works.

```
  duration: t.Number({
    minimum: 1,
    maximum: 5,
    error() {
      return "Duration should be between 1 and 5";
    },
  }),
```

https://elysiajs.com/essential/validation.html#error-is-called-per-field



Patch:
diff --git a/src/type-system.ts b/src/type-system.ts
index f9bd4389..a12be6e6 100644
--- a/src/type-system.ts
+++ b/src/type-system.ts
@@ -376,7 +376,7 @@ export const ElysiaType = {
 							format: 'integer',
 							default: 0
 						}),
-						t.Number(property)
+						Type.Integer(property)
 					],
 					property
 				)


Test patch:
diff --git a/test/validator/body.test.ts b/test/validator/body.test.ts
index 04aca8ed..ae02d86c 100644
--- a/test/validator/body.test.ts
+++ b/test/validator/body.test.ts
@@ -194,6 +194,40 @@ describe('Body Validator', () => {
 		expect(res.status).toBe(200)
 	})
 
+	it('rejects malformed integer from array object', async () => {
+		const app = new Elysia().post('/', ({ body }) => body, {
+			body: t.Array(
+				t.Object({
+					name: t.String(),
+					job: t.String(),
+					trait: t.Optional(t.String()),
+					age: t.Integer(),
+					rank: t.Integer()
+				})
+			)
+		})
+		const res = await app.handle(
+			post('/', [
+				{
+					name: 'sucrose',
+					job: 'alchemist',
+					age: 16.4,
+					rank: 4
+				}
+			])
+		)
+
+		expect(res.status).toBe(422)
+	})
+
+	it('rejects malformed integer directly in array', async () => {
+		const app = new Elysia().post('/', ({ body }) => body, {
+			body: t.Array(t.Integer())
+		})
+		const res = await app.handle(post('/', [1, 2, 3, 4.2]))
+
+		expect(res.status).toBe(422)
+	})
 	it('validate empty body', async () => {
 		const app = new Elysia().post('/', ({ body }) => body, {
 			body: t.Union([
diff --git a/test/validator/params.test.ts b/test/validator/params.test.ts
index 37f33e36..3345ddb7 100644
--- a/test/validator/params.test.ts
+++ b/test/validator/params.test.ts
@@ -110,27 +110,40 @@ describe('Params Validator', () => {
 		})
 
 		const res = await app.handle(req('/id/617.1234'))
-		expect(await res.json()).toEqual({
+		expect(await res.json()).toMatchObject({
+			type: 'validation',
+			on: 'params',
+			summary: "Property 'id' should be one of: 'integer', 'integer'",
+			property: '/id',
+			message: 'Expected union value',
+			expected: {
+				id: 0
+			},
+			found: {
+				id: '617.1234'
+			},
 			errors: [
 				{
-					errors: [],
-					message: 'Expected integer',
-					path: '',
+					type: 62,
 					schema: {
-						type: 'integer'
+						anyOf: [
+							{
+								format: 'integer',
+								default: 0,
+								type: 'string'
+							},
+							{
+								type: 'integer'
+							}
+						]
 					},
-					summary: 'Expected integer',
-					type: 27,
-					value: 617.1234
+					path: '/id',
+					value: '617.1234',
+					message: 'Expected union value',
+					summary:
+						"Property 'id' should be one of: 'integer', 'integer'"
 				}
-			],
-			expected: 0,
-			found: 617.1234,
-			message: 'Expected integer',
-			on: 'property',
-			property: 'root',
-			summary: 'Expected integer',
-			type: 'validation'
+			]
 		})
 		expect(res.status).toBe(422)
 	})