@agentuity/schema 0.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +86 -0
- package/README.md +323 -0
- package/dist/base.d.ts +111 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +93 -0
- package/dist/base.js.map +1 -0
- package/dist/coerce/boolean.d.ts +37 -0
- package/dist/coerce/boolean.d.ts.map +1 -0
- package/dist/coerce/boolean.js +49 -0
- package/dist/coerce/boolean.js.map +1 -0
- package/dist/coerce/date.d.ts +36 -0
- package/dist/coerce/date.d.ts.map +1 -0
- package/dist/coerce/date.js +60 -0
- package/dist/coerce/date.js.map +1 -0
- package/dist/coerce/number.d.ts +36 -0
- package/dist/coerce/number.d.ts.map +1 -0
- package/dist/coerce/number.js +59 -0
- package/dist/coerce/number.js.map +1 -0
- package/dist/coerce/string.d.ts +35 -0
- package/dist/coerce/string.d.ts.map +1 -0
- package/dist/coerce/string.js +47 -0
- package/dist/coerce/string.js.map +1 -0
- package/dist/complex/array.d.ts +56 -0
- package/dist/complex/array.d.ts.map +1 -0
- package/dist/complex/array.js +96 -0
- package/dist/complex/array.js.map +1 -0
- package/dist/complex/object.d.ts +76 -0
- package/dist/complex/object.d.ts.map +1 -0
- package/dist/complex/object.js +104 -0
- package/dist/complex/object.js.map +1 -0
- package/dist/complex/record.d.ts +53 -0
- package/dist/complex/record.d.ts.map +1 -0
- package/dist/complex/record.js +109 -0
- package/dist/complex/record.js.map +1 -0
- package/dist/index.d.ts +151 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -0
- package/dist/json-schema.d.ts +60 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +280 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/primitives/any.d.ts +44 -0
- package/dist/primitives/any.d.ts.map +1 -0
- package/dist/primitives/any.js +57 -0
- package/dist/primitives/any.js.map +1 -0
- package/dist/primitives/boolean.d.ts +39 -0
- package/dist/primitives/boolean.d.ts.map +1 -0
- package/dist/primitives/boolean.js +53 -0
- package/dist/primitives/boolean.js.map +1 -0
- package/dist/primitives/null.d.ts +26 -0
- package/dist/primitives/null.d.ts.map +1 -0
- package/dist/primitives/null.js +40 -0
- package/dist/primitives/null.js.map +1 -0
- package/dist/primitives/number.d.ts +87 -0
- package/dist/primitives/number.d.ts.map +1 -0
- package/dist/primitives/number.js +129 -0
- package/dist/primitives/number.js.map +1 -0
- package/dist/primitives/string.d.ts +64 -0
- package/dist/primitives/string.d.ts.map +1 -0
- package/dist/primitives/string.js +102 -0
- package/dist/primitives/string.js.map +1 -0
- package/dist/primitives/undefined.d.ts +26 -0
- package/dist/primitives/undefined.d.ts.map +1 -0
- package/dist/primitives/undefined.js +40 -0
- package/dist/primitives/undefined.js.map +1 -0
- package/dist/primitives/unknown.d.ts +47 -0
- package/dist/primitives/unknown.d.ts.map +1 -0
- package/dist/primitives/unknown.js +56 -0
- package/dist/primitives/unknown.js.map +1 -0
- package/dist/utils/literal.d.ts +47 -0
- package/dist/utils/literal.d.ts.map +1 -0
- package/dist/utils/literal.js +64 -0
- package/dist/utils/literal.js.map +1 -0
- package/dist/utils/nullable.d.ts +50 -0
- package/dist/utils/nullable.d.ts.map +1 -0
- package/dist/utils/nullable.js +69 -0
- package/dist/utils/nullable.js.map +1 -0
- package/dist/utils/optional.d.ts +50 -0
- package/dist/utils/optional.d.ts.map +1 -0
- package/dist/utils/optional.js +69 -0
- package/dist/utils/optional.js.map +1 -0
- package/dist/utils/union.d.ts +60 -0
- package/dist/utils/union.d.ts.map +1 -0
- package/dist/utils/union.js +87 -0
- package/dist/utils/union.js.map +1 -0
- package/package.json +39 -0
- package/src/__tests__/coerce.test.ts +88 -0
- package/src/__tests__/complex.test.ts +124 -0
- package/src/__tests__/errors.test.ts +129 -0
- package/src/__tests__/json-schema.test.ts +138 -0
- package/src/__tests__/primitives.test.ts +184 -0
- package/src/__tests__/type-inference.test.ts +68 -0
- package/src/__tests__/utils.test.ts +100 -0
- package/src/base.ts +185 -0
- package/src/coerce/boolean.ts +56 -0
- package/src/coerce/date.ts +68 -0
- package/src/coerce/number.ts +67 -0
- package/src/coerce/string.ts +54 -0
- package/src/complex/array.ts +108 -0
- package/src/complex/object.ts +141 -0
- package/src/complex/record.ts +129 -0
- package/src/index.ts +177 -0
- package/src/json-schema.ts +331 -0
- package/src/primitives/any.ts +64 -0
- package/src/primitives/boolean.ts +60 -0
- package/src/primitives/null.ts +47 -0
- package/src/primitives/number.ts +141 -0
- package/src/primitives/string.ts +113 -0
- package/src/primitives/undefined.ts +47 -0
- package/src/primitives/unknown.ts +63 -0
- package/src/utils/literal.ts +71 -0
- package/src/utils/nullable.ts +80 -0
- package/src/utils/optional.ts +80 -0
- package/src/utils/union.ts +103 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { s, ValidationError } from '../index.js';
|
|
3
|
+
|
|
4
|
+
describe('Error Handling', () => {
|
|
5
|
+
describe('ValidationError', () => {
|
|
6
|
+
test('should contain issues array', () => {
|
|
7
|
+
const schema = s.string();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
schema.parse(123);
|
|
11
|
+
throw new Error('Expected parse to throw');
|
|
12
|
+
} catch (error) {
|
|
13
|
+
expect(error).toBeInstanceOf(ValidationError);
|
|
14
|
+
if (error instanceof ValidationError) {
|
|
15
|
+
expect(Array.isArray(error.issues)).toBe(true);
|
|
16
|
+
expect(error.issues.length).toBeGreaterThan(0);
|
|
17
|
+
} else {
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('should include field paths', () => {
|
|
24
|
+
const schema = s.object({
|
|
25
|
+
user: s.object({
|
|
26
|
+
name: s.string(),
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
schema.parse({
|
|
32
|
+
user: {
|
|
33
|
+
name: 123,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
throw new Error('Expected parse to throw');
|
|
37
|
+
} catch (error) {
|
|
38
|
+
expect(error).toBeInstanceOf(ValidationError);
|
|
39
|
+
if (error instanceof ValidationError) {
|
|
40
|
+
expect(error.issues[0].path).toEqual(['user', 'name']);
|
|
41
|
+
} else {
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should have readable error message', () => {
|
|
48
|
+
const schema = s.object({
|
|
49
|
+
age: s.number(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
schema.parse({ age: 'invalid' });
|
|
54
|
+
throw new Error('Expected parse to throw');
|
|
55
|
+
} catch (error) {
|
|
56
|
+
expect(error).toBeInstanceOf(ValidationError);
|
|
57
|
+
if (error instanceof ValidationError) {
|
|
58
|
+
expect(error.message).toContain('age');
|
|
59
|
+
expect(error.message).toContain('number');
|
|
60
|
+
} else {
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('safeParse', () => {
|
|
68
|
+
test('should not throw on invalid data', () => {
|
|
69
|
+
const schema = s.string();
|
|
70
|
+
const result = schema.safeParse(123);
|
|
71
|
+
|
|
72
|
+
expect(result.success).toBe(false);
|
|
73
|
+
if (!result.success) {
|
|
74
|
+
expect(result.error).toBeInstanceOf(ValidationError);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should return data on valid input', () => {
|
|
79
|
+
const schema = s.string();
|
|
80
|
+
const result = schema.safeParse('hello');
|
|
81
|
+
|
|
82
|
+
expect(result.success).toBe(true);
|
|
83
|
+
if (result.success) {
|
|
84
|
+
expect(result.data).toBe('hello');
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('parse', () => {
|
|
90
|
+
test('should throw ValidationError on invalid data', () => {
|
|
91
|
+
const schema = s.number();
|
|
92
|
+
|
|
93
|
+
expect(() => schema.parse('not-a-number')).toThrow(ValidationError);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('should return typed data on valid input', () => {
|
|
97
|
+
const schema = s.number();
|
|
98
|
+
const result = schema.parse(42);
|
|
99
|
+
|
|
100
|
+
expect(result).toBe(42);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('multiple errors', () => {
|
|
105
|
+
test('should collect all validation errors', () => {
|
|
106
|
+
const schema = s.object({
|
|
107
|
+
name: s.string(),
|
|
108
|
+
age: s.number(),
|
|
109
|
+
email: s.string(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
schema.parse({
|
|
114
|
+
name: 123,
|
|
115
|
+
age: 'invalid',
|
|
116
|
+
email: false,
|
|
117
|
+
});
|
|
118
|
+
throw new Error('Expected parse to throw');
|
|
119
|
+
} catch (error) {
|
|
120
|
+
expect(error).toBeInstanceOf(ValidationError);
|
|
121
|
+
if (error instanceof ValidationError) {
|
|
122
|
+
expect(error.issues.length).toBe(3);
|
|
123
|
+
} else {
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { s } from '../index.js';
|
|
3
|
+
|
|
4
|
+
describe('JSON Schema Conversion', () => {
|
|
5
|
+
describe('toJSONSchema', () => {
|
|
6
|
+
test('should convert primitive types', () => {
|
|
7
|
+
const stringSchema = s.toJSONSchema(s.string());
|
|
8
|
+
expect(stringSchema.type).toBe('string');
|
|
9
|
+
|
|
10
|
+
const numberSchema = s.toJSONSchema(s.number());
|
|
11
|
+
expect(numberSchema.type).toBe('number');
|
|
12
|
+
|
|
13
|
+
const booleanSchema = s.toJSONSchema(s.boolean());
|
|
14
|
+
expect(booleanSchema.type).toBe('boolean');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('should convert object schemas', () => {
|
|
18
|
+
const schema = s.object({
|
|
19
|
+
name: s.string(),
|
|
20
|
+
age: s.number(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const jsonSchema = s.toJSONSchema(schema);
|
|
24
|
+
expect(jsonSchema.type).toBe('object');
|
|
25
|
+
expect(jsonSchema.properties).toHaveProperty('name');
|
|
26
|
+
expect(jsonSchema.properties).toHaveProperty('age');
|
|
27
|
+
expect(jsonSchema.required).toContain('name');
|
|
28
|
+
expect(jsonSchema.required).toContain('age');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should convert array schemas', () => {
|
|
32
|
+
const schema = s.array(s.string());
|
|
33
|
+
const jsonSchema = s.toJSONSchema(schema);
|
|
34
|
+
|
|
35
|
+
expect(jsonSchema.type).toBe('array');
|
|
36
|
+
expect(jsonSchema.items).toHaveProperty('type', 'string');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should preserve descriptions', () => {
|
|
40
|
+
const schema = s.string().describe('A test string');
|
|
41
|
+
const jsonSchema = s.toJSONSchema(schema);
|
|
42
|
+
|
|
43
|
+
expect(jsonSchema.description).toBe('A test string');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('should handle optional fields', () => {
|
|
47
|
+
const schema = s.object({
|
|
48
|
+
required: s.string(),
|
|
49
|
+
optional: s.optional(s.string()),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const jsonSchema = s.toJSONSchema(schema);
|
|
53
|
+
expect(jsonSchema.required).toContain('required');
|
|
54
|
+
expect(jsonSchema.required).not.toContain('optional');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('should handle nullable fields', () => {
|
|
58
|
+
const schema = s.nullable(s.string());
|
|
59
|
+
const jsonSchema = s.toJSONSchema(schema);
|
|
60
|
+
|
|
61
|
+
expect(jsonSchema.anyOf).toHaveLength(2);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('fromJSONSchema', () => {
|
|
66
|
+
test('should convert primitive types', () => {
|
|
67
|
+
const stringSchema = s.fromJSONSchema({ type: 'string' });
|
|
68
|
+
expect(stringSchema.parse('hello')).toBe('hello');
|
|
69
|
+
|
|
70
|
+
const numberSchema = s.fromJSONSchema({ type: 'number' });
|
|
71
|
+
expect(numberSchema.parse(123)).toBe(123);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('should convert object schemas', () => {
|
|
75
|
+
const jsonSchema = {
|
|
76
|
+
type: 'object' as const,
|
|
77
|
+
properties: {
|
|
78
|
+
name: { type: 'string' as const },
|
|
79
|
+
age: { type: 'number' as const },
|
|
80
|
+
},
|
|
81
|
+
required: ['name', 'age'],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const schema = s.fromJSONSchema(jsonSchema);
|
|
85
|
+
const result = schema.parse({
|
|
86
|
+
name: 'John',
|
|
87
|
+
age: 30,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(result).toEqual({ name: 'John', age: 30 });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('should convert array schemas', () => {
|
|
94
|
+
const jsonSchema = {
|
|
95
|
+
type: 'array' as const,
|
|
96
|
+
items: { type: 'string' as const },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const schema = s.fromJSONSchema(jsonSchema);
|
|
100
|
+
const result = schema.parse(['a', 'b', 'c']);
|
|
101
|
+
|
|
102
|
+
expect(result).toEqual(['a', 'b', 'c']);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should handle enum values', () => {
|
|
106
|
+
const jsonSchema = {
|
|
107
|
+
enum: ['red', 'green', 'blue'],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const schema = s.fromJSONSchema(jsonSchema);
|
|
111
|
+
expect(schema.parse('red')).toBe('red');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('round-trip conversion', () => {
|
|
116
|
+
test('should preserve schema through round-trip', () => {
|
|
117
|
+
const original = s.object({
|
|
118
|
+
name: s.string(),
|
|
119
|
+
age: s.number(),
|
|
120
|
+
tags: s.array(s.string()),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const jsonSchema = s.toJSONSchema(original);
|
|
124
|
+
const reconstructed = s.fromJSONSchema(jsonSchema);
|
|
125
|
+
|
|
126
|
+
const testData = {
|
|
127
|
+
name: 'Test',
|
|
128
|
+
age: 25,
|
|
129
|
+
tags: ['tag1', 'tag2'],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const originalResult = original.parse(testData);
|
|
133
|
+
const reconstructedResult = reconstructed.parse(testData);
|
|
134
|
+
|
|
135
|
+
expect(reconstructedResult).toEqual(originalResult);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { s, ValidationError } from '../index.js';
|
|
3
|
+
|
|
4
|
+
describe('Primitive Schemas', () => {
|
|
5
|
+
describe('string', () => {
|
|
6
|
+
const schema = s.string();
|
|
7
|
+
|
|
8
|
+
test('should validate strings', () => {
|
|
9
|
+
expect(schema.parse('hello')).toBe('hello');
|
|
10
|
+
expect(schema.parse('')).toBe('');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('should reject non-strings', () => {
|
|
14
|
+
expect(() => schema.parse(123)).toThrow(ValidationError);
|
|
15
|
+
expect(() => schema.parse(true)).toThrow(ValidationError);
|
|
16
|
+
expect(() => schema.parse(null)).toThrow(ValidationError);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('should work with safeParse', () => {
|
|
20
|
+
const result = schema.safeParse('test');
|
|
21
|
+
expect(result.success).toBe(true);
|
|
22
|
+
if (result.success) {
|
|
23
|
+
expect(result.data).toBe('test');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const badResult = schema.safeParse(123);
|
|
27
|
+
expect(badResult.success).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('min() should enforce minimum length', () => {
|
|
31
|
+
const minSchema = s.string().min(3);
|
|
32
|
+
expect(minSchema.parse('hello')).toBe('hello');
|
|
33
|
+
expect(minSchema.parse('abc')).toBe('abc');
|
|
34
|
+
expect(() => minSchema.parse('ab')).toThrow(ValidationError);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('max() should enforce maximum length', () => {
|
|
38
|
+
const maxSchema = s.string().max(5);
|
|
39
|
+
expect(maxSchema.parse('hello')).toBe('hello');
|
|
40
|
+
expect(maxSchema.parse('hi')).toBe('hi');
|
|
41
|
+
expect(() => maxSchema.parse('toolong')).toThrow(ValidationError);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('should chain min and max', () => {
|
|
45
|
+
const rangeSchema = s.string().min(3).max(10);
|
|
46
|
+
expect(rangeSchema.parse('hello')).toBe('hello');
|
|
47
|
+
expect(() => rangeSchema.parse('ab')).toThrow(ValidationError);
|
|
48
|
+
expect(() => rangeSchema.parse('this is too long')).toThrow(ValidationError);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('number', () => {
|
|
53
|
+
const schema = s.number();
|
|
54
|
+
|
|
55
|
+
test('should validate numbers', () => {
|
|
56
|
+
expect(schema.parse(123)).toBe(123);
|
|
57
|
+
expect(schema.parse(0)).toBe(0);
|
|
58
|
+
expect(schema.parse(-45.67)).toBe(-45.67);
|
|
59
|
+
expect(schema.parse(Infinity)).toBe(Infinity);
|
|
60
|
+
expect(schema.parse(-Infinity)).toBe(-Infinity);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('should reject non-numbers', () => {
|
|
64
|
+
expect(() => schema.parse('123')).toThrow(ValidationError);
|
|
65
|
+
expect(() => schema.parse(true)).toThrow(ValidationError);
|
|
66
|
+
expect(() => schema.parse(NaN)).toThrow(ValidationError);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('finite() should reject Infinity', () => {
|
|
70
|
+
const finiteSchema = s.number().finite();
|
|
71
|
+
expect(finiteSchema.parse(123)).toBe(123);
|
|
72
|
+
expect(finiteSchema.parse(0)).toBe(0);
|
|
73
|
+
expect(finiteSchema.parse(-45.67)).toBe(-45.67);
|
|
74
|
+
expect(() => finiteSchema.parse(Infinity)).toThrow(ValidationError);
|
|
75
|
+
expect(() => finiteSchema.parse(-Infinity)).toThrow(ValidationError);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('min() should enforce minimum', () => {
|
|
79
|
+
const minSchema = s.number().min(0);
|
|
80
|
+
expect(minSchema.parse(0)).toBe(0);
|
|
81
|
+
expect(minSchema.parse(10)).toBe(10);
|
|
82
|
+
expect(() => minSchema.parse(-1)).toThrow(ValidationError);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('max() should enforce maximum', () => {
|
|
86
|
+
const maxSchema = s.number().max(100);
|
|
87
|
+
expect(maxSchema.parse(100)).toBe(100);
|
|
88
|
+
expect(maxSchema.parse(50)).toBe(50);
|
|
89
|
+
expect(() => maxSchema.parse(101)).toThrow(ValidationError);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should chain min and max', () => {
|
|
93
|
+
const rangeSchema = s.number().min(0).max(100);
|
|
94
|
+
expect(rangeSchema.parse(50)).toBe(50);
|
|
95
|
+
expect(() => rangeSchema.parse(-1)).toThrow(ValidationError);
|
|
96
|
+
expect(() => rangeSchema.parse(101)).toThrow(ValidationError);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('boolean', () => {
|
|
101
|
+
const schema = s.boolean();
|
|
102
|
+
|
|
103
|
+
test('should validate booleans', () => {
|
|
104
|
+
expect(schema.parse(true)).toBe(true);
|
|
105
|
+
expect(schema.parse(false)).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should reject non-booleans', () => {
|
|
109
|
+
expect(() => schema.parse('true')).toThrow(ValidationError);
|
|
110
|
+
expect(() => schema.parse(1)).toThrow(ValidationError);
|
|
111
|
+
expect(() => schema.parse(0)).toThrow(ValidationError);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('null', () => {
|
|
116
|
+
const schema = s.null();
|
|
117
|
+
|
|
118
|
+
test('should validate null', () => {
|
|
119
|
+
expect(schema.parse(null)).toBe(null);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('should reject non-null', () => {
|
|
123
|
+
expect(() => schema.parse(undefined)).toThrow(ValidationError);
|
|
124
|
+
expect(() => schema.parse(0)).toThrow(ValidationError);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('undefined', () => {
|
|
129
|
+
const schema = s.undefined();
|
|
130
|
+
|
|
131
|
+
test('should validate undefined', () => {
|
|
132
|
+
expect(schema.parse(undefined)).toBe(undefined);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('should reject non-undefined', () => {
|
|
136
|
+
expect(() => schema.parse(null)).toThrow(ValidationError);
|
|
137
|
+
expect(() => schema.parse(0)).toThrow(ValidationError);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('unknown', () => {
|
|
142
|
+
const schema = s.unknown();
|
|
143
|
+
|
|
144
|
+
test('should accept any value', () => {
|
|
145
|
+
expect(schema.parse(123)).toBe(123);
|
|
146
|
+
expect(schema.parse('hello')).toBe('hello');
|
|
147
|
+
expect(schema.parse(true)).toBe(true);
|
|
148
|
+
expect(schema.parse(null)).toBe(null);
|
|
149
|
+
expect(schema.parse(undefined)).toBe(undefined);
|
|
150
|
+
expect(schema.parse({ foo: 'bar' })).toEqual({ foo: 'bar' });
|
|
151
|
+
expect(schema.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should work with safeParse', () => {
|
|
155
|
+
const result = schema.safeParse('anything');
|
|
156
|
+
expect(result.success).toBe(true);
|
|
157
|
+
if (result.success) {
|
|
158
|
+
expect(result.data).toBe('anything');
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('any', () => {
|
|
164
|
+
const schema = s.any();
|
|
165
|
+
|
|
166
|
+
test('should accept any value', () => {
|
|
167
|
+
expect(schema.parse(123)).toBe(123);
|
|
168
|
+
expect(schema.parse('hello')).toBe('hello');
|
|
169
|
+
expect(schema.parse(true)).toBe(true);
|
|
170
|
+
expect(schema.parse(null)).toBe(null);
|
|
171
|
+
expect(schema.parse(undefined)).toBe(undefined);
|
|
172
|
+
expect(schema.parse({ foo: 'bar' })).toEqual({ foo: 'bar' });
|
|
173
|
+
expect(schema.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('should work with safeParse', () => {
|
|
177
|
+
const result = schema.safeParse('anything');
|
|
178
|
+
expect(result.success).toBe(true);
|
|
179
|
+
if (result.success) {
|
|
180
|
+
expect(result.data).toBe('anything');
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { s } from '../index.js';
|
|
3
|
+
|
|
4
|
+
describe('Type Inference', () => {
|
|
5
|
+
test('should infer object types', () => {
|
|
6
|
+
const schema = s.object({
|
|
7
|
+
name: s.string(),
|
|
8
|
+
age: s.number(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
type User = s.infer<typeof schema>;
|
|
12
|
+
|
|
13
|
+
const user: User = schema.parse({
|
|
14
|
+
name: 'John',
|
|
15
|
+
age: 30,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(user.name).toBe('John');
|
|
19
|
+
expect(user.age).toBe(30);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should infer array types', () => {
|
|
23
|
+
const schema = s.array(s.string());
|
|
24
|
+
type StringArray = s.infer<typeof schema>;
|
|
25
|
+
|
|
26
|
+
const arr: StringArray = schema.parse(['a', 'b', 'c']);
|
|
27
|
+
expect(arr).toEqual(['a', 'b', 'c']);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should infer union types', () => {
|
|
31
|
+
const schema = s.union(s.literal('admin'), s.literal('user'));
|
|
32
|
+
|
|
33
|
+
type Role = s.infer<typeof schema>;
|
|
34
|
+
|
|
35
|
+
const role: Role = schema.parse('admin');
|
|
36
|
+
expect(role).toBe('admin');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should infer optional types', () => {
|
|
40
|
+
const schema = s.object({
|
|
41
|
+
required: s.string(),
|
|
42
|
+
optional: s.optional(s.number()),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
type Data = s.infer<typeof schema>;
|
|
46
|
+
|
|
47
|
+
const data: Data = schema.parse({
|
|
48
|
+
required: 'test',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(data.required).toBe('test');
|
|
52
|
+
expect(data.optional).toBe(undefined);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('should infer nullable types', () => {
|
|
56
|
+
const schema = s.object({
|
|
57
|
+
value: s.nullable(s.string()),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
type Data = s.infer<typeof schema>;
|
|
61
|
+
|
|
62
|
+
const data: Data = schema.parse({
|
|
63
|
+
value: null,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(data.value).toBe(null);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { s, ValidationError } from '../index.js';
|
|
3
|
+
|
|
4
|
+
describe('Utility Schemas', () => {
|
|
5
|
+
describe('literal', () => {
|
|
6
|
+
test('should validate exact string values', () => {
|
|
7
|
+
const schema = s.literal('admin');
|
|
8
|
+
expect(schema.parse('admin')).toBe('admin');
|
|
9
|
+
expect(() => schema.parse('user')).toThrow(ValidationError);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('should validate exact number values', () => {
|
|
13
|
+
const schema = s.literal(42);
|
|
14
|
+
expect(schema.parse(42)).toBe(42);
|
|
15
|
+
expect(() => schema.parse(41)).toThrow(ValidationError);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should validate exact boolean values', () => {
|
|
19
|
+
const schema = s.literal(true);
|
|
20
|
+
expect(schema.parse(true)).toBe(true);
|
|
21
|
+
expect(() => schema.parse(false)).toThrow(ValidationError);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('enum', () => {
|
|
26
|
+
test('should validate enum values', () => {
|
|
27
|
+
const schema = s.enum(['red', 'green', 'blue']);
|
|
28
|
+
expect(schema.parse('red')).toBe('red');
|
|
29
|
+
expect(schema.parse('green')).toBe('green');
|
|
30
|
+
expect(() => schema.parse('yellow')).toThrow(ValidationError);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('should work with numbers', () => {
|
|
34
|
+
const schema = s.enum([1, 2, 3]);
|
|
35
|
+
expect(schema.parse(2)).toBe(2);
|
|
36
|
+
expect(() => schema.parse(4)).toThrow(ValidationError);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should work with mixed types', () => {
|
|
40
|
+
const schema = s.enum(['active', 'inactive', 1, 2]);
|
|
41
|
+
expect(schema.parse('active')).toBe('active');
|
|
42
|
+
expect(schema.parse(1)).toBe(1);
|
|
43
|
+
expect(() => schema.parse('pending')).toThrow(ValidationError);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('optional', () => {
|
|
48
|
+
const schema = s.optional(s.string());
|
|
49
|
+
|
|
50
|
+
test('should validate undefined', () => {
|
|
51
|
+
expect(schema.parse(undefined)).toBe(undefined);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should validate the wrapped type', () => {
|
|
55
|
+
expect(schema.parse('hello')).toBe('hello');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('should reject invalid values', () => {
|
|
59
|
+
expect(() => schema.parse(123)).toThrow(ValidationError);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('nullable', () => {
|
|
64
|
+
const schema = s.nullable(s.string());
|
|
65
|
+
|
|
66
|
+
test('should validate null', () => {
|
|
67
|
+
expect(schema.parse(null)).toBe(null);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should validate the wrapped type', () => {
|
|
71
|
+
expect(schema.parse('hello')).toBe('hello');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('should reject invalid values', () => {
|
|
75
|
+
expect(() => schema.parse(123)).toThrow(ValidationError);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('union', () => {
|
|
80
|
+
const schema = s.union(s.string(), s.number(), s.boolean());
|
|
81
|
+
|
|
82
|
+
test('should validate any union member', () => {
|
|
83
|
+
expect(schema.parse('hello')).toBe('hello');
|
|
84
|
+
expect(schema.parse(123)).toBe(123);
|
|
85
|
+
expect(schema.parse(true)).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should reject non-members', () => {
|
|
89
|
+
expect(() => schema.parse(null)).toThrow(ValidationError);
|
|
90
|
+
expect(() => schema.parse(undefined)).toThrow(ValidationError);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('should work with literal union (enum-like)', () => {
|
|
94
|
+
const roleSchema = s.union(s.literal('admin'), s.literal('user'), s.literal('guest'));
|
|
95
|
+
|
|
96
|
+
expect(roleSchema.parse('admin')).toBe('admin');
|
|
97
|
+
expect(() => roleSchema.parse('superuser')).toThrow(ValidationError);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|