Skip to content

Commit cb8c979

Browse files
committed
Added @typeschema/class-validator
Use arktype as a starting point due to no json schema support WIP Test coverage Fixed class-validator tests PR Feedback
1 parent 10310ff commit cb8c979

File tree

7 files changed

+176
-1
lines changed

7 files changed

+176
-1
lines changed

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@
105105
"@exodus/schemasafe": "^1.3.0",
106106
"@sinclair/typebox": ">=0.32.30 <1",
107107
"@sveltejs/kit": "1.x || 2.x",
108+
"@typeschema/class-validator": "^0.2.0",
109+
"class-validator": "^0.14.1",
108110
"@vinejs/vine": "^1.8.0",
109111
"arktype": ">=2.0.0-beta.0",
110112
"joi": "^17.13.1",
@@ -121,6 +123,12 @@
121123
"@sinclair/typebox": {
122124
"optional": true
123125
},
126+
"@typeschema/class-validator": {
127+
"optional": true
128+
},
129+
"class-validator": {
130+
"optional": true
131+
},
124132
"arktype": {
125133
"optional": true
126134
},
@@ -148,6 +156,8 @@
148156
"@gcornut/valibot-json-schema": "^0.31.0",
149157
"@sinclair/typebox": "^0.32.35",
150158
"@sodaru/yup-to-json-schema": "^2.0.1",
159+
"@typeschema/class-validator": "^0.2.0",
160+
"class-validator": "^0.14.1",
151161
"@vinejs/vine": "^1.8.0",
152162
"arktype": "2.0.0-beta.0",
153163
"joi": "^17.13.3",

pnpm-lock.yaml

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/adapters/adapters.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type InferIn<T extends Schema> = NonNullable<InferInSchema<T>>;
2020

2121
export type ValidationLibrary =
2222
| 'arktype'
23+
| 'classValidator'
2324
| 'custom'
2425
| 'joi'
2526
| 'superform'
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
createAdapter,
3+
createJsonSchema,
4+
type ValidationAdapter,
5+
type Infer,
6+
type InferIn,
7+
type ValidationResult,
8+
type ClientValidationAdapter,
9+
type RequiredDefaultsOptions,
10+
} from './adapters.js';
11+
import { memoize } from '$lib/memoize.js';
12+
13+
import {
14+
validate as classValidatorValidate,
15+
type Schema
16+
} from '@typeschema/class-validator';
17+
18+
async function validate<T extends Schema>(
19+
schema: T,
20+
data: unknown
21+
): Promise<ValidationResult<Infer<T>>> {
22+
const result = await classValidatorValidate<T>(schema, data);
23+
if (result.success) {
24+
return {
25+
data: result.data as Infer<T>,
26+
success: true
27+
};
28+
}
29+
return {
30+
issues: result.issues.map(({ message, path }) => ({
31+
message,
32+
path
33+
})),
34+
success: false
35+
};
36+
}
37+
38+
function _classValidator<T extends Schema>(
39+
schema: T,
40+
options: RequiredDefaultsOptions<Infer<T>>
41+
): ValidationAdapter<Infer<T>, InferIn<T>> {
42+
return createAdapter({
43+
superFormValidationLibrary: 'classValidator',
44+
validate: async (data: unknown) => validate(schema, data),
45+
jsonSchema: createJsonSchema(options),
46+
defaults: options.defaults
47+
});
48+
}
49+
50+
function _classValidatorClient<T extends Schema>(
51+
schema: T
52+
): ClientValidationAdapter<Infer<T>, InferIn<T>> {
53+
return {
54+
superFormValidationLibrary: 'classValidator',
55+
validate: async (data) => validate(schema, data)
56+
};
57+
}
58+
59+
export const classValidator = /* @__PURE__ */ memoize(_classValidator);
60+
export const classValidatorClient = /* @__PURE__ */ memoize(_classValidatorClient);

src/lib/adapters/typeSchema.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import type { TSchema, Static as Static$1 } from '@sinclair/typebox';
22
import type { Type } from 'arktype';
33
import type { AnySchema } from 'joi';
4+
import type {
5+
Infer as ClassValidatorInfer,
6+
InferIn as ClassValidatorInferIn,
7+
Schema as ClassValidatorSchema
8+
} from '@typeschema/class-validator';
9+
410
import type {
511
GenericSchema,
612
GenericSchemaAsync,
@@ -72,6 +78,12 @@ interface ArkTypeResolver extends Resolver {
7278
output: this['schema'] extends Type ? this['schema']['infer'] : never;
7379
}
7480

81+
interface ClassValidatorResolver extends Resolver {
82+
base: ClassValidatorSchema;
83+
input: this['schema'] extends ClassValidatorSchema ? ClassValidatorInferIn<this['schema']> : never;
84+
output: this['schema'] extends ClassValidatorSchema ? ClassValidatorInfer<this['schema']> : never;
85+
}
86+
7587
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7688
type CustomSchema<T = any> = (data: unknown) => Promise<T> | T;
7789
interface CustomResolver extends Resolver {
@@ -179,6 +191,7 @@ interface RuntypesResolver extends Resolver {
179191

180192
type Registry = {
181193
arktype: ArkTypeResolver;
194+
classValidator: ClassValidatorResolver;
182195
custom: CustomResolver;
183196
joi: JoiResolver;
184197
typebox: TypeBoxResolver;

src/tests/superValidate.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import { z, type ZodErrorMap } from 'zod';
2323
import { valibot } from '$lib/adapters/valibot.js';
2424
import * as v from 'valibot';
2525

26+
import { classValidator } from '$lib/adapters/class-validator.js';
27+
import { ArrayMinSize, IsOptional, IsString, IsEmail, IsArray, MinLength, IsInt, Min, IsDate, Matches } from 'class-validator';
28+
2629
//import { ajv } from '$lib/adapters/ajv.js';
2730
//import type { JSONSchema } from '$lib/jsonSchema/index.js';
2831

@@ -70,7 +73,7 @@ import { SchemaError, type JSONSchema } from '$lib/index.js';
7073

7174
///// Test data /////////////////////////////////////////////////////
7275

73-
/*
76+
/*
7477
TEST SCHEMA TEMPLATE:
7578
7679
| field | type | opt/null | constraints | default |
@@ -467,6 +470,40 @@ describe('Valibot', () => {
467470
*/
468471
});
469472

473+
describe('class-validator', () => {
474+
class ClassValidatorSchema {
475+
@IsString()
476+
name: string = 'Unknown';
477+
478+
@IsString()
479+
@IsEmail()
480+
email: string | undefined;
481+
482+
@IsArray()
483+
@MinLength(2, { each: true })
484+
@ArrayMinSize(3)
485+
tags: string[] | undefined;
486+
487+
@IsInt()
488+
@Min(0)
489+
score: number | undefined;
490+
491+
@IsDate()
492+
date: Date | undefined;
493+
494+
@IsOptional()
495+
@Matches(/^\S*$/, { message: 'No spaces allowed' })
496+
nospace: string | undefined;
497+
498+
@IsOptional()
499+
@IsString()
500+
extra: string | null = null;
501+
}
502+
503+
const adapter = classValidator(ClassValidatorSchema, { defaults });
504+
schemaTest(adapter, ['email', 'date', 'nospace', 'tags'], 'simple');
505+
});
506+
470507
/////////////////////////////////////////////////////////////////////
471508

472509
// ajv is disabled due to no ESM compatibility.

tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"allowJs": true,
55
"checkJs": true,
66
"esModuleInterop": true,
7+
"emitDecoratorMetadata": true,
8+
"experimentalDecorators": true,
79
"forceConsistentCasingInFileNames": true,
810
"resolveJsonModule": true,
911
"skipLibCheck": true,

0 commit comments

Comments
 (0)