Skip to content

Commit 728433e

Browse files
fix: enum types
1 parent a612acf commit 728433e

File tree

6 files changed

+73
-85
lines changed

6 files changed

+73
-85
lines changed

examples/enum-usage.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { ValidationType } from '../src'
2+
import { v } from '../src'
3+
4+
interface ValidationRule {
5+
rule: ValidationType
6+
}
7+
8+
const _sample: ValidationRule = {
9+
rule: v.enum(['a', 'b', 'c']).required(),
10+
}

src/types/enum.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Validator } from './base'
22

3-
export interface EnumValidatorType<T extends string | number> extends Validator<T> {
4-
getAllowedValues: () => readonly T[]
5-
custom: (fn: (value: T) => boolean, message: string) => EnumValidatorType<T>
3+
export interface EnumValidatorType extends Validator<string> {
4+
getAllowedValues: () => readonly string[]
5+
custom: (fn: (value: string) => boolean, message: string) => EnumValidatorType
66
}

src/types/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export interface ValidationInstance {
5656
bigint: () => BigintValidatorType
5757
array: <T>() => ArrayValidatorType<T>
5858
boolean: () => BooleanValidatorType
59-
enum: <T extends string | number>(values: readonly T[]) => EnumValidatorType<T>
59+
enum: (values: readonly string[]) => EnumValidatorType
6060
date: () => DateValidatorType
6161
datetime: () => DatetimeValidatorType
6262
object: <T extends Record<string, any>>(schema?: Record<string, Validator<any>>) => ObjectValidatorType<T>

src/validators/enums.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
import type { EnumValidatorType, ValidationNames } from '../types'
22
import { BaseValidator } from './base'
33

4-
export class EnumValidator<T extends string | number> extends BaseValidator<T> implements EnumValidatorType<T> {
4+
export class EnumValidator extends BaseValidator<string> implements EnumValidatorType {
55
public name: ValidationNames = 'enum'
66

7-
private allowedValues: readonly T[]
7+
private allowedValues: readonly string[]
88

9-
constructor(allowedValues: readonly T[]) {
9+
constructor(allowedValues: readonly string[]) {
1010
super()
1111
this.allowedValues = allowedValues
1212
this.addRule({
1313
name: 'enum',
14-
test: (value: T) => this.allowedValues.includes(value),
14+
test: (value: string) => this.allowedValues.includes(value),
1515
message: 'Must be one of: {values}',
1616
params: { values: this.allowedValues.join(', ') },
1717
})
1818
}
1919

20-
getAllowedValues(): readonly T[] {
20+
getAllowedValues(): readonly string[] {
2121
return this.allowedValues
2222
}
2323

24-
custom(fn: (value: T) => boolean, message: string): this {
24+
custom(fn: (value: string) => boolean, message: string): this {
2525
return this.addRule({
2626
name: 'custom',
2727
test: fn,
@@ -30,6 +30,6 @@ export class EnumValidator<T extends string | number> extends BaseValidator<T> i
3030
}
3131
}
3232

33-
export function enum_<T extends string | number>(allowedValues: readonly T[]): EnumValidator<T> {
33+
export function enum_(allowedValues: readonly string[]): EnumValidator {
3434
return new EnumValidator(allowedValues)
3535
}

test/validation.test.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -408,25 +408,16 @@ describe('Validation Library', () => {
408408

409409
describe('Enum Validator', () => {
410410
test('string enum validation', () => {
411-
const validator = v.enum(['red', 'green', 'blue'] as const)
411+
const validator = v.enum(['red', 'green', 'blue'])
412412
expect(validator.test('red')).toBe(true)
413413
expect(validator.test('green')).toBe(true)
414414
expect(validator.test('blue')).toBe(true)
415415
expect(validator.test('yellow' as any)).toBe(false)
416416
expect(validator.test(123 as any)).toBe(false)
417417
})
418418

419-
test('number enum validation', () => {
420-
const validator = v.enum([1, 2, 3] as const)
421-
expect(validator.test(1)).toBe(true)
422-
expect(validator.test(2)).toBe(true)
423-
expect(validator.test(3)).toBe(true)
424-
expect(validator.test(4 as any)).toBe(false)
425-
expect(validator.test('1' as any)).toBe(false)
426-
})
427-
428419
test('custom enum validation', () => {
429-
const validator = v.enum(['admin', 'user', 'guest'] as const).custom(
420+
const validator = v.enum(['admin', 'user', 'guest']).custom(
430421
value => value !== 'guest',
431422
'Guest access is not allowed',
432423
)
@@ -436,7 +427,7 @@ describe('Validation Library', () => {
436427
})
437428

438429
test('enum validation with readonly array', () => {
439-
const roles = ['admin', 'user', 'guest'] as const
430+
const roles = ['admin', 'user', 'guest']
440431
const validator = v.enum(roles)
441432
expect(validator.test('admin')).toBe(true)
442433
expect(validator.test('invalid' as any)).toBe(false)

test/validators/enums.test.ts

Lines changed: 49 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { enum_ } from '../../src/validators/enums'
44
describe('EnumValidator', () => {
55
describe('basic validation', () => {
66
test('should validate string enum values', () => {
7-
const validator = enum_(['red', 'green', 'blue'] as const)
7+
const validator = enum_(['red', 'green', 'blue'])
88
expect(validator.test('red')).toBe(true)
99
expect(validator.test('green')).toBe(true)
1010
expect(validator.test('blue')).toBe(true)
@@ -13,37 +13,24 @@ describe('EnumValidator', () => {
1313
expect(validator.test('' as any)).toBe(true) // empty string is valid when optional
1414
})
1515

16-
test('should validate number enum values', () => {
17-
const validator = enum_([1, 2, 3] as const)
18-
expect(validator.test(1)).toBe(true)
19-
expect(validator.test(2)).toBe(true)
20-
expect(validator.test(3)).toBe(true)
21-
expect(validator.test(4 as any)).toBe(false)
22-
expect(validator.test(0 as any)).toBe(false)
23-
expect(validator.test('1' as any)).toBe(false) // type sensitive
24-
})
25-
2616
test('should have correct name', () => {
27-
const validator = enum_(['a', 'b'] as const)
17+
const validator = enum_(['a', 'b'])
2818
expect(validator.name).toBe('enum')
2919
})
3020
})
3121

32-
describe('mixed type enums', () => {
33-
test('should validate mixed string and number enums', () => {
34-
const validator = enum_(['active', 'inactive', 1, 2] as const)
22+
describe('string enums', () => {
23+
test('should validate mixed string enums', () => {
24+
const validator = enum_(['active', 'inactive', 'pending', 'completed'])
3525
expect(validator.test('active')).toBe(true)
3626
expect(validator.test('inactive')).toBe(true)
37-
expect(validator.test(1)).toBe(true)
38-
expect(validator.test(2)).toBe(true)
39-
expect(validator.test('1' as any)).toBe(false)
40-
expect(validator.test('2' as any)).toBe(false)
41-
expect(validator.test(3 as any)).toBe(false)
42-
expect(validator.test('pending' as any)).toBe(false)
27+
expect(validator.test('pending')).toBe(true)
28+
expect(validator.test('completed')).toBe(true)
29+
expect(validator.test('archived' as any)).toBe(false)
4330
})
4431

4532
test('should handle string representations of booleans', () => {
46-
const validator = enum_(['true', 'false', 'maybe'] as const)
33+
const validator = enum_(['true', 'false', 'maybe'])
4734
expect(validator.test('true')).toBe(true)
4835
expect(validator.test('false')).toBe(true)
4936
expect(validator.test('maybe')).toBe(true)
@@ -56,7 +43,7 @@ describe('EnumValidator', () => {
5643

5744
describe('real-world enum scenarios', () => {
5845
test('should validate user roles', () => {
59-
const roleValidator = enum_(['admin', 'user', 'guest', 'moderator'] as const)
46+
const roleValidator = enum_(['admin', 'user', 'guest', 'moderator'])
6047
expect(roleValidator.test('admin')).toBe(true)
6148
expect(roleValidator.test('user')).toBe(true)
6249
expect(roleValidator.test('guest')).toBe(true)
@@ -66,16 +53,16 @@ describe('EnumValidator', () => {
6653
})
6754

6855
test('should validate status codes', () => {
69-
const statusValidator = enum_([200, 201, 400, 401, 403, 404, 500] as const)
70-
expect(statusValidator.test(200)).toBe(true)
71-
expect(statusValidator.test(404)).toBe(true)
72-
expect(statusValidator.test(500)).toBe(true)
73-
expect(statusValidator.test(418 as any)).toBe(false) // I'm a teapot
74-
expect(statusValidator.test('200' as any)).toBe(false)
56+
const statusValidator = enum_(['200', '201', '400', '401', '403', '404', '500'])
57+
expect(statusValidator.test('200')).toBe(true)
58+
expect(statusValidator.test('404')).toBe(true)
59+
expect(statusValidator.test('500')).toBe(true)
60+
expect(statusValidator.test('418' as any)).toBe(false) // I'm a teapot
61+
expect(statusValidator.test(200 as any)).toBe(false)
7562
})
7663

7764
test('should validate priority levels', () => {
78-
const priorityValidator = enum_(['low', 'medium', 'high', 'urgent'] as const)
65+
const priorityValidator = enum_(['low', 'medium', 'high', 'urgent'])
7966
expect(priorityValidator.test('low')).toBe(true)
8067
expect(priorityValidator.test('medium')).toBe(true)
8168
expect(priorityValidator.test('high')).toBe(true)
@@ -85,7 +72,7 @@ describe('EnumValidator', () => {
8572
})
8673

8774
test('should validate file types', () => {
88-
const fileTypeValidator = enum_(['.jpg', '.png', '.gif', '.pdf', '.doc'] as const)
75+
const fileTypeValidator = enum_(['.jpg', '.png', '.gif', '.pdf', '.doc'])
8976
expect(fileTypeValidator.test('.jpg')).toBe(true)
9077
expect(fileTypeValidator.test('.png')).toBe(true)
9178
expect(fileTypeValidator.test('.pdf')).toBe(true)
@@ -96,7 +83,7 @@ describe('EnumValidator', () => {
9683

9784
describe('custom validation', () => {
9885
test('custom() should accept custom validation functions', () => {
99-
const validator = enum_(['small', 'medium', 'large'] as const).custom(
86+
const validator = enum_(['small', 'medium', 'large']).custom(
10087
value => value !== 'medium',
10188
'Medium size not available',
10289
)
@@ -107,7 +94,7 @@ describe('EnumValidator', () => {
10794
})
10895

10996
test('should provide custom error messages', () => {
110-
const validator = enum_(['read', 'write', 'execute'] as const).custom(
97+
const validator = enum_(['read', 'write', 'execute']).custom(
11198
value => value !== 'execute',
11299
'Execute permission not allowed',
113100
)
@@ -120,7 +107,7 @@ describe('EnumValidator', () => {
120107
})
121108

122109
test('should chain enum and custom validations', () => {
123-
const validator = enum_(['bronze', 'silver', 'gold', 'platinum'] as const).custom(
110+
const validator = enum_(['bronze', 'silver', 'gold', 'platinum']).custom(
124111
value => value !== 'bronze',
125112
'Bronze tier not supported',
126113
)
@@ -134,43 +121,43 @@ describe('EnumValidator', () => {
134121

135122
describe('getAllowedValues method', () => {
136123
test('should return all allowed values', () => {
137-
const values = ['apple', 'banana', 'cherry'] as const
124+
const values = ['apple', 'banana', 'cherry']
138125
const validator = enum_(values)
139126
expect(validator.getAllowedValues()).toEqual(values)
140127
})
141128

142-
test('should return numeric values correctly', () => {
143-
const values = [1, 2, 3, 5, 8] as const
129+
test('should return string values correctly', () => {
130+
const values = ['one', 'two', 'three', 'five', 'eight']
144131
const validator = enum_(values)
145132
expect(validator.getAllowedValues()).toEqual(values)
146133
})
147134

148-
test('should return mixed type values correctly', () => {
149-
const values = ['active', 1, 'inactive', 0] as const
135+
test('should return mixed string values correctly', () => {
136+
const values = ['active', 'one', 'inactive', 'zero']
150137
const validator = enum_(values)
151138
expect(validator.getAllowedValues()).toEqual(values)
152139
})
153140
})
154141

155142
describe('required and optional', () => {
156143
test('required() should reject null/undefined', () => {
157-
const validator = enum_(['yes', 'no'] as const).required()
144+
const validator = enum_(['yes', 'no']).required()
158145
expect(validator.test('yes')).toBe(true)
159146
expect(validator.test('no')).toBe(true)
160147
expect(validator.test(null as any)).toBe(false) // required validator should reject null
161148
expect(validator.test(undefined as any)).toBe(false) // required validator should reject undefined
162149
})
163150

164151
test('optional() should accept null/undefined', () => {
165-
const validator = enum_(['yes', 'no'] as const).optional()
152+
const validator = enum_(['yes', 'no']).optional()
166153
expect(validator.test('yes')).toBe(true)
167154
expect(validator.test('no')).toBe(true)
168155
expect(validator.test(null as any)).toBe(true)
169156
expect(validator.test(undefined as any)).toBe(true)
170157
})
171158

172159
test('should work with custom validations when optional', () => {
173-
const validator = enum_(['small', 'large'] as const)
160+
const validator = enum_(['small', 'large'])
174161
.optional()
175162
.custom(value => value !== 'small', 'Small not allowed')
176163

@@ -184,7 +171,7 @@ describe('EnumValidator', () => {
184171

185172
describe('validation results', () => {
186173
test('should return detailed validation results', () => {
187-
const validator = enum_(['cat', 'dog', 'bird'] as const)
174+
const validator = enum_(['cat', 'dog', 'bird'])
188175
const result = validator.validate('fish' as any)
189176
expect(result.valid).toBe(false)
190177
expect(Array.isArray(result.errors)).toBe(true)
@@ -194,7 +181,7 @@ describe('EnumValidator', () => {
194181
})
195182

196183
test('should return multiple errors for multiple failed validations', () => {
197-
const validator = enum_(['xs', 's', 'm', 'l', 'xl'] as const).custom(
184+
const validator = enum_(['xs', 's', 'm', 'l', 'xl']).custom(
198185
value => value !== 'xs',
199186
'XS size unavailable',
200187
)
@@ -209,17 +196,17 @@ describe('EnumValidator', () => {
209196

210197
describe('edge cases', () => {
211198
test('should handle single value enums', () => {
212-
const stringValidator = enum_(['only'] as const)
199+
const stringValidator = enum_(['only'])
213200
expect(stringValidator.test('only')).toBe(true)
214201
expect(stringValidator.test('other' as any)).toBe(false)
215202

216-
const numberValidator = enum_([42] as const)
217-
expect(numberValidator.test(42)).toBe(true)
218-
expect(numberValidator.test(41 as any)).toBe(false)
203+
const singleValidator = enum_(['single'])
204+
expect(singleValidator.test('single')).toBe(true)
205+
expect(singleValidator.test('other' as any)).toBe(false)
219206
})
220207

221208
test('should handle special string values', () => {
222-
const validator = enum_(['', ' ', '\n', '\t', 'null', 'undefined'] as const)
209+
const validator = enum_(['', ' ', '\n', '\t', 'null', 'undefined'])
223210
expect(validator.test('')).toBe(true)
224211
expect(validator.test(' ')).toBe(true)
225212
expect(validator.test('\n')).toBe(true)
@@ -230,19 +217,19 @@ describe('EnumValidator', () => {
230217
expect(validator.test(undefined as any)).toBe(true) // undefined is valid when optional
231218
})
232219

233-
test('should handle numeric edge cases', () => {
234-
const validator = enum_([0, -1, 3.14, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY] as const)
235-
expect(validator.test(0)).toBe(true)
236-
expect(validator.test(-1)).toBe(true)
237-
expect(validator.test(3.14)).toBe(true)
238-
expect(validator.test(Number.POSITIVE_INFINITY)).toBe(true)
239-
expect(validator.test(Number.NEGATIVE_INFINITY)).toBe(true)
240-
expect(validator.test(Number.NaN as any)).toBe(false)
241-
expect(validator.test(1 as any)).toBe(false)
220+
test('should handle string edge cases', () => {
221+
const validator = enum_(['0', '-1', '3.14', 'infinity', '-infinity'])
222+
expect(validator.test('0')).toBe(true)
223+
expect(validator.test('-1')).toBe(true)
224+
expect(validator.test('3.14')).toBe(true)
225+
expect(validator.test('infinity')).toBe(true)
226+
expect(validator.test('-infinity')).toBe(true)
227+
expect(validator.test('NaN' as any)).toBe(false)
228+
expect(validator.test('1' as any)).toBe(false)
242229
})
243230

244231
test('should handle unicode strings', () => {
245-
const validator = enum_(['🚀', '🌟', '✨', 'café', 'naïve'] as const)
232+
const validator = enum_(['🚀', '🌟', '✨', 'café', 'naïve'])
246233
expect(validator.test('🚀')).toBe(true)
247234
expect(validator.test('🌟')).toBe(true)
248235
expect(validator.test('✨')).toBe(true)
@@ -253,7 +240,7 @@ describe('EnumValidator', () => {
253240
})
254241

255242
test('should be case and type sensitive', () => {
256-
const validator = enum_(['True', 'False', '0', '1'] as const)
243+
const validator = enum_(['True', 'False', '0', '1'])
257244
expect(validator.test('True')).toBe(true)
258245
expect(validator.test('False')).toBe(true)
259246
expect(validator.test('0')).toBe(true)
@@ -269,15 +256,15 @@ describe('EnumValidator', () => {
269256

270257
describe('type safety', () => {
271258
test('should work with readonly arrays', () => {
272-
const colors = ['red', 'green', 'blue'] as const
259+
const colors = ['red', 'green', 'blue']
273260
const validator = enum_(colors)
274261
expect(validator.test('red')).toBe(true)
275262
expect(validator.test('purple' as any)).toBe(false)
276263
expect(validator.getAllowedValues()).toEqual(colors)
277264
})
278265

279266
test('should maintain type information', () => {
280-
const sizes = ['xs', 's', 'm', 'l', 'xl'] as const
267+
const sizes = ['xs', 's', 'm', 'l', 'xl']
281268
const validator = enum_(sizes)
282269
// TypeScript would enforce that only these values are valid
283270
expect(validator.test('m')).toBe(true)

0 commit comments

Comments
 (0)