@nestjs/platform-fastify 12.1.0 → 12.1.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.
@@ -122,8 +122,8 @@ export declare class FastifyAdapter<TServer extends RawServerBase = RawServerDef
122
122
  inject(opts: InjectOptions | string): Promise<LightMyRequestResponse>;
123
123
  close(): Promise<void>;
124
124
  initHttpServer(options?: NestApplicationOptions): void;
125
- useStaticAssets(options: FastifyStaticOptions): Promise<any>;
126
- setViewEngine(options: FastifyViewOptions | string): Promise<any>;
125
+ useStaticAssets(options: FastifyStaticOptions): any;
126
+ setViewEngine(options: FastifyViewOptions | string): any;
127
127
  isHeadersSent(response: TReply): boolean;
128
128
  getHeader(response: any, name: string): any;
129
129
  setHeader(response: TReply, name: string, value: string): FastifyReply<RouteGenericInterface, TServer, TRawRequest, TRawResponse, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
@@ -8,6 +8,7 @@ import middie from '@fastify/middie';
8
8
  import { loadPackage, tryLoadPackage, isNil, isString, isUndefined, } from '@nestjs/common/internal';
9
9
  import { AbstractHttpAdapter } from '@nestjs/core';
10
10
  import { LegacyRouteConverter } from '@nestjs/core/internal';
11
+ import { getMediaTypeVersion } from './utils/get-media-type-version.util.js';
11
12
  const { kRouteContext } = fastifySymbols;
12
13
  // Fastify uses `fast-querystring` internally to quickly parse URL query strings.
13
14
  import { parse as querystringParse } from 'fast-querystring';
@@ -22,6 +23,12 @@ function isFastifyMultipartPlugin(plugin) {
22
23
  (fn[Symbol.for('plugin-meta')]?.name === '@fastify/multipart' ||
23
24
  fn.name === 'fastifyMultipart'));
24
25
  }
26
+ // Covers "application/json" and the "+json" structured syntax suffix (RFC 6839),
27
+ // e.g. "application/problem+json", with or without parameters.
28
+ function isJsonContentType(contentType) {
29
+ const mediaType = contentType.split(';')[0].trim().toLowerCase();
30
+ return (mediaType.startsWith('application/json') || mediaType.endsWith('+json'));
31
+ }
25
32
  /**
26
33
  * @publicApi
27
34
  */
@@ -79,13 +86,12 @@ export class FastifyAdapter extends AbstractHttpAdapter {
79
86
  // Media Type (Accept Header) Versioning Handler
80
87
  if (this.versioningOptions?.type === VersioningType.MEDIA_TYPE) {
81
88
  const MEDIA_TYPE_HEADER = 'Accept';
82
- const acceptHeaderValue = (req.headers?.[MEDIA_TYPE_HEADER] || req.headers?.[MEDIA_TYPE_HEADER.toLowerCase()]);
83
- const acceptHeaderVersionParameter = acceptHeaderValue
84
- ? acceptHeaderValue.split(';')[1]
85
- : undefined;
86
- return isUndefined(acceptHeaderVersionParameter)
89
+ const acceptHeaderValue = req.headers?.[MEDIA_TYPE_HEADER] ||
90
+ req.headers?.[MEDIA_TYPE_HEADER.toLowerCase()];
91
+ const headerVersion = getMediaTypeVersion(acceptHeaderValue, this.versioningOptions.key);
92
+ return isUndefined(headerVersion)
87
93
  ? VERSION_NEUTRAL // No version was supplied
88
- : acceptHeaderVersionParameter.split(this.versioningOptions.key)[1];
94
+ : headerVersion;
89
95
  }
90
96
  // Header Versioning Handler
91
97
  else if (this.versioningOptions?.type === VersioningType.HEADER) {
@@ -280,8 +286,9 @@ export class FastifyAdapter extends AbstractHttpAdapter {
280
286
  }
281
287
  body = body.getStream();
282
288
  }
283
- if (fastifyReply.getHeader('Content-Type') !== undefined &&
284
- fastifyReply.getHeader('Content-Type') !== 'application/json' &&
289
+ const responseContentType = fastifyReply.getHeader('Content-Type');
290
+ if (typeof responseContentType === 'string' &&
291
+ !isJsonContentType(responseContentType) &&
285
292
  body?.statusCode >= HttpStatus.BAD_REQUEST) {
286
293
  Logger.warn("Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses", FastifyAdapter.name);
287
294
  fastifyReply.header('Content-Type', 'application/json');
@@ -392,15 +399,20 @@ export class FastifyAdapter extends AbstractHttpAdapter {
392
399
  this.trackOpenConnections();
393
400
  }
394
401
  }
395
- async useStaticAssets(options) {
396
- return this.register(await loadPackage('@fastify/static', 'FastifyAdapter.useStaticAssets()', () => import('@fastify/static')), options);
402
+ // `register()` accepts a promise of a plugin, so the import is handed over
403
+ // unawaited on purpose. `NestApplication.useStaticAssets()` discards what
404
+ // this returns, so awaiting here would enqueue the plugin after the caller
405
+ // has already moved on to `listen()`.
406
+ useStaticAssets(options) {
407
+ return this.register(loadPackage('@fastify/static', 'FastifyAdapter.useStaticAssets()', () => import('@fastify/static')), options);
397
408
  }
398
- async setViewEngine(options) {
409
+ // Handed over unawaited for the same reason as `useStaticAssets()` above.
410
+ setViewEngine(options) {
399
411
  if (isString(options)) {
400
412
  new Logger('FastifyAdapter').error("setViewEngine() doesn't support a string argument.");
401
413
  process.exit(1);
402
414
  }
403
- return this.register(await loadPackage('@fastify/view', 'FastifyAdapter.setViewEngine()', () => import('@fastify/view')), options);
415
+ return this.register(loadPackage('@fastify/view', 'FastifyAdapter.setViewEngine()', () => import('@fastify/view')), options);
404
416
  }
405
417
  isHeadersSent(response) {
406
418
  return response.sent;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Returns the version carried by an `Accept` header for media type versioning,
3
+ * e.g. `2` for `application/json;v=2` with the `v=` key.
4
+ *
5
+ * The version parameter may follow other parameters and may belong to any
6
+ * media range of a list (`text/html, application/json;v=2`); the first match
7
+ * wins. Returns `undefined` when no parameter starts with the key.
8
+ *
9
+ * Duplicated in each platform package so that a platform package never
10
+ * depends on a newer `@nestjs/core` than the one installed.
11
+ */
12
+ export declare function getMediaTypeVersion(acceptHeader: string | string[] | undefined, key: string): string | undefined;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Returns the version carried by an `Accept` header for media type versioning,
3
+ * e.g. `2` for `application/json;v=2` with the `v=` key.
4
+ *
5
+ * The version parameter may follow other parameters and may belong to any
6
+ * media range of a list (`text/html, application/json;v=2`); the first match
7
+ * wins. Returns `undefined` when no parameter starts with the key.
8
+ *
9
+ * Duplicated in each platform package so that a platform package never
10
+ * depends on a newer `@nestjs/core` than the one installed.
11
+ */
12
+ export function getMediaTypeVersion(acceptHeader, key) {
13
+ const mediaRanges = []
14
+ .concat(acceptHeader ?? [])
15
+ .flatMap(value => value.split(','));
16
+ for (const mediaRange of mediaRanges) {
17
+ const [, ...parameters] = mediaRange.split(';');
18
+ for (const parameter of parameters) {
19
+ const trimmedParameter = parameter.trim();
20
+ if (trimmedParameter.startsWith(key)) {
21
+ return trimmedParameter.slice(key.length);
22
+ }
23
+ }
24
+ }
25
+ return undefined;
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs/platform-fastify",
3
- "version": "12.1.0",
3
+ "version": "12.1.2",
4
4
  "description": "Nest - modern, fast, powerful node.js web framework (@platform-fastify)",
5
5
  "author": "Kamil Mysliwiec",
6
6
  "license": "MIT",
@@ -54,5 +54,5 @@
54
54
  "optional": true
55
55
  }
56
56
  },
57
- "gitHead": "6739656fa3e8c98bd55883f0363a2254e6412232"
57
+ "gitHead": "8ab60aa1b2927fbe0e23135ffc65d610c623236a"
58
58
  }