@hazeljs/core 0.2.0-beta.1
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/README.md +522 -0
- package/dist/__tests__/container.test.d.ts +2 -0
- package/dist/__tests__/container.test.d.ts.map +1 -0
- package/dist/__tests__/container.test.js +454 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +693 -0
- package/dist/__tests__/errors/http.error.test.d.ts +2 -0
- package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
- package/dist/__tests__/errors/http.error.test.js +117 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/exception-filter.test.js +135 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
- package/dist/__tests__/hazel-app.test.d.ts +2 -0
- package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-app.test.js +682 -0
- package/dist/__tests__/hazel-module.test.d.ts +2 -0
- package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-module.test.js +408 -0
- package/dist/__tests__/hazel-response.test.d.ts +2 -0
- package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-response.test.js +138 -0
- package/dist/__tests__/health.test.d.ts +2 -0
- package/dist/__tests__/health.test.d.ts.map +1 -0
- package/dist/__tests__/health.test.js +147 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +239 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
- package/dist/__tests__/interceptors/interceptor.test.js +166 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +141 -0
- package/dist/__tests__/middleware/cors.test.d.ts +2 -0
- package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/cors.test.js +129 -0
- package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
- package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/csrf.test.js +247 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/global-middleware.test.js +259 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/rate-limit.test.js +264 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/security-headers.test.js +229 -0
- package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
- package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/timeout.test.js +132 -0
- package/dist/__tests__/middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware.test.js +180 -0
- package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/pipe.test.js +245 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
- package/dist/__tests__/request-parser.test.d.ts +2 -0
- package/dist/__tests__/request-parser.test.d.ts.map +1 -0
- package/dist/__tests__/request-parser.test.js +182 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +680 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
- package/dist/__tests__/routing/route-matcher.test.js +219 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
- package/dist/__tests__/routing/version.decorator.test.js +298 -0
- package/dist/__tests__/service.test.d.ts +2 -0
- package/dist/__tests__/service.test.d.ts.map +1 -0
- package/dist/__tests__/service.test.js +121 -0
- package/dist/__tests__/shutdown.test.d.ts +2 -0
- package/dist/__tests__/shutdown.test.d.ts.map +1 -0
- package/dist/__tests__/shutdown.test.js +250 -0
- package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
- package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
- package/dist/__tests__/testing/testing.module.test.js +370 -0
- package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
- package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
- package/dist/__tests__/upload/file-upload.test.js +498 -0
- package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
- package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/utils/sanitize.test.js +291 -0
- package/dist/__tests__/validator.test.d.ts +2 -0
- package/dist/__tests__/validator.test.d.ts.map +1 -0
- package/dist/__tests__/validator.test.js +300 -0
- package/dist/container.d.ts +80 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +271 -0
- package/dist/decorators.d.ts +92 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +343 -0
- package/dist/errors/http.error.d.ts +31 -0
- package/dist/errors/http.error.d.ts.map +1 -0
- package/dist/errors/http.error.js +62 -0
- package/dist/filters/exception-filter.d.ts +39 -0
- package/dist/filters/exception-filter.d.ts.map +1 -0
- package/dist/filters/exception-filter.js +38 -0
- package/dist/filters/http-exception.filter.d.ts +9 -0
- package/dist/filters/http-exception.filter.d.ts.map +1 -0
- package/dist/filters/http-exception.filter.js +42 -0
- package/dist/hazel-app.d.ts +78 -0
- package/dist/hazel-app.d.ts.map +1 -0
- package/dist/hazel-app.js +453 -0
- package/dist/hazel-module.d.ts +20 -0
- package/dist/hazel-module.d.ts.map +1 -0
- package/dist/hazel-module.js +109 -0
- package/dist/hazel-response.d.ts +20 -0
- package/dist/hazel-response.d.ts.map +1 -0
- package/dist/hazel-response.js +68 -0
- package/dist/health.d.ts +73 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +174 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +140 -0
- package/dist/interceptors/interceptor.d.ts +22 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -0
- package/dist/interceptors/interceptor.js +46 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +238 -0
- package/dist/middleware/cors.middleware.d.ts +44 -0
- package/dist/middleware/cors.middleware.d.ts.map +1 -0
- package/dist/middleware/cors.middleware.js +118 -0
- package/dist/middleware/csrf.middleware.d.ts +82 -0
- package/dist/middleware/csrf.middleware.d.ts.map +1 -0
- package/dist/middleware/csrf.middleware.js +183 -0
- package/dist/middleware/global-middleware.d.ts +111 -0
- package/dist/middleware/global-middleware.d.ts.map +1 -0
- package/dist/middleware/global-middleware.js +179 -0
- package/dist/middleware/rate-limit.middleware.d.ts +73 -0
- package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
- package/dist/middleware/rate-limit.middleware.js +124 -0
- package/dist/middleware/security-headers.middleware.d.ts +76 -0
- package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
- package/dist/middleware/security-headers.middleware.js +123 -0
- package/dist/middleware/timeout.middleware.d.ts +25 -0
- package/dist/middleware/timeout.middleware.d.ts.map +1 -0
- package/dist/middleware/timeout.middleware.js +74 -0
- package/dist/middleware.d.ts +13 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +47 -0
- package/dist/pipes/pipe.d.ts +50 -0
- package/dist/pipes/pipe.d.ts.map +1 -0
- package/dist/pipes/pipe.js +96 -0
- package/dist/pipes/validation.pipe.d.ts +6 -0
- package/dist/pipes/validation.pipe.d.ts.map +1 -0
- package/dist/pipes/validation.pipe.js +61 -0
- package/dist/request-context.d.ts +17 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +2 -0
- package/dist/request-parser.d.ts +7 -0
- package/dist/request-parser.d.ts.map +1 -0
- package/dist/request-parser.js +60 -0
- package/dist/router.d.ts +33 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +426 -0
- package/dist/routing/route-matcher.d.ts +39 -0
- package/dist/routing/route-matcher.d.ts.map +1 -0
- package/dist/routing/route-matcher.js +93 -0
- package/dist/routing/version.decorator.d.ts +36 -0
- package/dist/routing/version.decorator.d.ts.map +1 -0
- package/dist/routing/version.decorator.js +89 -0
- package/dist/service.d.ts +9 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +39 -0
- package/dist/shutdown.d.ts +32 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +109 -0
- package/dist/testing/testing.module.d.ts +83 -0
- package/dist/testing/testing.module.d.ts.map +1 -0
- package/dist/testing/testing.module.js +164 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/upload/file-upload.d.ts +75 -0
- package/dist/upload/file-upload.d.ts.map +1 -0
- package/dist/upload/file-upload.js +261 -0
- package/dist/utils/sanitize.d.ts +45 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +165 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +119 -0
- package/package.json +65 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Request } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Uploaded file information
|
|
4
|
+
*/
|
|
5
|
+
export interface UploadedFile {
|
|
6
|
+
fieldname: string;
|
|
7
|
+
originalname: string;
|
|
8
|
+
encoding: string;
|
|
9
|
+
mimetype: string;
|
|
10
|
+
size: number;
|
|
11
|
+
destination: string;
|
|
12
|
+
filename: string;
|
|
13
|
+
path: string;
|
|
14
|
+
buffer?: Buffer;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* File upload options
|
|
18
|
+
*/
|
|
19
|
+
export interface FileUploadOptions {
|
|
20
|
+
destination?: string;
|
|
21
|
+
filename?: (file: Partial<UploadedFile>) => string;
|
|
22
|
+
limits?: {
|
|
23
|
+
fileSize?: number;
|
|
24
|
+
files?: number;
|
|
25
|
+
};
|
|
26
|
+
fileFilter?: (file: Partial<UploadedFile>) => boolean;
|
|
27
|
+
storage?: 'disk' | 'memory';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* File upload interceptor
|
|
31
|
+
*/
|
|
32
|
+
export declare class FileUploadInterceptor {
|
|
33
|
+
private options;
|
|
34
|
+
constructor(options?: FileUploadOptions);
|
|
35
|
+
/**
|
|
36
|
+
* Default filename generator
|
|
37
|
+
*/
|
|
38
|
+
private defaultFilename;
|
|
39
|
+
/**
|
|
40
|
+
* Parse multipart form data
|
|
41
|
+
*/
|
|
42
|
+
parseMultipart(req: Request): Promise<{
|
|
43
|
+
fields: Record<string, string>;
|
|
44
|
+
files: UploadedFile[];
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Extract boundary from Content-Type header
|
|
48
|
+
*/
|
|
49
|
+
private extractBoundary;
|
|
50
|
+
/**
|
|
51
|
+
* Parse buffer containing multipart data
|
|
52
|
+
*/
|
|
53
|
+
private parseBuffer;
|
|
54
|
+
/**
|
|
55
|
+
* Split buffer by boundary
|
|
56
|
+
*/
|
|
57
|
+
private splitBuffer;
|
|
58
|
+
/**
|
|
59
|
+
* Parse a single part
|
|
60
|
+
*/
|
|
61
|
+
private parsePart;
|
|
62
|
+
/**
|
|
63
|
+
* Process uploaded file
|
|
64
|
+
*/
|
|
65
|
+
private processFile;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* File decorator for route parameters
|
|
69
|
+
*/
|
|
70
|
+
export declare function UploadedFileDecorator(fieldname?: string): ParameterDecorator;
|
|
71
|
+
/**
|
|
72
|
+
* Files decorator for multiple files
|
|
73
|
+
*/
|
|
74
|
+
export declare function UploadedFilesDecorator(fieldname?: string): ParameterDecorator;
|
|
75
|
+
//# sourceMappingURL=file-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-upload.d.ts","sourceRoot":"","sources":["../../src/upload/file-upload.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAMnC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,MAAM,CAAC;IACnD,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC7B;AAED;;GAEG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,OAAO,CAA8B;gBAEjC,OAAO,GAAE,iBAAsB;IAe3C;;OAEG;IACH,OAAO,CAAC,eAAe;IAOvB;;OAEG;IACG,cAAc,CAClB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IAkCrE;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB;;OAEG;YACW,WAAW;IA4CzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAkBnB;;OAEG;IACH,OAAO,CAAC,SAAS;IAwBjB;;OAEG;YACW,WAAW;CAsC1B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAqB5E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAqB7E"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.FileUploadInterceptor = void 0;
|
|
40
|
+
exports.UploadedFileDecorator = UploadedFileDecorator;
|
|
41
|
+
exports.UploadedFilesDecorator = UploadedFilesDecorator;
|
|
42
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const crypto = __importStar(require("crypto"));
|
|
46
|
+
/**
|
|
47
|
+
* File upload interceptor
|
|
48
|
+
*/
|
|
49
|
+
class FileUploadInterceptor {
|
|
50
|
+
constructor(options = {}) {
|
|
51
|
+
this.options = {
|
|
52
|
+
destination: options.destination || './uploads',
|
|
53
|
+
filename: options.filename || this.defaultFilename,
|
|
54
|
+
limits: options.limits || { fileSize: 10 * 1024 * 1024, files: 10 },
|
|
55
|
+
fileFilter: options.fileFilter || (() => true),
|
|
56
|
+
storage: options.storage || 'disk',
|
|
57
|
+
};
|
|
58
|
+
// Ensure destination directory exists
|
|
59
|
+
if (this.options.storage === 'disk' && !fs.existsSync(this.options.destination)) {
|
|
60
|
+
fs.mkdirSync(this.options.destination, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Default filename generator
|
|
65
|
+
*/
|
|
66
|
+
defaultFilename(file) {
|
|
67
|
+
const timestamp = Date.now();
|
|
68
|
+
const random = crypto.randomBytes(8).toString('hex');
|
|
69
|
+
const ext = path.extname(file.originalname || '');
|
|
70
|
+
return `${timestamp}-${random}${ext}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse multipart form data
|
|
74
|
+
*/
|
|
75
|
+
async parseMultipart(req) {
|
|
76
|
+
const contentType = req.headers?.['content-type'] || '';
|
|
77
|
+
if (!contentType.includes('multipart/form-data')) {
|
|
78
|
+
throw new Error('Content-Type must be multipart/form-data');
|
|
79
|
+
}
|
|
80
|
+
const boundary = this.extractBoundary(contentType);
|
|
81
|
+
if (!boundary) {
|
|
82
|
+
throw new Error('Missing boundary in Content-Type');
|
|
83
|
+
}
|
|
84
|
+
const chunks = [];
|
|
85
|
+
const incomingMessage = req;
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
incomingMessage.on('data', (chunk) => {
|
|
88
|
+
chunks.push(chunk);
|
|
89
|
+
});
|
|
90
|
+
incomingMessage.on('end', async () => {
|
|
91
|
+
try {
|
|
92
|
+
const buffer = Buffer.concat(chunks);
|
|
93
|
+
const result = await this.parseBuffer(buffer, boundary);
|
|
94
|
+
resolve(result);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
reject(error);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
incomingMessage.on('error', reject);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Extract boundary from Content-Type header
|
|
105
|
+
*/
|
|
106
|
+
extractBoundary(contentType) {
|
|
107
|
+
const match = contentType.match(/boundary=([^;]+)/);
|
|
108
|
+
return match ? match[1].trim() : null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Parse buffer containing multipart data
|
|
112
|
+
*/
|
|
113
|
+
async parseBuffer(buffer, boundary) {
|
|
114
|
+
const fields = {};
|
|
115
|
+
const files = [];
|
|
116
|
+
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
|
117
|
+
const parts = this.splitBuffer(buffer, boundaryBuffer);
|
|
118
|
+
for (const part of parts) {
|
|
119
|
+
if (part.length === 0)
|
|
120
|
+
continue;
|
|
121
|
+
const { headers, body } = this.parsePart(part);
|
|
122
|
+
const disposition = headers['content-disposition'];
|
|
123
|
+
if (!disposition)
|
|
124
|
+
continue;
|
|
125
|
+
const nameMatch = disposition.match(/name="([^"]+)"/);
|
|
126
|
+
const filenameMatch = disposition.match(/filename="([^"]+)"/);
|
|
127
|
+
if (filenameMatch) {
|
|
128
|
+
// It's a file
|
|
129
|
+
const file = await this.processFile({
|
|
130
|
+
fieldname: nameMatch ? nameMatch[1] : 'file',
|
|
131
|
+
originalname: filenameMatch[1],
|
|
132
|
+
encoding: headers['content-transfer-encoding'] || '7bit',
|
|
133
|
+
mimetype: headers['content-type'] || 'application/octet-stream',
|
|
134
|
+
size: body.length,
|
|
135
|
+
buffer: body,
|
|
136
|
+
});
|
|
137
|
+
if (file) {
|
|
138
|
+
files.push(file);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (nameMatch) {
|
|
142
|
+
// It's a field
|
|
143
|
+
fields[nameMatch[1]] = body.toString('utf-8');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { fields, files };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Split buffer by boundary
|
|
150
|
+
*/
|
|
151
|
+
splitBuffer(buffer, boundary) {
|
|
152
|
+
const parts = [];
|
|
153
|
+
let start = 0;
|
|
154
|
+
while (start < buffer.length) {
|
|
155
|
+
const index = buffer.indexOf(boundary, start);
|
|
156
|
+
if (index === -1)
|
|
157
|
+
break;
|
|
158
|
+
if (start !== index) {
|
|
159
|
+
parts.push(buffer.slice(start, index));
|
|
160
|
+
}
|
|
161
|
+
start = index + boundary.length;
|
|
162
|
+
}
|
|
163
|
+
return parts;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Parse a single part
|
|
167
|
+
*/
|
|
168
|
+
parsePart(part) {
|
|
169
|
+
const headerEnd = part.indexOf('\r\n\r\n');
|
|
170
|
+
if (headerEnd === -1) {
|
|
171
|
+
return { headers: {}, body: part };
|
|
172
|
+
}
|
|
173
|
+
const headerSection = part.slice(0, headerEnd).toString('utf-8');
|
|
174
|
+
const body = part.slice(headerEnd + 4);
|
|
175
|
+
const headers = {};
|
|
176
|
+
const lines = headerSection.split('\r\n');
|
|
177
|
+
for (const line of lines) {
|
|
178
|
+
const colonIndex = line.indexOf(':');
|
|
179
|
+
if (colonIndex !== -1) {
|
|
180
|
+
const key = line.slice(0, colonIndex).trim().toLowerCase();
|
|
181
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
182
|
+
headers[key] = value;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { headers, body };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Process uploaded file
|
|
189
|
+
*/
|
|
190
|
+
async processFile(file) {
|
|
191
|
+
// Check file filter
|
|
192
|
+
if (!this.options.fileFilter(file)) {
|
|
193
|
+
logger_1.default.warn(`File rejected by filter: ${file.originalname}`);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
// Check file size
|
|
197
|
+
if (file.size && file.size > (this.options.limits.fileSize || Infinity)) {
|
|
198
|
+
throw new Error(`File too large: ${file.originalname} (${file.size} bytes)`);
|
|
199
|
+
}
|
|
200
|
+
const filename = this.options.filename(file);
|
|
201
|
+
const destination = this.options.destination;
|
|
202
|
+
const filepath = path.join(destination, filename);
|
|
203
|
+
const uploadedFile = {
|
|
204
|
+
fieldname: file.fieldname,
|
|
205
|
+
originalname: file.originalname,
|
|
206
|
+
encoding: file.encoding,
|
|
207
|
+
mimetype: file.mimetype,
|
|
208
|
+
size: file.size,
|
|
209
|
+
destination,
|
|
210
|
+
filename,
|
|
211
|
+
path: filepath,
|
|
212
|
+
};
|
|
213
|
+
if (this.options.storage === 'disk') {
|
|
214
|
+
// Save to disk
|
|
215
|
+
await fs.promises.writeFile(filepath, file.buffer);
|
|
216
|
+
logger_1.default.info(`File saved: ${filepath}`);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// Keep in memory
|
|
220
|
+
uploadedFile.buffer = file.buffer;
|
|
221
|
+
}
|
|
222
|
+
return uploadedFile;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.FileUploadInterceptor = FileUploadInterceptor;
|
|
226
|
+
/**
|
|
227
|
+
* File decorator for route parameters
|
|
228
|
+
*/
|
|
229
|
+
function UploadedFileDecorator(fieldname) {
|
|
230
|
+
return (target, propertyKey, parameterIndex) => {
|
|
231
|
+
if (!propertyKey) {
|
|
232
|
+
throw new Error('UploadedFile decorator must be used on a method parameter');
|
|
233
|
+
}
|
|
234
|
+
const constructor = target
|
|
235
|
+
.constructor;
|
|
236
|
+
const injections = Reflect.getMetadata('hazel:inject', constructor, propertyKey) || [];
|
|
237
|
+
injections[parameterIndex] = {
|
|
238
|
+
type: 'file',
|
|
239
|
+
fieldname,
|
|
240
|
+
};
|
|
241
|
+
Reflect.defineMetadata('hazel:inject', injections, constructor, propertyKey);
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Files decorator for multiple files
|
|
246
|
+
*/
|
|
247
|
+
function UploadedFilesDecorator(fieldname) {
|
|
248
|
+
return (target, propertyKey, parameterIndex) => {
|
|
249
|
+
if (!propertyKey) {
|
|
250
|
+
throw new Error('UploadedFiles decorator must be used on a method parameter');
|
|
251
|
+
}
|
|
252
|
+
const constructor = target
|
|
253
|
+
.constructor;
|
|
254
|
+
const injections = Reflect.getMetadata('hazel:inject', constructor, propertyKey) || [];
|
|
255
|
+
injections[parameterIndex] = {
|
|
256
|
+
type: 'files',
|
|
257
|
+
fieldname,
|
|
258
|
+
};
|
|
259
|
+
Reflect.defineMetadata('hazel:inject', injections, constructor, propertyKey);
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input sanitization utilities
|
|
3
|
+
* Helps prevent XSS and injection attacks
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Sanitize HTML string
|
|
7
|
+
* Removes potentially dangerous HTML tags and attributes
|
|
8
|
+
*/
|
|
9
|
+
export declare function sanitizeHtml(input: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Sanitize string input
|
|
12
|
+
* Removes control characters and normalizes whitespace
|
|
13
|
+
*/
|
|
14
|
+
export declare function sanitizeString(input: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Sanitize URL
|
|
17
|
+
* Validates and sanitizes URL input
|
|
18
|
+
*/
|
|
19
|
+
export declare function sanitizeUrl(input: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Sanitize email
|
|
22
|
+
* Basic email validation and sanitization
|
|
23
|
+
*/
|
|
24
|
+
export declare function sanitizeEmail(input: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize SQL input (for raw queries)
|
|
27
|
+
* Escapes special characters (use parameterized queries instead!)
|
|
28
|
+
*/
|
|
29
|
+
export declare function sanitizeSql(input: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Sanitize object recursively
|
|
32
|
+
* Applies sanitization to all string values in an object
|
|
33
|
+
*/
|
|
34
|
+
export declare function sanitizeObject<T extends Record<string, unknown>>(obj: T, options?: {
|
|
35
|
+
sanitizeHtml?: boolean;
|
|
36
|
+
sanitizeStrings?: boolean;
|
|
37
|
+
allowedKeys?: string[];
|
|
38
|
+
maxDepth?: number;
|
|
39
|
+
}): T;
|
|
40
|
+
/**
|
|
41
|
+
* Escape HTML entities
|
|
42
|
+
* Converts special characters to HTML entities
|
|
43
|
+
*/
|
|
44
|
+
export declare function escapeHtml(input: string): string;
|
|
45
|
+
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYlD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASpD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAejD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAanD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,GAAG,EAAE,CAAC,EACN,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACd,GACL,CAAC,CA2DH;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAchD"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Input sanitization utilities
|
|
4
|
+
* Helps prevent XSS and injection attacks
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.sanitizeHtml = sanitizeHtml;
|
|
8
|
+
exports.sanitizeString = sanitizeString;
|
|
9
|
+
exports.sanitizeUrl = sanitizeUrl;
|
|
10
|
+
exports.sanitizeEmail = sanitizeEmail;
|
|
11
|
+
exports.sanitizeSql = sanitizeSql;
|
|
12
|
+
exports.sanitizeObject = sanitizeObject;
|
|
13
|
+
exports.escapeHtml = escapeHtml;
|
|
14
|
+
/**
|
|
15
|
+
* Sanitize HTML string
|
|
16
|
+
* Removes potentially dangerous HTML tags and attributes
|
|
17
|
+
*/
|
|
18
|
+
function sanitizeHtml(input) {
|
|
19
|
+
if (typeof input !== 'string') {
|
|
20
|
+
return String(input);
|
|
21
|
+
}
|
|
22
|
+
// Remove script tags and event handlers
|
|
23
|
+
return input
|
|
24
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
25
|
+
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
|
|
26
|
+
.replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')
|
|
27
|
+
.replace(/javascript:/gi, '')
|
|
28
|
+
.replace(/data:text\/html/gi, '');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Sanitize string input
|
|
32
|
+
* Removes control characters and normalizes whitespace
|
|
33
|
+
*/
|
|
34
|
+
function sanitizeString(input) {
|
|
35
|
+
if (typeof input !== 'string') {
|
|
36
|
+
return String(input);
|
|
37
|
+
}
|
|
38
|
+
return input
|
|
39
|
+
.replace(/[\x00-\x1F\x7F-\x9F]/g, '') // Remove control characters
|
|
40
|
+
.trim()
|
|
41
|
+
.replace(/\s+/g, ' '); // Normalize whitespace
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Sanitize URL
|
|
45
|
+
* Validates and sanitizes URL input
|
|
46
|
+
*/
|
|
47
|
+
function sanitizeUrl(input) {
|
|
48
|
+
if (typeof input !== 'string') {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const url = new URL(input);
|
|
53
|
+
// Only allow http and https protocols
|
|
54
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
return url.toString();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Sanitize email
|
|
65
|
+
* Basic email validation and sanitization
|
|
66
|
+
*/
|
|
67
|
+
function sanitizeEmail(input) {
|
|
68
|
+
if (typeof input !== 'string') {
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
const email = input.trim().toLowerCase();
|
|
72
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
73
|
+
if (!emailRegex.test(email)) {
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
return email;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Sanitize SQL input (for raw queries)
|
|
80
|
+
* Escapes special characters (use parameterized queries instead!)
|
|
81
|
+
*/
|
|
82
|
+
function sanitizeSql(input) {
|
|
83
|
+
if (typeof input !== 'string') {
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
// WARNING: This is a basic sanitization
|
|
87
|
+
// Always use parameterized queries with Prisma instead!
|
|
88
|
+
return input
|
|
89
|
+
.replace(/'/g, "''")
|
|
90
|
+
.replace(/;/g, '')
|
|
91
|
+
.replace(/--/g, '')
|
|
92
|
+
.replace(/\/\*/g, '')
|
|
93
|
+
.replace(/\*\//g, '');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Sanitize object recursively
|
|
97
|
+
* Applies sanitization to all string values in an object
|
|
98
|
+
*/
|
|
99
|
+
function sanitizeObject(obj, options = {}) {
|
|
100
|
+
const { sanitizeHtml: sanitizeHtmlFields = false, sanitizeStrings: sanitizeStringFields = true, allowedKeys, maxDepth = 10, } = options;
|
|
101
|
+
if (maxDepth <= 0) {
|
|
102
|
+
return obj;
|
|
103
|
+
}
|
|
104
|
+
const sanitized = {};
|
|
105
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
106
|
+
// Skip disallowed keys
|
|
107
|
+
if (allowedKeys && !allowedKeys.includes(key)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (typeof value === 'string') {
|
|
111
|
+
if (sanitizeHtmlFields) {
|
|
112
|
+
sanitized[key] = sanitizeHtml(value);
|
|
113
|
+
}
|
|
114
|
+
else if (sanitizeStringFields) {
|
|
115
|
+
sanitized[key] = sanitizeString(value);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
sanitized[key] = value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (Array.isArray(value)) {
|
|
122
|
+
sanitized[key] = value.map((item) => typeof item === 'object' && item !== null
|
|
123
|
+
? sanitizeObject(item, {
|
|
124
|
+
sanitizeHtml: sanitizeHtmlFields,
|
|
125
|
+
sanitizeStrings: sanitizeStringFields,
|
|
126
|
+
maxDepth: maxDepth - 1,
|
|
127
|
+
})
|
|
128
|
+
: typeof item === 'string'
|
|
129
|
+
? sanitizeHtmlFields
|
|
130
|
+
? sanitizeHtml(item)
|
|
131
|
+
: sanitizeStringFields
|
|
132
|
+
? sanitizeString(item)
|
|
133
|
+
: item
|
|
134
|
+
: item);
|
|
135
|
+
}
|
|
136
|
+
else if (typeof value === 'object' && value !== null) {
|
|
137
|
+
sanitized[key] = sanitizeObject(value, {
|
|
138
|
+
sanitizeHtml: sanitizeHtmlFields,
|
|
139
|
+
sanitizeStrings: sanitizeStringFields,
|
|
140
|
+
maxDepth: maxDepth - 1,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
sanitized[key] = value;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return sanitized;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Escape HTML entities
|
|
151
|
+
* Converts special characters to HTML entities
|
|
152
|
+
*/
|
|
153
|
+
function escapeHtml(input) {
|
|
154
|
+
if (typeof input !== 'string') {
|
|
155
|
+
return String(input);
|
|
156
|
+
}
|
|
157
|
+
const map = {
|
|
158
|
+
'&': '&',
|
|
159
|
+
'<': '<',
|
|
160
|
+
'>': '>',
|
|
161
|
+
'"': '"',
|
|
162
|
+
"'": ''',
|
|
163
|
+
};
|
|
164
|
+
return input.replace(/[&<>"']/g, (m) => map[m]);
|
|
165
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ValidationSchema, ValidationError } from './types';
|
|
2
|
+
type ValidatableData = Record<string, unknown>;
|
|
3
|
+
export declare class Validator {
|
|
4
|
+
static validate(data: ValidatableData, schema: ValidationSchema): ValidationError[];
|
|
5
|
+
}
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE5D,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,qBAAa,SAAS;IACpB,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,gBAAgB,GAAG,eAAe,EAAE;CAsHpF"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Validator = void 0;
|
|
4
|
+
class Validator {
|
|
5
|
+
static validate(data, schema) {
|
|
6
|
+
const errors = [];
|
|
7
|
+
for (const [field, rules] of Object.entries(schema)) {
|
|
8
|
+
const value = data[field];
|
|
9
|
+
if (rules.required && (value === undefined || value === null || value === '')) {
|
|
10
|
+
errors.push({
|
|
11
|
+
field,
|
|
12
|
+
message: `${field} is required`,
|
|
13
|
+
});
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (value === undefined || value === null) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
switch (rules.type) {
|
|
20
|
+
case 'string':
|
|
21
|
+
if (typeof value !== 'string') {
|
|
22
|
+
errors.push({
|
|
23
|
+
field,
|
|
24
|
+
message: `${field} must be a string`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
if (rules.min !== undefined && value.length < rules.min) {
|
|
29
|
+
errors.push({
|
|
30
|
+
field,
|
|
31
|
+
message: `${field} must be at least ${rules.min} characters`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (rules.max !== undefined && value.length > rules.max) {
|
|
35
|
+
errors.push({
|
|
36
|
+
field,
|
|
37
|
+
message: `${field} must be at most ${rules.max} characters`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (rules.pattern && !rules.pattern.test(value)) {
|
|
41
|
+
errors.push({
|
|
42
|
+
field,
|
|
43
|
+
message: `${field} has invalid format`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case 'number':
|
|
49
|
+
if (typeof value !== 'number') {
|
|
50
|
+
errors.push({
|
|
51
|
+
field,
|
|
52
|
+
message: `${field} must be a number`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
if (rules.min !== undefined && value < rules.min) {
|
|
57
|
+
errors.push({
|
|
58
|
+
field,
|
|
59
|
+
message: `${field} must be at least ${rules.min}`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (rules.max !== undefined && value > rules.max) {
|
|
63
|
+
errors.push({
|
|
64
|
+
field,
|
|
65
|
+
message: `${field} must be at most ${rules.max}`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
case 'boolean':
|
|
71
|
+
if (typeof value !== 'boolean') {
|
|
72
|
+
errors.push({
|
|
73
|
+
field,
|
|
74
|
+
message: `${field} must be a boolean`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
case 'object':
|
|
79
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
80
|
+
errors.push({
|
|
81
|
+
field,
|
|
82
|
+
message: `${field} must be an object`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else if (rules.properties) {
|
|
86
|
+
errors.push(...this.validate(value, rules.properties));
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case 'array':
|
|
90
|
+
if (!Array.isArray(value)) {
|
|
91
|
+
errors.push({
|
|
92
|
+
field,
|
|
93
|
+
message: `${field} must be an array`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else if (rules.items) {
|
|
97
|
+
value.forEach((item, index) => {
|
|
98
|
+
const itemSchema = {
|
|
99
|
+
[`item_${index}`]: {
|
|
100
|
+
type: rules.items.type,
|
|
101
|
+
required: rules.items.required,
|
|
102
|
+
min: rules.items.min,
|
|
103
|
+
max: rules.items.max,
|
|
104
|
+
pattern: rules.items.pattern,
|
|
105
|
+
properties: rules.items.properties,
|
|
106
|
+
items: rules.items.items,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const itemErrors = this.validate({ [`item_${index}`]: item }, itemSchema);
|
|
110
|
+
errors.push(...itemErrors);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return errors;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.Validator = Validator;
|