@rsdk/http.server 6.0.0-next.1 → 6.0.0-next.11

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.
@@ -1,3 +1,3 @@
1
1
  export * from './http-errors.formatter';
2
2
  export * from './http-errors.sender';
3
- export * from './http-errors.transformer';
3
+ export * from './no-interception.filter';
@@ -16,5 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./http-errors.formatter"), exports);
18
18
  __exportStar(require("./http-errors.sender"), exports);
19
- __exportStar(require("./http-errors.transformer"), exports);
19
+ __exportStar(require("./no-interception.filter"), exports);
20
20
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/error-handling/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,uDAAqC;AACrC,4DAA0C"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/error-handling/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,uDAAqC;AACrC,2DAAyC"}
@@ -0,0 +1,19 @@
1
+ import { ArgumentsHost, ExceptionFilter, NotFoundException as NestNotFoundException } from '@nestjs/common';
2
+ import { ILogger } from '@rsdk/logging';
3
+ import { Observable } from 'rxjs';
4
+ /**
5
+ * Специализированный фильтр для обработки ошибок NotFound.
6
+ * Имеет более высокий приоритет, чем GlobalExceptionsFilter,
7
+ * т. к. задуман для перехвата только NotFoundException. Её выбрасывает
8
+ * nest, когда не находит маршрут.
9
+ *
10
+ * К сожалению, эти ошибки не попадают в интерцепторы и, соответственно,
11
+ * приходится обрабатывать их вот так отдельно.
12
+ */
13
+ export declare class RouteNotFoundFilter implements ExceptionFilter {
14
+ private readonly logger;
15
+ private readonly formatter;
16
+ private readonly sender;
17
+ constructor(logger: ILogger);
18
+ catch(exception: NestNotFoundException, host: ArgumentsHost): Observable<any>;
19
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.RouteNotFoundFilter = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@rsdk/core");
18
+ const http_errors_formatter_1 = require("./http-errors.formatter");
19
+ const http_errors_sender_1 = require("./http-errors.sender");
20
+ /**
21
+ * Специализированный фильтр для обработки ошибок NotFound.
22
+ * Имеет более высокий приоритет, чем GlobalExceptionsFilter,
23
+ * т. к. задуман для перехвата только NotFoundException. Её выбрасывает
24
+ * nest, когда не находит маршрут.
25
+ *
26
+ * К сожалению, эти ошибки не попадают в интерцепторы и, соответственно,
27
+ * приходится обрабатывать их вот так отдельно.
28
+ */
29
+ let RouteNotFoundFilter = class RouteNotFoundFilter {
30
+ logger;
31
+ formatter = new http_errors_formatter_1.HttpErrorsFormatter();
32
+ sender = new http_errors_sender_1.HttpErrorsSender();
33
+ constructor(logger) {
34
+ this.logger = logger;
35
+ }
36
+ catch(exception, host) {
37
+ const ctx = host.switchToHttp();
38
+ const { method, path } = ctx.getRequest();
39
+ const pipelineEx = new core_1.NotFoundException(exception.message);
40
+ // Логируем ошибку со всеми деталями
41
+ this.logger.error(`Route not found [HTTP] ${method} ${path}`);
42
+ const formattedEx = this.formatter.format(pipelineEx);
43
+ return this.sender.send(host, formattedEx);
44
+ }
45
+ };
46
+ exports.RouteNotFoundFilter = RouteNotFoundFilter;
47
+ exports.RouteNotFoundFilter = RouteNotFoundFilter = __decorate([
48
+ (0, common_1.Catch)(common_1.NotFoundException),
49
+ __param(0, (0, core_1.InjectLogger)(RouteNotFoundFilter)),
50
+ __metadata("design:paramtypes", [Object])
51
+ ], RouteNotFoundFilter);
52
+ //# sourceMappingURL=no-interception.filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-interception.filter.js","sourceRoot":"","sources":["../../src/error-handling/no-interception.filter.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAKwB;AACxB,qCAA6D;AAI7D,mEAA8D;AAC9D,6DAAwD;AAExD;;;;;;;;GAQG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAKwB;IAJrC,SAAS,GAAG,IAAI,2CAAmB,EAAE,CAAC;IACtC,MAAM,GAAG,IAAI,qCAAgB,EAAE,CAAC;IAEjD,YACsD,MAAe;QAAf,WAAM,GAAN,MAAM,CAAS;IAClE,CAAC;IAEJ,KAAK,CACH,SAAgC,EAChC,IAAmB;QAEnB,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAChC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;QAE1C,MAAM,UAAU,GAAG,IAAI,wBAAiB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAE5D,oCAAoC;QACpC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;QAE9D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAEtD,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC7C,CAAC;CACF,CAAA;AAxBY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,cAAK,EAAC,0BAAqB,CAAC;IAMxB,WAAA,IAAA,mBAAY,EAAC,mBAAmB,CAAC,CAAA;;GALzB,mBAAmB,CAwB/B"}
@@ -20,14 +20,14 @@ let HttpConfig = class HttpConfig extends core_1.Config {
20
20
  };
21
21
  exports.HttpConfig = HttpConfig;
22
22
  __decorate([
23
- (0, core_1.Property)('HTTP_HOST', new core_1.StringParser(), {
23
+ (0, core_1.Property)('HTTP_HOST', new common_1.StringParser(), {
24
24
  defaultValue: '0.0.0.0',
25
25
  description: 'HTTP host',
26
26
  }),
27
27
  __metadata("design:type", String)
28
28
  ], HttpConfig.prototype, "host", void 0);
29
29
  __decorate([
30
- (0, core_1.Property)('HTTP_PORT', new core_1.IntParser(), {
30
+ (0, core_1.Property)('HTTP_PORT', new common_1.IntParser(), {
31
31
  // eslint-disable-next-line unicorn/numeric-separators-style
32
32
  defaultValue: 50050,
33
33
  description: 'HTTP port',
@@ -35,16 +35,16 @@ __decorate([
35
35
  __metadata("design:type", Number)
36
36
  ], HttpConfig.prototype, "port", void 0);
37
37
  __decorate([
38
- (0, core_1.Property)('HTTP_BODY_LIMIT', new core_1.SizeParser(), {
38
+ (0, core_1.Property)('HTTP_BODY_LIMIT', new common_1.SizeParser(), {
39
39
  defaultValue: new common_1.Size(100, 'kb'),
40
40
  description: 'Max HTTP body size',
41
41
  }),
42
42
  __metadata("design:type", common_1.Size)
43
43
  ], HttpConfig.prototype, "bodyLimit", void 0);
44
44
  __decorate([
45
- (0, core_1.Property)('HTTP_SWAGGER_UI_ENABLED', new core_1.BoolParser(), {
45
+ (0, core_1.Property)('HTTP_SWAGGER_UI_ENABLED', new common_1.BoolParser(), {
46
46
  defaultValue: true,
47
- description: 'Enable swagger UI on /swagger endpoint',
47
+ description: 'Enable swagger UI on /swagger (can be configured) endpoint',
48
48
  }),
49
49
  __metadata("design:type", Boolean)
50
50
  ], HttpConfig.prototype, "swaggerUI", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"http.config.js","sourceRoot":"","sources":["../src/http.config.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,yCAAoC;AACpC,qCASoB;AAKb,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,aAAM;IAK3B,IAAI,CAAU;IAOd,IAAI,CAAU;IAMd,SAAS,CAAQ;IAMjB,SAAS,CAAW;CAC9B,CAAA;AAzBY,gCAAU;AAKZ;IAJR,IAAA,eAAQ,EAAC,WAAW,EAAE,IAAI,mBAAY,EAAE,EAAE;QACzC,YAAY,EAAE,SAAS;QACvB,WAAW,EAAE,WAAW;KACzB,CAAC;;wCACqB;AAOd;IALR,IAAA,eAAQ,EAAC,WAAW,EAAE,IAAI,gBAAS,EAAE,EAAE;QACtC,4DAA4D;QAC5D,YAAY,EAAE,KAAK;QACnB,WAAW,EAAE,WAAW;KACzB,CAAC;;wCACqB;AAMd;IAJR,IAAA,eAAQ,EAAC,iBAAiB,EAAE,IAAI,iBAAU,EAAE,EAAE;QAC7C,YAAY,EAAE,IAAI,aAAI,CAAC,GAAG,EAAE,IAAI,CAAC;QACjC,WAAW,EAAE,oBAAoB;KAClC,CAAC;8BACmB,aAAI;6CAAC;AAMjB;IAJR,IAAA,eAAQ,EAAC,yBAAyB,EAAE,IAAI,iBAAU,EAAE,EAAE;QACrD,YAAY,EAAE,IAAI;QAClB,WAAW,EAAE,wCAAwC;KACtD,CAAC;;6CAC2B;qBAxBlB,UAAU;IAHtB,IAAA,oBAAa,EAAC;QACb,IAAI,EAAE,CAAC,gBAAS,CAAC,cAAc,EAAE,gBAAS,CAAC,SAAS,EAAE,gBAAS,CAAC,IAAI,CAAC;KACtE,CAAC;GACW,UAAU,CAyBtB"}
1
+ {"version":3,"file":"http.config.js","sourceRoot":"","sources":["../src/http.config.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,yCAMsB;AACtB,qCAAwE;AAKjE,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,aAAM;IAK3B,IAAI,CAAU;IAOd,IAAI,CAAU;IAMd,SAAS,CAAQ;IAMjB,SAAS,CAAW;CAC9B,CAAA;AAzBY,gCAAU;AAKZ;IAJR,IAAA,eAAQ,EAAC,WAAW,EAAE,IAAI,qBAAY,EAAE,EAAE;QACzC,YAAY,EAAE,SAAS;QACvB,WAAW,EAAE,WAAW;KACzB,CAAC;;wCACqB;AAOd;IALR,IAAA,eAAQ,EAAC,WAAW,EAAE,IAAI,kBAAS,EAAE,EAAE;QACtC,4DAA4D;QAC5D,YAAY,EAAE,KAAK;QACnB,WAAW,EAAE,WAAW;KACzB,CAAC;;wCACqB;AAMd;IAJR,IAAA,eAAQ,EAAC,iBAAiB,EAAE,IAAI,mBAAU,EAAE,EAAE;QAC7C,YAAY,EAAE,IAAI,aAAI,CAAC,GAAG,EAAE,IAAI,CAAC;QACjC,WAAW,EAAE,oBAAoB;KAClC,CAAC;8BACmB,aAAI;6CAAC;AAMjB;IAJR,IAAA,eAAQ,EAAC,yBAAyB,EAAE,IAAI,mBAAU,EAAE,EAAE;QACrD,YAAY,EAAE,IAAI;QAClB,WAAW,EAAE,4DAA4D;KAC1E,CAAC;;6CAC2B;qBAxBlB,UAAU;IAHtB,IAAA,oBAAa,EAAC;QACb,IAAI,EAAE,CAAC,gBAAS,CAAC,cAAc,EAAE,gBAAS,CAAC,SAAS,EAAE,gBAAS,CAAC,IAAI,CAAC;KACtE,CAAC;GACW,UAAU,CAyBtB"}
@@ -1,10 +1,11 @@
1
1
  import { type ExecutionContext } from '@nestjs/common';
2
2
  import type { Controller, INestApplication } from '@nestjs/common/interfaces';
3
3
  import type { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
4
- import type { AbstractHttpAdapter } from '@nestjs/core';
5
- import { DocumentBuilder as OriginalDocumentBuilder } from '@nestjs/swagger';
4
+ import { type AbstractHttpAdapter } from '@nestjs/core';
6
5
  import type { Constructor, DeepPartial } from '@rsdk/common';
7
- import type { ConfigContext, GenericHeaders, HttpOptions, IErrorsFormatter, IErrorsSender, IErrorsTransformer, IHttpTransport, LogFormatter } from '@rsdk/core';
6
+ import type { ConfigContext, GenericHeaders, HttpOptions, IErrorsFormatter, IErrorsSender, IErrorsTransformer, IHttpTransport, LogFormatter, NestModuleDefinitions, PlatformContext } from '@rsdk/core';
7
+ import type { OpenApiOptions } from '@rsdk/http.openapi';
8
+ import type { ILogger } from '@rsdk/logging';
8
9
  import { HttpConfig } from './http.config';
9
10
  export interface ParsersConfig {
10
11
  body: {
@@ -13,12 +14,12 @@ export interface ParsersConfig {
13
14
  };
14
15
  cookie: boolean;
15
16
  }
16
- export type DocumentBuilder = Omit<OriginalDocumentBuilder, 'build' | 'setTitle' | 'setDescription' | 'setVersion'>;
17
- export interface SwaggerOptions {
18
- url: string;
19
- file: string;
20
- configure(builder: DocumentBuilder): void;
21
- }
17
+ export type SwaggerOptions = Partial<OpenApiOptions> | {
18
+ fromFile: string;
19
+ };
20
+ export declare function isFromFile(swagger: SwaggerOptions): swagger is {
21
+ fromFile: string;
22
+ };
22
23
  export interface HttpTransportOptions {
23
24
  /**
24
25
  * CORS middleware options
@@ -30,7 +31,6 @@ export interface HttpTransportOptions {
30
31
  globalPrefix?: string;
31
32
  /**
32
33
  * Configuration of body and cookie parsers
33
- // NOTE: This fields is actually is handled in transport specific way
34
34
  */
35
35
  parsers?: DeepPartial<ParsersConfig>;
36
36
  /**
@@ -40,11 +40,11 @@ export interface HttpTransportOptions {
40
40
  * NOTE: SwaggerUI is enabled or disabled by HTTP_SWAGGER_UI_ENABLED
41
41
  * (true by default).
42
42
  */
43
- swagger?: DeepPartial<SwaggerOptions>;
43
+ swagger?: Record<string, SwaggerOptions>;
44
44
  }
45
45
  export declare abstract class AbstractHttpTransport implements IHttpTransport {
46
- protected readonly options?: HttpTransportOptions | undefined;
47
- constructor(options?: HttpTransportOptions | undefined);
46
+ protected readonly options: HttpTransportOptions;
47
+ constructor(options?: HttpTransportOptions);
48
48
  extractHeaders(ctx: ExecutionContext): GenericHeaders;
49
49
  matchByContext(ctx: ExecutionContext): boolean;
50
50
  errorFormatter(): IErrorsFormatter;
@@ -55,7 +55,8 @@ export declare abstract class AbstractHttpTransport implements IHttpTransport {
55
55
  getMetricsController(): Constructor<Controller>;
56
56
  createHttpOptions(configContext: ConfigContext): HttpOptions;
57
57
  createAdapter(configContext: ConfigContext): AbstractHttpAdapter;
58
- configureApp(app: INestApplication, configContext: ConfigContext): Promise<void> | void;
58
+ modules(): NestModuleDefinitions;
59
+ configureApp(app: INestApplication, context: PlatformContext, logger: ILogger): Promise<void>;
59
60
  abstract createHttpAdapter(config: HttpConfig): AbstractHttpAdapter;
60
61
  abstract logFormatter(): LogFormatter;
61
62
  }
@@ -1,16 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AbstractHttpTransport = void 0;
4
+ exports.isFromFile = isFromFile;
5
+ const core_1 = require("@nestjs/core");
4
6
  const swagger_1 = require("@nestjs/swagger");
5
- const core_1 = require("@rsdk/core");
6
- const node_fs_1 = require("node:fs");
7
+ const common_node_1 = require("@rsdk/common.node");
8
+ const core_2 = require("@rsdk/core");
9
+ const http_openapi_1 = require("@rsdk/http.openapi");
10
+ const no_interception_filter_1 = require("./error-handling/no-interception.filter");
7
11
  const controllers_1 = require("./controllers");
8
12
  const error_handling_1 = require("./error-handling");
9
13
  const http_config_1 = require("./http.config");
10
14
  const http_headers_1 = require("./http.headers");
15
+ function isFromFile(swagger) {
16
+ return typeof swagger === 'object' && 'fromFile' in swagger;
17
+ }
11
18
  class AbstractHttpTransport {
12
19
  options;
13
- constructor(options) {
20
+ constructor(options = {}) {
14
21
  this.options = options;
15
22
  }
16
23
  extractHeaders(ctx) {
@@ -26,7 +33,7 @@ class AbstractHttpTransport {
26
33
  return new error_handling_1.HttpErrorsSender();
27
34
  }
28
35
  errorTransformers() {
29
- return [new error_handling_1.HttpErrorsTransformer()];
36
+ return [];
30
37
  }
31
38
  getProtocol() {
32
39
  return 'http';
@@ -39,38 +46,51 @@ class AbstractHttpTransport {
39
46
  }
40
47
  createHttpOptions(configContext) {
41
48
  const { host, port } = configContext.resolve(http_config_1.HttpConfig);
42
- return {
43
- host,
44
- port,
45
- };
49
+ return { host, port };
46
50
  }
47
51
  createAdapter(configContext) {
48
52
  return this.createHttpAdapter(configContext.resolve(http_config_1.HttpConfig));
49
53
  }
50
- configureApp(app, configContext) {
54
+ modules() {
55
+ return [
56
+ {
57
+ module: no_interception_filter_1.RouteNotFoundFilter,
58
+ imports: [],
59
+ providers: [{ provide: core_1.APP_FILTER, useClass: no_interception_filter_1.RouteNotFoundFilter }],
60
+ },
61
+ ];
62
+ }
63
+ async configureApp(app, context, logger) {
64
+ const configContext = await context.getConfigContext();
51
65
  const { swaggerUI } = configContext.resolve(http_config_1.HttpConfig);
52
- const { globalPrefix, swagger, cors } = this.options ?? {};
66
+ const { globalPrefix, swagger, cors } = this.options;
53
67
  if (globalPrefix) {
54
68
  app.setGlobalPrefix(globalPrefix);
55
69
  }
56
70
  if (cors) {
57
71
  app.enableCors(cors);
58
72
  }
59
- if (swaggerUI || swagger?.file) {
60
- const builder = new swagger_1.DocumentBuilder()
61
- .setTitle(app.get(core_1.APP_NAME))
62
- .setVersion(app.get(core_1.APP_VERSION))
63
- .setDescription(app.get(core_1.APP_DESCRIPTION));
64
- // Applies additional configuration from user
65
- swagger?.configure?.(builder);
66
- const openApiConfig = builder.build();
67
- const spec = swagger_1.SwaggerModule.createDocument(app, openApiConfig);
68
- if (swagger?.file) {
69
- (0, node_fs_1.writeFileSync)(swagger.file, JSON.stringify(spec));
70
- }
71
- if (swaggerUI) {
72
- swagger_1.SwaggerModule.setup(swagger?.url ?? '/swagger', app, spec);
73
+ if (!swaggerUI || !swagger) {
74
+ return;
75
+ }
76
+ for (const [url, config] of Object.entries(swagger)) {
77
+ if (isFromFile(config)) {
78
+ try {
79
+ const document = await (0, common_node_1.readObj)(config.fromFile);
80
+ swagger_1.SwaggerModule.setup(url, app, document);
81
+ }
82
+ catch {
83
+ logger.warn(`Failed to load OpenAPI spec from ${config.fromFile}`);
84
+ }
85
+ continue;
73
86
  }
87
+ const document = await new http_openapi_1.OpenApiGenerator(context, {
88
+ title: app.get(core_2.APP_NAME),
89
+ version: app.get(core_2.APP_VERSION),
90
+ description: app.get(core_2.APP_DESCRIPTION),
91
+ ...config,
92
+ }).generate();
93
+ swagger_1.SwaggerModule.setup(url, app, document);
74
94
  }
75
95
  }
76
96
  }
@@ -1 +1 @@
1
- {"version":3,"file":"http.transport.js","sourceRoot":"","sources":["../src/http.transport.ts"],"names":[],"mappings":";;;AAIA,6CAGyB;AAYzB,qCAAoE;AACpE,qCAAwC;AAExC,+CAA4E;AAC5E,qDAI0B;AAC1B,+CAA2C;AAC3C,iDAA6C;AAiD7C,MAAsB,qBAAqB;IACV;IAA/B,YAA+B,OAA8B;QAA9B,YAAO,GAAP,OAAO,CAAuB;IAAG,CAAC;IAEjE,cAAc,CAAC,GAAqB;QAClC,OAAO,IAAI,0BAAW,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,cAAc,CAAC,GAAqB;QAClC,OAAO,GAAG,CAAC,OAAO,EAAE,KAAK,MAAM,CAAC;IAClC,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,oCAAmB,EAAE,CAAC;IACnC,CAAC;IAED,WAAW;QACT,OAAO,IAAI,iCAAgB,EAAE,CAAC;IAChC,CAAC;IAED,iBAAiB;QACf,OAAO,CAAC,IAAI,sCAAqB,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,WAAW;QACT,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,mBAAmB;QACjB,OAAO,kCAAoB,CAAC;IAC9B,CAAC;IAED,oBAAoB;QAClB,OAAO,mCAAqB,CAAC;IAC/B,CAAC;IAED,iBAAiB,CAAC,aAA4B;QAC5C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,wBAAU,CAAC,CAAC;QAEzD,OAAO;YACL,IAAI;YACJ,IAAI;SACL,CAAC;IACJ,CAAC;IAED,aAAa,CAAC,aAA4B;QACxC,OAAO,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC,OAAO,CAAC,wBAAU,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,YAAY,CACV,GAAqB,EACrB,aAA4B;QAE5B,MAAM,EAAE,SAAS,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,wBAAU,CAAC,CAAC;QACxD,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QAC3D,IAAI,YAAY,EAAE,CAAC;YACjB,GAAG,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,IAAI,EAAE,CAAC;YACT,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;QAED,IAAI,SAAS,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,IAAI,yBAAuB,EAAE;iBAC1C,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,eAAQ,CAAC,CAAC;iBAC3B,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,kBAAW,CAAC,CAAC;iBAChC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,sBAAe,CAAC,CAAC,CAAC;YAE5C,6CAA6C;YAC7C,OAAO,EAAE,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC;YAE9B,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,uBAAa,CAAC,cAAc,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;YAE9D,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC;gBAClB,IAAA,uBAAa,EAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;YACpD,CAAC;YAED,IAAI,SAAS,EAAE,CAAC;gBACd,uBAAa,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;CAKF;AAtFD,sDAsFC"}
1
+ {"version":3,"file":"http.transport.js","sourceRoot":"","sources":["../src/http.transport.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsdk/http.server",
3
- "version": "6.0.0-next.1",
3
+ "version": "6.0.0-next.11",
4
4
  "description": "HTTP transport for rsdk apps (needs some of HTTP adapters)",
5
5
  "license": "Apache License 2.0",
6
6
  "publishConfig": {
@@ -15,13 +15,16 @@
15
15
  "@nestjs/core": "^10.0.0",
16
16
  "@nestjs/swagger": "^7.0.0 || ^8.0.0",
17
17
  "@rsdk/common": "*",
18
+ "@rsdk/common.node": "*",
18
19
  "@rsdk/core": "*",
20
+ "@rsdk/http.openapi": "*",
19
21
  "@rsdk/logging": "*",
20
22
  "@rsdk/zones": "*",
21
23
  "body-parser": "^1.20.1",
22
24
  "cookie-parser": "^1.4.6",
25
+ "lodash": "^4.17.21",
23
26
  "reflect-metadata": "^0.1.12 || ^0.2.0",
24
27
  "rxjs": "^7.8.1"
25
28
  },
26
- "gitHead": "3114a76df17cf98d6f3cbb8a931599a883af6783"
29
+ "gitHead": "e4a3d962f23e2c4ea39ec08e07628290f54812a6"
27
30
  }
@@ -1,3 +1,3 @@
1
1
  export * from './http-errors.formatter';
2
2
  export * from './http-errors.sender';
3
- export * from './http-errors.transformer';
3
+ export * from './no-interception.filter';
@@ -0,0 +1,48 @@
1
+ import {
2
+ ArgumentsHost,
3
+ Catch,
4
+ ExceptionFilter,
5
+ NotFoundException as NestNotFoundException,
6
+ } from '@nestjs/common';
7
+ import { InjectLogger, NotFoundException } from '@rsdk/core';
8
+ import { ILogger } from '@rsdk/logging';
9
+ import { Observable } from 'rxjs';
10
+
11
+ import { HttpErrorsFormatter } from './http-errors.formatter';
12
+ import { HttpErrorsSender } from './http-errors.sender';
13
+
14
+ /**
15
+ * Специализированный фильтр для обработки ошибок NotFound.
16
+ * Имеет более высокий приоритет, чем GlobalExceptionsFilter,
17
+ * т. к. задуман для перехвата только NotFoundException. Её выбрасывает
18
+ * nest, когда не находит маршрут.
19
+ *
20
+ * К сожалению, эти ошибки не попадают в интерцепторы и, соответственно,
21
+ * приходится обрабатывать их вот так отдельно.
22
+ */
23
+ @Catch(NestNotFoundException)
24
+ export class RouteNotFoundFilter implements ExceptionFilter {
25
+ private readonly formatter = new HttpErrorsFormatter();
26
+ private readonly sender = new HttpErrorsSender();
27
+
28
+ constructor(
29
+ @InjectLogger(RouteNotFoundFilter) private readonly logger: ILogger,
30
+ ) {}
31
+
32
+ catch(
33
+ exception: NestNotFoundException,
34
+ host: ArgumentsHost,
35
+ ): Observable<any> {
36
+ const ctx = host.switchToHttp();
37
+ const { method, path } = ctx.getRequest();
38
+
39
+ const pipelineEx = new NotFoundException(exception.message);
40
+
41
+ // Логируем ошибку со всеми деталями
42
+ this.logger.error(`Route not found [HTTP] ${method} ${path}`);
43
+
44
+ const formattedEx = this.formatter.format(pipelineEx);
45
+
46
+ return this.sender.send(host, formattedEx);
47
+ }
48
+ }
@@ -1,14 +1,11 @@
1
- import { Size } from '@rsdk/common';
2
1
  import {
3
2
  BoolParser,
4
- Config,
5
- ConfigSection,
6
- ConfigTag,
7
3
  IntParser,
8
- Property,
4
+ Size,
9
5
  SizeParser,
10
6
  StringParser,
11
- } from '@rsdk/core';
7
+ } from '@rsdk/common';
8
+ import { Config, ConfigSection, ConfigTag, Property } from '@rsdk/core';
12
9
 
13
10
  @ConfigSection({
14
11
  tags: [ConfigTag.infrastructure, ConfigTag.transport, ConfigTag.http],
@@ -35,7 +32,7 @@ export class HttpConfig extends Config {
35
32
 
36
33
  @Property('HTTP_SWAGGER_UI_ENABLED', new BoolParser(), {
37
34
  defaultValue: true,
38
- description: 'Enable swagger UI on /swagger endpoint',
35
+ description: 'Enable swagger UI on /swagger (can be configured) endpoint',
39
36
  })
40
37
  readonly swaggerUI!: boolean;
41
38
  }
@@ -1,12 +1,11 @@
1
1
  import { type ExecutionContext } from '@nestjs/common';
2
2
  import type { Controller, INestApplication } from '@nestjs/common/interfaces';
3
3
  import type { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
4
- import type { AbstractHttpAdapter } from '@nestjs/core';
5
- import {
6
- DocumentBuilder as OriginalDocumentBuilder,
7
- SwaggerModule,
8
- } from '@nestjs/swagger';
4
+ import { type AbstractHttpAdapter, APP_FILTER } from '@nestjs/core';
5
+ import type { OpenAPIObject } from '@nestjs/swagger';
6
+ import { SwaggerModule } from '@nestjs/swagger';
9
7
  import type { Constructor, DeepPartial } from '@rsdk/common';
8
+ import { readObj } from '@rsdk/common.node';
10
9
  import type {
11
10
  ConfigContext,
12
11
  GenericHeaders,
@@ -16,16 +15,18 @@ import type {
16
15
  IErrorsTransformer,
17
16
  IHttpTransport,
18
17
  LogFormatter,
18
+ NestModuleDefinitions,
19
+ PlatformContext,
19
20
  } from '@rsdk/core';
20
21
  import { APP_DESCRIPTION, APP_NAME, APP_VERSION } from '@rsdk/core';
21
- import { writeFileSync } from 'node:fs';
22
+ import type { OpenApiOptions } from '@rsdk/http.openapi';
23
+ import { OpenApiGenerator } from '@rsdk/http.openapi';
24
+ import type { ILogger } from '@rsdk/logging';
25
+ import _ from 'lodash';
22
26
 
27
+ import { RouteNotFoundFilter } from './error-handling/no-interception.filter';
23
28
  import { HealthHttpController, MetricsHttpController } from './controllers';
24
- import {
25
- HttpErrorsFormatter,
26
- HttpErrorsSender,
27
- HttpErrorsTransformer,
28
- } from './error-handling';
29
+ import { HttpErrorsFormatter, HttpErrorsSender } from './error-handling';
29
30
  import { HttpConfig } from './http.config';
30
31
  import { HttpHeaders } from './http.headers';
31
32
 
@@ -37,16 +38,12 @@ export interface ParsersConfig {
37
38
  cookie: boolean;
38
39
  }
39
40
 
40
- export type DocumentBuilder = Omit<
41
- OriginalDocumentBuilder,
42
- 'build' | 'setTitle' | 'setDescription' | 'setVersion'
43
- >;
41
+ export type SwaggerOptions = Partial<OpenApiOptions> | { fromFile: string };
44
42
 
45
- export interface SwaggerOptions {
46
- url: string;
47
- file: string;
48
-
49
- configure(builder: DocumentBuilder): void;
43
+ export function isFromFile(
44
+ swagger: SwaggerOptions,
45
+ ): swagger is { fromFile: string } {
46
+ return typeof swagger === 'object' && 'fromFile' in swagger;
50
47
  }
51
48
 
52
49
  export interface HttpTransportOptions {
@@ -62,7 +59,6 @@ export interface HttpTransportOptions {
62
59
 
63
60
  /**
64
61
  * Configuration of body and cookie parsers
65
- // NOTE: This fields is actually is handled in transport specific way
66
62
  */
67
63
  parsers?: DeepPartial<ParsersConfig>;
68
64
 
@@ -73,11 +69,11 @@ export interface HttpTransportOptions {
73
69
  * NOTE: SwaggerUI is enabled or disabled by HTTP_SWAGGER_UI_ENABLED
74
70
  * (true by default).
75
71
  */
76
- swagger?: DeepPartial<SwaggerOptions>;
72
+ swagger?: Record<string, SwaggerOptions>;
77
73
  }
78
74
 
79
75
  export abstract class AbstractHttpTransport implements IHttpTransport {
80
- constructor(protected readonly options?: HttpTransportOptions) {}
76
+ constructor(protected readonly options: HttpTransportOptions = {}) {}
81
77
 
82
78
  extractHeaders(ctx: ExecutionContext): GenericHeaders {
83
79
  return new HttpHeaders(ctx);
@@ -96,7 +92,7 @@ export abstract class AbstractHttpTransport implements IHttpTransport {
96
92
  }
97
93
 
98
94
  errorTransformers(): IErrorsTransformer[] {
99
- return [new HttpErrorsTransformer()];
95
+ return [];
100
96
  }
101
97
 
102
98
  getProtocol(): string {
@@ -114,48 +110,65 @@ export abstract class AbstractHttpTransport implements IHttpTransport {
114
110
  createHttpOptions(configContext: ConfigContext): HttpOptions {
115
111
  const { host, port } = configContext.resolve(HttpConfig);
116
112
 
117
- return {
118
- host,
119
- port,
120
- };
113
+ return { host, port };
121
114
  }
122
115
 
123
116
  createAdapter(configContext: ConfigContext): AbstractHttpAdapter {
124
117
  return this.createHttpAdapter(configContext.resolve(HttpConfig));
125
118
  }
126
119
 
127
- configureApp(
120
+ modules(): NestModuleDefinitions {
121
+ return [
122
+ {
123
+ module: RouteNotFoundFilter,
124
+ imports: [],
125
+ providers: [{ provide: APP_FILTER, useClass: RouteNotFoundFilter }],
126
+ },
127
+ ];
128
+ }
129
+
130
+ async configureApp(
128
131
  app: INestApplication,
129
- configContext: ConfigContext,
130
- ): Promise<void> | void {
132
+ context: PlatformContext,
133
+ logger: ILogger,
134
+ ): Promise<void> {
135
+ const configContext = await context.getConfigContext();
131
136
  const { swaggerUI } = configContext.resolve(HttpConfig);
132
- const { globalPrefix, swagger, cors } = this.options ?? {};
137
+ const { globalPrefix, swagger, cors } = this.options;
138
+
133
139
  if (globalPrefix) {
134
140
  app.setGlobalPrefix(globalPrefix);
135
141
  }
142
+
136
143
  if (cors) {
137
144
  app.enableCors(cors);
138
145
  }
139
146
 
140
- if (swaggerUI || swagger?.file) {
141
- const builder = new OriginalDocumentBuilder()
142
- .setTitle(app.get(APP_NAME))
143
- .setVersion(app.get(APP_VERSION))
144
- .setDescription(app.get(APP_DESCRIPTION));
147
+ if (!swaggerUI || !swagger) {
148
+ return;
149
+ }
145
150
 
146
- // Applies additional configuration from user
147
- swagger?.configure?.(builder);
151
+ for (const [url, config] of Object.entries(swagger)) {
152
+ if (isFromFile(config)) {
153
+ try {
154
+ const document = await readObj<OpenAPIObject>(config.fromFile);
148
155
 
149
- const openApiConfig = builder.build();
150
- const spec = SwaggerModule.createDocument(app, openApiConfig);
156
+ SwaggerModule.setup(url, app, document);
157
+ } catch {
158
+ logger.warn(`Failed to load OpenAPI spec from ${config.fromFile}`);
159
+ }
151
160
 
152
- if (swagger?.file) {
153
- writeFileSync(swagger.file, JSON.stringify(spec));
161
+ continue;
154
162
  }
155
163
 
156
- if (swaggerUI) {
157
- SwaggerModule.setup(swagger?.url ?? '/swagger', app, spec);
158
- }
164
+ const document = await new OpenApiGenerator(context, {
165
+ title: app.get(APP_NAME),
166
+ version: app.get(APP_VERSION),
167
+ description: app.get(APP_DESCRIPTION),
168
+ ...config,
169
+ }).generate();
170
+
171
+ SwaggerModule.setup(url, app, document);
159
172
  }
160
173
  }
161
174
 
@@ -1,5 +0,0 @@
1
- import type { IErrorsTransformer, PipelineException } from '@rsdk/core';
2
- export declare class HttpErrorsTransformer implements IErrorsTransformer {
3
- match(ex: any): boolean;
4
- transform(ex: any): PipelineException;
5
- }
@@ -1,17 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HttpErrorsTransformer = void 0;
4
- const common_1 = require("@nestjs/common");
5
- const core_1 = require("@rsdk/core");
6
- class HttpErrorsTransformer {
7
- match(ex) {
8
- return ex instanceof common_1.NotFoundException;
9
- }
10
- transform(ex) {
11
- const err = new core_1.NotFoundException(ex.message);
12
- err.stack = ex.stack;
13
- return err;
14
- }
15
- }
16
- exports.HttpErrorsTransformer = HttpErrorsTransformer;
17
- //# sourceMappingURL=http-errors.transformer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-errors.transformer.js","sourceRoot":"","sources":["../../src/error-handling/http-errors.transformer.ts"],"names":[],"mappings":";;;AAAA,2CAA4E;AAE5E,qCAA+C;AAE/C,MAAa,qBAAqB;IAChC,KAAK,CAAC,EAAO;QACX,OAAO,EAAE,YAAY,0BAAqB,CAAC;IAC7C,CAAC;IAED,SAAS,CAAC,EAAO;QACf,MAAM,GAAG,GAAG,IAAI,wBAAiB,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QAE9C,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;QAErB,OAAO,GAAG,CAAC;IACb,CAAC;CACF;AAZD,sDAYC"}
@@ -1,17 +0,0 @@
1
- import { NotFoundException as NestNotFoundException } from '@nestjs/common';
2
- import type { IErrorsTransformer, PipelineException } from '@rsdk/core';
3
- import { NotFoundException } from '@rsdk/core';
4
-
5
- export class HttpErrorsTransformer implements IErrorsTransformer {
6
- match(ex: any): boolean {
7
- return ex instanceof NestNotFoundException;
8
- }
9
-
10
- transform(ex: any): PipelineException {
11
- const err = new NotFoundException(ex.message);
12
-
13
- err.stack = ex.stack;
14
-
15
- return err;
16
- }
17
- }