taro-bluetooth-print 2.9.0 → 2.9.2
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/CHANGELOG.md +16 -1
- package/README.md +53 -4
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/core/di/Container.d.ts +84 -0
- package/dist/types/core/di/Tokens.d.ts +29 -0
- package/dist/types/core/di/index.d.ts +3 -0
- package/dist/types/core/event/EventBus.d.ts +66 -0
- package/dist/types/core/event/index.d.ts +2 -0
- package/dist/types/core/index.d.ts +5 -4
- package/dist/types/core/plugin/PluginManager.d.ts +64 -0
- package/dist/types/core/plugin/index.d.ts +2 -0
- package/dist/types/device/MultiPrinterManager.d.ts +2 -0
- package/dist/types/factory/di-factory.d.ts +52 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/providers/ServiceProvider.d.ts +56 -0
- package/dist/types/providers/index.d.ts +2 -0
- package/dist/types/template/TemplateEngine.d.ts +24 -68
- package/dist/types/template/engines/TemplateRenderer.d.ts +71 -0
- package/dist/types/template/parsers/TemplateParser.d.ts +23 -0
- package/dist/types/utils/index.d.ts +8 -0
- package/dist/types/utils/logger.d.ts +4 -3
- package/dist/types/utils/outputLimiter.d.ts +87 -0
- package/dist/types/utils/validation.d.ts +11 -309
- package/dist/types/utils/validators/array.d.ts +19 -0
- package/dist/types/utils/validators/buffer.d.ts +18 -0
- package/dist/types/utils/validators/chain.d.ts +31 -0
- package/dist/types/utils/validators/common.d.ts +22 -0
- package/dist/types/utils/validators/number.d.ts +20 -0
- package/dist/types/utils/validators/object.d.ts +24 -0
- package/dist/types/utils/validators/printer.d.ts +40 -0
- package/dist/types/utils/validators/types.d.ts +125 -0
- package/dist/types/utils/validators/uuid.d.ts +23 -0
- package/package.json +1 -1
- package/src/core/BluetoothPrinter.ts +2 -1
- package/src/core/di/Container.ts +332 -0
- package/src/core/di/Tokens.ts +45 -0
- package/src/core/di/index.ts +3 -0
- package/src/core/event/EventBus.ts +251 -0
- package/src/core/event/index.ts +2 -0
- package/src/core/index.ts +10 -4
- package/src/core/plugin/PluginManager.ts +161 -0
- package/src/core/plugin/index.ts +2 -0
- package/src/device/MultiPrinterManager.ts +15 -6
- package/src/factory/di-factory.ts +61 -0
- package/src/index.ts +50 -1
- package/src/providers/ServiceProvider.ts +213 -0
- package/src/providers/index.ts +2 -0
- package/src/template/TemplateEngine.ts +27 -792
- package/src/template/engines/TemplateRenderer.ts +762 -0
- package/src/template/parsers/TemplateParser.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/logger.ts +17 -4
- package/src/utils/outputLimiter.ts +227 -0
- package/src/utils/validation.ts +21 -1138
- package/src/utils/validators/array.ts +95 -0
- package/src/utils/validators/buffer.ts +81 -0
- package/src/utils/validators/chain.ts +181 -0
- package/src/utils/validators/common.ts +216 -0
- package/src/utils/validators/number.ts +101 -0
- package/src/utils/validators/object.ts +63 -0
- package/src/utils/validators/printer.ts +294 -0
- package/src/utils/validators/types.ts +105 -0
- package/src/utils/validators/uuid.ts +49 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array validation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ValidationCodes, type ValidationError, type ValidationResult } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate an array of items
|
|
9
|
+
*
|
|
10
|
+
* @param items - Array to validate
|
|
11
|
+
* @param itemValidator - Validator function for each item
|
|
12
|
+
* @param options - Array validation options
|
|
13
|
+
* @returns Validation result
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const result = validateArray(items, item => validatePrintJob(item, schema), { minItems: 1, maxItems: 100 });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function validateArray<T>(
|
|
21
|
+
items: unknown,
|
|
22
|
+
itemValidator: (item: T, index: number) => ValidationResult,
|
|
23
|
+
options?: {
|
|
24
|
+
required?: boolean;
|
|
25
|
+
minItems?: number;
|
|
26
|
+
maxItems?: number;
|
|
27
|
+
}
|
|
28
|
+
): ValidationResult {
|
|
29
|
+
const errors: ValidationError[] = [];
|
|
30
|
+
const warnings: ValidationError[] = [];
|
|
31
|
+
|
|
32
|
+
if (items === undefined || items === null) {
|
|
33
|
+
if (options?.required) {
|
|
34
|
+
errors.push({
|
|
35
|
+
field: 'array',
|
|
36
|
+
message: 'Array is required',
|
|
37
|
+
code: ValidationCodes.REQUIRED,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!Array.isArray(items)) {
|
|
44
|
+
errors.push({
|
|
45
|
+
field: 'array',
|
|
46
|
+
message: 'Expected an array',
|
|
47
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
48
|
+
value: typeof items,
|
|
49
|
+
});
|
|
50
|
+
return { valid: false, errors, warnings };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (options?.minItems !== undefined && items.length < options.minItems) {
|
|
54
|
+
errors.push({
|
|
55
|
+
field: 'array',
|
|
56
|
+
message: `Array must have at least ${options.minItems} items`,
|
|
57
|
+
code: ValidationCodes.ARRAY_TOO_SHORT,
|
|
58
|
+
value: items.length,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (options?.maxItems !== undefined && items.length > options.maxItems) {
|
|
63
|
+
errors.push({
|
|
64
|
+
field: 'array',
|
|
65
|
+
message: `Array must have at most ${options.maxItems} items`,
|
|
66
|
+
code: ValidationCodes.ARRAY_TOO_LONG,
|
|
67
|
+
value: items.length,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Validate each item
|
|
72
|
+
for (let i = 0; i < items.length; i++) {
|
|
73
|
+
const itemResult = itemValidator(items[i] as T, i);
|
|
74
|
+
errors.push(
|
|
75
|
+
...itemResult.errors.map(err => ({
|
|
76
|
+
...err,
|
|
77
|
+
field: `${err.field}[${i}]`,
|
|
78
|
+
}))
|
|
79
|
+
);
|
|
80
|
+
if (itemResult.warnings) {
|
|
81
|
+
warnings.push(
|
|
82
|
+
...itemResult.warnings.map(w => ({
|
|
83
|
+
...w,
|
|
84
|
+
field: `${w.field}[${i}]`,
|
|
85
|
+
}))
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
valid: errors.length === 0,
|
|
92
|
+
errors,
|
|
93
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArrayBuffer validation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ValidationCodes, type ValidationError, type ValidationResult } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate an ArrayBuffer
|
|
9
|
+
*
|
|
10
|
+
* @param buffer - Buffer to validate
|
|
11
|
+
* @param options - Validation options
|
|
12
|
+
* @returns Validation result
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const result = isValidBuffer(data, { minSize: 1, maxSize: 1024 * 1024 });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function isValidBuffer(
|
|
20
|
+
buffer: unknown,
|
|
21
|
+
options?: {
|
|
22
|
+
minSize?: number;
|
|
23
|
+
maxSize?: number;
|
|
24
|
+
required?: boolean;
|
|
25
|
+
}
|
|
26
|
+
): ValidationResult {
|
|
27
|
+
const errors: ValidationError[] = [];
|
|
28
|
+
const warnings: ValidationError[] = [];
|
|
29
|
+
|
|
30
|
+
if (!(buffer instanceof ArrayBuffer)) {
|
|
31
|
+
errors.push({
|
|
32
|
+
field: 'buffer',
|
|
33
|
+
message: 'Expected an ArrayBuffer',
|
|
34
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
35
|
+
value: typeof buffer,
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
valid: false,
|
|
39
|
+
errors,
|
|
40
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const byteLength = buffer.byteLength;
|
|
45
|
+
// When required is true without explicit minSize, treat empty buffer as TOO_SHORT
|
|
46
|
+
const effectiveMinSize =
|
|
47
|
+
options?.required && options?.minSize === undefined ? 1 : options?.minSize;
|
|
48
|
+
|
|
49
|
+
if (effectiveMinSize !== undefined && byteLength < effectiveMinSize) {
|
|
50
|
+
errors.push({
|
|
51
|
+
field: 'buffer',
|
|
52
|
+
message: `Buffer must be at least ${effectiveMinSize} bytes`,
|
|
53
|
+
code: ValidationCodes.TOO_SHORT,
|
|
54
|
+
value: byteLength,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (options?.maxSize !== undefined && byteLength > options.maxSize) {
|
|
59
|
+
errors.push({
|
|
60
|
+
field: 'buffer',
|
|
61
|
+
message: `Buffer exceeds maximum size of ${options.maxSize} bytes`,
|
|
62
|
+
code: ValidationCodes.TOO_LONG,
|
|
63
|
+
value: byteLength,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (byteLength === 0) {
|
|
68
|
+
warnings.push({
|
|
69
|
+
field: 'buffer',
|
|
70
|
+
message: 'Buffer is empty',
|
|
71
|
+
code: ValidationCodes.EMPTY_ARRAY,
|
|
72
|
+
value: byteLength,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
valid: errors.length === 0,
|
|
78
|
+
errors,
|
|
79
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chainable validation helper
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ValidationCodes, type ValidationError, type ValidationResult } from './types';
|
|
6
|
+
|
|
7
|
+
export interface ChainableValidator {
|
|
8
|
+
addError(message: string, code: string): ChainableValidator;
|
|
9
|
+
required(): ChainableValidator;
|
|
10
|
+
string(): ChainableValidator;
|
|
11
|
+
number(): ChainableValidator;
|
|
12
|
+
integer(): ChainableValidator;
|
|
13
|
+
range(min: number, max: number): ChainableValidator;
|
|
14
|
+
length(min: number, max: number): ChainableValidator;
|
|
15
|
+
pattern(regex: RegExp, message?: string): ChainableValidator;
|
|
16
|
+
oneOf<T>(values: readonly T[], message?: string): ChainableValidator;
|
|
17
|
+
result(): ValidationResult;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a chainable validation helper
|
|
22
|
+
*
|
|
23
|
+
* @param value - Value to validate
|
|
24
|
+
* @param field - Field name
|
|
25
|
+
* @returns Chainable validator
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const result = check(value, 'timeout')
|
|
30
|
+
* .required()
|
|
31
|
+
* .number()
|
|
32
|
+
* .range(0, 30000)
|
|
33
|
+
* .integer()
|
|
34
|
+
* .result();
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function check(value: unknown, field: string): ChainableValidator {
|
|
38
|
+
const errors: ValidationError[] = [];
|
|
39
|
+
|
|
40
|
+
const self: ChainableValidator = {
|
|
41
|
+
/** Add error manually */
|
|
42
|
+
addError(message: string, code: string) {
|
|
43
|
+
errors.push({ field, message, code, value });
|
|
44
|
+
return this;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/** Validate required */
|
|
48
|
+
required() {
|
|
49
|
+
if (value === undefined || value === null || value === '') {
|
|
50
|
+
errors.push({
|
|
51
|
+
field,
|
|
52
|
+
message: `${field} is required`,
|
|
53
|
+
code: ValidationCodes.REQUIRED,
|
|
54
|
+
value,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return this;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/** Validate type is string */
|
|
61
|
+
string() {
|
|
62
|
+
if (value !== undefined && value !== null && typeof value !== 'string') {
|
|
63
|
+
errors.push({
|
|
64
|
+
field,
|
|
65
|
+
message: `${field} must be a string`,
|
|
66
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
67
|
+
value: typeof value,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return this;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/** Validate type is number */
|
|
74
|
+
number() {
|
|
75
|
+
if (value !== undefined && value !== null && typeof value !== 'number') {
|
|
76
|
+
errors.push({
|
|
77
|
+
field,
|
|
78
|
+
message: `${field} must be a number`,
|
|
79
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
80
|
+
value: typeof value,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return this;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/** Validate is integer */
|
|
87
|
+
integer() {
|
|
88
|
+
if (typeof value === 'number' && !Number.isInteger(value)) {
|
|
89
|
+
errors.push({
|
|
90
|
+
field,
|
|
91
|
+
message: `${field} must be an integer`,
|
|
92
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
93
|
+
value,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return this;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/** Validate range */
|
|
100
|
+
range(min: number, max: number) {
|
|
101
|
+
if (typeof value === 'number') {
|
|
102
|
+
if (value < min) {
|
|
103
|
+
errors.push({
|
|
104
|
+
field,
|
|
105
|
+
message: `${field} must be at least ${min}`,
|
|
106
|
+
code: ValidationCodes.OUT_OF_RANGE,
|
|
107
|
+
value,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (value > max) {
|
|
111
|
+
errors.push({
|
|
112
|
+
field,
|
|
113
|
+
message: `${field} must be at most ${max}`,
|
|
114
|
+
code: ValidationCodes.OUT_OF_RANGE,
|
|
115
|
+
value,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return this;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/** Validate string length */
|
|
123
|
+
length(min: number, max: number) {
|
|
124
|
+
if (typeof value === 'string') {
|
|
125
|
+
if (value.length < min) {
|
|
126
|
+
errors.push({
|
|
127
|
+
field,
|
|
128
|
+
message: `${field} must be at least ${min} characters`,
|
|
129
|
+
code: ValidationCodes.TOO_SHORT,
|
|
130
|
+
value,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (value.length > max) {
|
|
134
|
+
errors.push({
|
|
135
|
+
field,
|
|
136
|
+
message: `${field} must be at most ${max} characters`,
|
|
137
|
+
code: ValidationCodes.TOO_LONG,
|
|
138
|
+
value,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return this;
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/** Validate matches pattern */
|
|
146
|
+
pattern(regex: RegExp, message?: string) {
|
|
147
|
+
if (typeof value === 'string' && !regex.test(value)) {
|
|
148
|
+
errors.push({
|
|
149
|
+
field,
|
|
150
|
+
message: message ?? `${field} format is invalid`,
|
|
151
|
+
code: ValidationCodes.PATTERN_MISMATCH,
|
|
152
|
+
value,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return this;
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/** Validate is one of values */
|
|
159
|
+
oneOf<T>(values: readonly T[], message?: string) {
|
|
160
|
+
if (!values.includes(value as T)) {
|
|
161
|
+
errors.push({
|
|
162
|
+
field,
|
|
163
|
+
message: message ?? `${field} must be one of: ${values.join(', ')}`,
|
|
164
|
+
code: ValidationCodes.INVALID_ENUM,
|
|
165
|
+
value,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return this;
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/** Get validation result */
|
|
172
|
+
result() {
|
|
173
|
+
return {
|
|
174
|
+
valid: errors.length === 0,
|
|
175
|
+
errors,
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return self;
|
|
181
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common validation helpers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ValidationCodes, type ValidationError } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Common validation rules
|
|
9
|
+
*/
|
|
10
|
+
export const CommonValidators = {
|
|
11
|
+
/** Required string (non-empty) */
|
|
12
|
+
requiredString: (field: string, value: unknown): ValidationError | null => {
|
|
13
|
+
if (value === undefined || value === null || value === '') {
|
|
14
|
+
return {
|
|
15
|
+
field,
|
|
16
|
+
message: `${field} is required`,
|
|
17
|
+
code: ValidationCodes.REQUIRED,
|
|
18
|
+
value,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (typeof value !== 'string') {
|
|
22
|
+
return {
|
|
23
|
+
field,
|
|
24
|
+
message: `${field} must be a string`,
|
|
25
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
26
|
+
value,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/** Positive number */
|
|
33
|
+
positiveNumber: (field: string, value: unknown): ValidationError | null => {
|
|
34
|
+
if (typeof value !== 'number') {
|
|
35
|
+
return {
|
|
36
|
+
field,
|
|
37
|
+
message: `${field} must be a number`,
|
|
38
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
39
|
+
value,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (isNaN(value)) {
|
|
43
|
+
return {
|
|
44
|
+
field,
|
|
45
|
+
message: `${field} must be a valid number`,
|
|
46
|
+
code: ValidationCodes.NOT_A_NUMBER,
|
|
47
|
+
value,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (!isFinite(value)) {
|
|
51
|
+
return {
|
|
52
|
+
field,
|
|
53
|
+
message: `${field} must be a finite number`,
|
|
54
|
+
code: ValidationCodes.INFINITY_VALUE,
|
|
55
|
+
value,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (value <= 0) {
|
|
59
|
+
return {
|
|
60
|
+
field,
|
|
61
|
+
message: `${field} must be positive`,
|
|
62
|
+
code: ValidationCodes.NEGATIVE_VALUE,
|
|
63
|
+
value,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/** Non-negative number */
|
|
70
|
+
nonNegativeNumber: (field: string, value: unknown): ValidationError | null => {
|
|
71
|
+
if (typeof value !== 'number') {
|
|
72
|
+
return {
|
|
73
|
+
field,
|
|
74
|
+
message: `${field} must be a number`,
|
|
75
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
76
|
+
value,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (isNaN(value)) {
|
|
80
|
+
return {
|
|
81
|
+
field,
|
|
82
|
+
message: `${field} must be a valid number`,
|
|
83
|
+
code: ValidationCodes.NOT_A_NUMBER,
|
|
84
|
+
value,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (value < 0) {
|
|
88
|
+
return {
|
|
89
|
+
field,
|
|
90
|
+
message: `${field} cannot be negative`,
|
|
91
|
+
code: ValidationCodes.NEGATIVE_VALUE,
|
|
92
|
+
value,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/** Array with items */
|
|
99
|
+
nonEmptyArray: (field: string, value: unknown): ValidationError | null => {
|
|
100
|
+
if (!Array.isArray(value)) {
|
|
101
|
+
return {
|
|
102
|
+
field,
|
|
103
|
+
message: `${field} must be an array`,
|
|
104
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
105
|
+
value,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (value.length === 0) {
|
|
109
|
+
return {
|
|
110
|
+
field,
|
|
111
|
+
message: `${field} cannot be empty`,
|
|
112
|
+
code: ValidationCodes.EMPTY_ARRAY,
|
|
113
|
+
value,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/** In range */
|
|
120
|
+
inRange: (field: string, value: unknown, min: number, max: number): ValidationError | null => {
|
|
121
|
+
if (typeof value !== 'number') {
|
|
122
|
+
return {
|
|
123
|
+
field,
|
|
124
|
+
message: `${field} must be a number`,
|
|
125
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
126
|
+
value,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (value < min || value > max) {
|
|
130
|
+
return {
|
|
131
|
+
field,
|
|
132
|
+
message: `${field} must be between ${min} and ${max}`,
|
|
133
|
+
code: ValidationCodes.OUT_OF_RANGE,
|
|
134
|
+
value,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/** String length */
|
|
141
|
+
stringLength: (
|
|
142
|
+
field: string,
|
|
143
|
+
value: unknown,
|
|
144
|
+
min: number,
|
|
145
|
+
max: number
|
|
146
|
+
): ValidationError | null => {
|
|
147
|
+
if (typeof value !== 'string') {
|
|
148
|
+
return {
|
|
149
|
+
field,
|
|
150
|
+
message: `${field} must be a string`,
|
|
151
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
152
|
+
value,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (value.length < min) {
|
|
156
|
+
return {
|
|
157
|
+
field,
|
|
158
|
+
message: `${field} must be at least ${min} characters`,
|
|
159
|
+
code: ValidationCodes.TOO_SHORT,
|
|
160
|
+
value,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (value.length > max) {
|
|
164
|
+
return {
|
|
165
|
+
field,
|
|
166
|
+
message: `${field} must be at most ${max} characters`,
|
|
167
|
+
code: ValidationCodes.TOO_LONG,
|
|
168
|
+
value,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/** Matches pattern */
|
|
175
|
+
matchesPattern: (
|
|
176
|
+
field: string,
|
|
177
|
+
value: unknown,
|
|
178
|
+
pattern: RegExp,
|
|
179
|
+
message?: string
|
|
180
|
+
): ValidationError | null => {
|
|
181
|
+
if (typeof value !== 'string') {
|
|
182
|
+
return {
|
|
183
|
+
field,
|
|
184
|
+
message: `${field} must be a string`,
|
|
185
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
186
|
+
value,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (!pattern.test(value)) {
|
|
190
|
+
return {
|
|
191
|
+
field,
|
|
192
|
+
message: message ?? `${field} format is invalid`,
|
|
193
|
+
code: ValidationCodes.PATTERN_MISMATCH,
|
|
194
|
+
value,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/** Is one of enum values */
|
|
201
|
+
isEnum: <T extends string>(
|
|
202
|
+
field: string,
|
|
203
|
+
value: unknown,
|
|
204
|
+
enumValues: readonly T[]
|
|
205
|
+
): ValidationError | null => {
|
|
206
|
+
if (!enumValues.includes(value as T)) {
|
|
207
|
+
return {
|
|
208
|
+
field,
|
|
209
|
+
message: `${field} must be one of: ${enumValues.join(', ')}`,
|
|
210
|
+
code: ValidationCodes.INVALID_ENUM,
|
|
211
|
+
value,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
},
|
|
216
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Numeric range validation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ValidationCodes, type ValidationError, type ValidationResult } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate numeric range
|
|
9
|
+
*
|
|
10
|
+
* @param value - Value to validate
|
|
11
|
+
* @param field - Field name for error messages
|
|
12
|
+
* @param options - Range options
|
|
13
|
+
* @returns Validation result
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const result = validateRange(value, 'timeout', { min: 0, max: 30000, integer: true });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function validateRange(
|
|
21
|
+
value: unknown,
|
|
22
|
+
field: string,
|
|
23
|
+
options?: {
|
|
24
|
+
required?: boolean;
|
|
25
|
+
min?: number;
|
|
26
|
+
max?: number;
|
|
27
|
+
integer?: boolean;
|
|
28
|
+
}
|
|
29
|
+
): ValidationResult {
|
|
30
|
+
const errors: ValidationError[] = [];
|
|
31
|
+
|
|
32
|
+
if (value === undefined || value === null) {
|
|
33
|
+
if (options?.required) {
|
|
34
|
+
errors.push({
|
|
35
|
+
field,
|
|
36
|
+
message: `${field} is required`,
|
|
37
|
+
code: ValidationCodes.REQUIRED,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return { valid: errors.length === 0, errors };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof value !== 'number') {
|
|
44
|
+
errors.push({
|
|
45
|
+
field,
|
|
46
|
+
message: `${field} must be a number`,
|
|
47
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
48
|
+
value: typeof value,
|
|
49
|
+
});
|
|
50
|
+
return { valid: false, errors };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isNaN(value)) {
|
|
54
|
+
errors.push({
|
|
55
|
+
field,
|
|
56
|
+
message: `${field} must be a valid number`,
|
|
57
|
+
code: ValidationCodes.NOT_A_NUMBER,
|
|
58
|
+
value,
|
|
59
|
+
});
|
|
60
|
+
return { valid: false, errors };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!isFinite(value)) {
|
|
64
|
+
errors.push({
|
|
65
|
+
field,
|
|
66
|
+
message: `${field} must be a finite number`,
|
|
67
|
+
code: ValidationCodes.INFINITY_VALUE,
|
|
68
|
+
value,
|
|
69
|
+
});
|
|
70
|
+
return { valid: false, errors };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (options?.integer && !Number.isInteger(value)) {
|
|
74
|
+
errors.push({
|
|
75
|
+
field,
|
|
76
|
+
message: `${field} must be an integer`,
|
|
77
|
+
code: ValidationCodes.INVALID_TYPE,
|
|
78
|
+
value,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (options?.min !== undefined && value < options.min) {
|
|
83
|
+
errors.push({
|
|
84
|
+
field,
|
|
85
|
+
message: `${field} must be at least ${options.min}`,
|
|
86
|
+
code: ValidationCodes.OUT_OF_RANGE,
|
|
87
|
+
value,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (options?.max !== undefined && value > options.max) {
|
|
92
|
+
errors.push({
|
|
93
|
+
field,
|
|
94
|
+
message: `${field} must be at most ${options.max}`,
|
|
95
|
+
code: ValidationCodes.OUT_OF_RANGE,
|
|
96
|
+
value,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { valid: errors.length === 0, errors };
|
|
101
|
+
}
|