@tsed/platform-response-filter
Advanced tools
Comparing version 8.3.6 to 8.4.0
@@ -12,3 +12,2 @@ /** | ||
export * from "./services/PlatformResponseFilter.js"; | ||
export * from "./utils/renderView.js"; | ||
//# sourceMappingURL=index.js.map |
import { isSerializable } from "@tsed/core"; | ||
import { constant, inject, injectable } from "@tsed/di"; | ||
import { serialize } from "@tsed/json-mapper"; | ||
import { renderView } from "../utils/renderView.js"; | ||
import { TemplateRenderError } from "../errors/TemplateRenderError.js"; | ||
import { PLATFORM_CONTENT_TYPE_RESOLVER } from "./PlatformContentTypeResolver.js"; | ||
import { PLATFORM_CONTENT_TYPES_CONTAINER } from "./PlatformContentTypesContainer.js"; | ||
/** | ||
* PlatformResponseFilter is responsible for transforming the response data | ||
* to the appropriate format based on the endpoint metadata and context. | ||
* | ||
* @platform | ||
@@ -17,19 +20,35 @@ */ | ||
/** | ||
* Call filters to transform data | ||
* @param data | ||
* @param ctx | ||
* Transform the data to the right format. | ||
* @param data The data to transform. | ||
* @param $ctx The context. | ||
*/ | ||
transform(data, ctx) { | ||
const { response } = ctx; | ||
if (ctx.endpoint?.operation) { | ||
const bestContentType = this.contentTypeResolver(data, ctx); | ||
bestContentType && response.contentType(bestContentType); | ||
const resolved = this.container.resolve(bestContentType); | ||
if (resolved) { | ||
return resolved.transform(data, ctx); | ||
async transform(data, $ctx) { | ||
const { endpoint } = $ctx; | ||
if (endpoint) { | ||
if (endpoint.view) { | ||
data = await this.renderView(data, $ctx); | ||
} | ||
else if (isSerializable(data)) { | ||
data = await this.serialize(data, $ctx); | ||
} | ||
} | ||
return data; | ||
return this.resolve(data, $ctx); | ||
} | ||
/** | ||
* Render the view with the given data. | ||
* @param data The data to render. | ||
* @param $ctx The context. | ||
* @protected | ||
*/ | ||
async renderView(data, $ctx) { | ||
const { response, endpoint } = $ctx; | ||
try { | ||
const { path, options } = endpoint.view; | ||
return await response.render(path, { ...options, ...data, $ctx }); | ||
} | ||
catch (err) { | ||
throw new TemplateRenderError(endpoint.targetName, endpoint.propertyKey, err); | ||
} | ||
} | ||
/** | ||
* Serialize data before calling filters | ||
@@ -41,17 +60,22 @@ * @param data | ||
const { response, endpoint } = ctx; | ||
if (endpoint) { | ||
if (endpoint.view) { | ||
data = await renderView(data, ctx); | ||
const responseOpts = endpoint.getResponseOptions(response.statusCode, { | ||
includes: this.getIncludes(ctx) | ||
}); | ||
data = serialize(data, { | ||
useAlias: true, | ||
additionalProperties: this.additionalProperties, | ||
...responseOpts, | ||
endpoint: true | ||
}); | ||
return data; | ||
} | ||
resolve(data, ctx) { | ||
const { response } = ctx; | ||
if (ctx.endpoint?.operation) { | ||
const bestContentType = this.contentTypeResolver(data, ctx); | ||
bestContentType && response.contentType(bestContentType); | ||
const resolved = this.container.resolve(bestContentType); | ||
if (resolved) { | ||
return resolved.transform(data, ctx); | ||
} | ||
else if (isSerializable(data)) { | ||
const responseOpts = endpoint.getResponseOptions(response.statusCode, { | ||
includes: this.getIncludes(ctx) | ||
}); | ||
data = serialize(data, { | ||
useAlias: true, | ||
additionalProperties: this.additionalProperties, | ||
...responseOpts, | ||
endpoint: true | ||
}); | ||
} | ||
} | ||
@@ -58,0 +82,0 @@ return data; |
@@ -12,2 +12,1 @@ /** | ||
export * from "./services/PlatformResponseFilter.js"; | ||
export * from "./utils/renderView.js"; |
import { BaseContext } from "@tsed/di"; | ||
/** | ||
* PlatformResponseFilter is responsible for transforming the response data | ||
* to the appropriate format based on the endpoint metadata and context. | ||
* | ||
* @platform | ||
@@ -13,8 +16,15 @@ */ | ||
/** | ||
* Call filters to transform data | ||
* @param data | ||
* @param ctx | ||
* Transform the data to the right format. | ||
* @param data The data to transform. | ||
* @param $ctx The context. | ||
*/ | ||
transform(data: unknown, ctx: BaseContext): any; | ||
transform(data: unknown, $ctx: BaseContext): Promise<unknown>; | ||
/** | ||
* Render the view with the given data. | ||
* @param data The data to render. | ||
* @param $ctx The context. | ||
* @protected | ||
*/ | ||
protected renderView(data: unknown, $ctx: BaseContext): Promise<any>; | ||
/** | ||
* Serialize data before calling filters | ||
@@ -24,4 +34,5 @@ * @param data | ||
*/ | ||
serialize(data: unknown, ctx: BaseContext): Promise<unknown>; | ||
private getIncludes; | ||
protected serialize(data: unknown, ctx: BaseContext): Promise<unknown>; | ||
protected resolve(data: any, ctx: BaseContext): any; | ||
protected getIncludes(ctx: BaseContext): string[] | undefined; | ||
} |
@@ -5,3 +5,3 @@ { | ||
"type": "module", | ||
"version": "8.3.6", | ||
"version": "8.4.0", | ||
"source": "./src/index.ts", | ||
@@ -30,9 +30,9 @@ "main": "./lib/esm/index.js", | ||
"devDependencies": { | ||
"@tsed/barrels": "8.3.6", | ||
"@tsed/core": "8.3.6", | ||
"@tsed/di": "8.3.6", | ||
"@tsed/exceptions": "8.3.6", | ||
"@tsed/json-mapper": "8.3.6", | ||
"@tsed/schema": "8.3.6", | ||
"@tsed/typescript": "8.3.6", | ||
"@tsed/barrels": "8.4.0", | ||
"@tsed/core": "8.4.0", | ||
"@tsed/di": "8.4.0", | ||
"@tsed/exceptions": "8.4.0", | ||
"@tsed/json-mapper": "8.4.0", | ||
"@tsed/schema": "8.4.0", | ||
"@tsed/typescript": "8.4.0", | ||
"eslint": "9.12.0", | ||
@@ -43,7 +43,7 @@ "typescript": "5.4.5", | ||
"peerDependencies": { | ||
"@tsed/core": "8.3.6", | ||
"@tsed/di": "8.3.6", | ||
"@tsed/exceptions": "8.3.6", | ||
"@tsed/json-mapper": "8.3.6", | ||
"@tsed/schema": "8.3.6" | ||
"@tsed/core": "8.4.0", | ||
"@tsed/di": "8.4.0", | ||
"@tsed/exceptions": "8.4.0", | ||
"@tsed/json-mapper": "8.4.0", | ||
"@tsed/schema": "8.4.0" | ||
}, | ||
@@ -50,0 +50,0 @@ "peerDependenciesMeta": { |
@@ -12,2 +12,1 @@ /** | ||
export * from "./services/PlatformResponseFilter.js"; | ||
export * from "./utils/renderView.js"; |
import {catchAsyncError} from "@tsed/core"; | ||
import {PlatformTest} from "@tsed/platform-http/testing"; | ||
import {Context} from "@tsed/platform-params"; | ||
import {EndpointMetadata, Get, Returns, View} from "@tsed/schema"; | ||
import {EndpointMetadata, Get, Ignore, Property, Returns, View} from "@tsed/schema"; | ||
@@ -32,3 +32,3 @@ import {ResponseFilter} from "../decorators/responseFilter.js"; | ||
describe("PlatformResponseFilter", () => { | ||
describe("transform()", () => { | ||
describe("transform() with registered filters", () => { | ||
describe("when filter list is given", () => { | ||
@@ -168,3 +168,2 @@ beforeEach(() => | ||
}); | ||
describe("when filter list is not given", () => { | ||
@@ -233,8 +232,4 @@ beforeEach(() => | ||
}); | ||
describe("serialize()", () => { | ||
beforeEach(() => | ||
PlatformTest.create({ | ||
responseFilters: [CustomJsonFilter, AllFilter, ApplicationJsonFilter] | ||
}) | ||
); | ||
describe("transform() without registered filters", () => { | ||
beforeEach(() => PlatformTest.create()); | ||
afterEach(() => PlatformTest.reset()); | ||
@@ -245,3 +240,3 @@ it("should transform value", async () => { | ||
const result = await platformResponseFilter.serialize({test: "test"}, ctx); | ||
const result = await platformResponseFilter.transform({test: "test"}, ctx); | ||
@@ -262,3 +257,3 @@ expect(result).toEqual({test: "test"}); | ||
const result = await platformResponseFilter.serialize({test: "test"}, ctx); | ||
const result = await platformResponseFilter.transform({test: "test"}, ctx); | ||
@@ -282,3 +277,3 @@ expect(result).toEqual({test: "test"}); | ||
const result = await platformResponseFilter.serialize({test: "test"}, ctx); | ||
const result = await platformResponseFilter.transform({test: "test"}, ctx); | ||
@@ -302,3 +297,3 @@ expect(result).toEqual({test: "test"}); | ||
const result = await platformResponseFilter.serialize({test: "test"}, ctx); | ||
const result = await platformResponseFilter.transform({test: "test"}, ctx); | ||
@@ -322,3 +317,3 @@ expect(result).toEqual({test: "test"}); | ||
const result = await platformResponseFilter.serialize({test: "test"}, ctx); | ||
const result = await platformResponseFilter.transform({test: "test"}, ctx); | ||
@@ -339,7 +334,60 @@ expect(result).toEqual("template"); | ||
const result = await catchAsyncError(() => platformResponseFilter.serialize({test: "test"}, ctx)); | ||
const result = await catchAsyncError(() => platformResponseFilter.transform({test: "test"}, ctx)); | ||
expect(result?.message).toEqual("Template rendering error: Test.test()\nError: parsing error"); | ||
}); | ||
it("should render content", async () => { | ||
class Model { | ||
@Property() | ||
data: string; | ||
@Ignore() | ||
test: string; | ||
} | ||
class Test { | ||
@Get("/") | ||
@View("view", {options: "options"}) | ||
@Returns(200, Model) | ||
test() {} | ||
} | ||
const platformResponseFilter = PlatformTest.get<PlatformResponseFilter>(PlatformResponseFilter); | ||
const ctx = PlatformTest.createRequestContext(); | ||
ctx.endpoint = EndpointMetadata.get(Test, "test"); | ||
vi.spyOn(ctx.response, "render").mockResolvedValue("HTML"); | ||
ctx.data = {data: "data"}; | ||
await platformResponseFilter.transform(ctx.data, ctx); | ||
expect(ctx.response.render).toHaveBeenCalledWith("view", { | ||
$ctx: ctx, | ||
data: "data", | ||
options: "options" | ||
}); | ||
}); | ||
it("should render content and throw an error", async () => { | ||
class Test { | ||
@Get("/") | ||
@View("view", {options: "options"}) | ||
test() {} | ||
} | ||
const platformResponseFilter = PlatformTest.get<PlatformResponseFilter>(PlatformResponseFilter); | ||
const ctx = PlatformTest.createRequestContext(); | ||
ctx.endpoint = EndpointMetadata.get(Test, "test"); | ||
vi.spyOn(ctx.response, "render").mockRejectedValue(new Error("parser error")); | ||
ctx.data = {data: "data"}; | ||
let actualError: any = await catchAsyncError(() => platformResponseFilter.transform(ctx.data, ctx)); | ||
expect(actualError.message).toEqual("Template rendering error: Test.test()\nError: parser error"); | ||
}); | ||
}); | ||
}); |
@@ -5,3 +5,3 @@ import {isSerializable} from "@tsed/core"; | ||
import {renderView} from "../utils/renderView.js"; | ||
import {TemplateRenderError} from "../errors/TemplateRenderError.js"; | ||
import {PLATFORM_CONTENT_TYPE_RESOLVER} from "./PlatformContentTypeResolver.js"; | ||
@@ -11,2 +11,5 @@ import {PLATFORM_CONTENT_TYPES_CONTAINER} from "./PlatformContentTypesContainer.js"; | ||
/** | ||
* PlatformResponseFilter is responsible for transforming the response data | ||
* to the appropriate format based on the endpoint metadata and context. | ||
* | ||
* @platform | ||
@@ -20,22 +23,35 @@ */ | ||
/** | ||
* Call filters to transform data | ||
* @param data | ||
* @param ctx | ||
* Transform the data to the right format. | ||
* @param data The data to transform. | ||
* @param $ctx The context. | ||
*/ | ||
transform(data: unknown, ctx: BaseContext) { | ||
const {response} = ctx; | ||
async transform(data: unknown, $ctx: BaseContext): Promise<unknown> { | ||
const {endpoint} = $ctx; | ||
if (ctx.endpoint?.operation) { | ||
const bestContentType = this.contentTypeResolver(data, ctx); | ||
if (endpoint) { | ||
if (endpoint.view) { | ||
data = await this.renderView(data, $ctx); | ||
} else if (isSerializable(data)) { | ||
data = await this.serialize(data, $ctx); | ||
} | ||
} | ||
bestContentType && response.contentType(bestContentType); | ||
return this.resolve(data, $ctx); | ||
} | ||
const resolved = this.container.resolve(bestContentType); | ||
/** | ||
* Render the view with the given data. | ||
* @param data The data to render. | ||
* @param $ctx The context. | ||
* @protected | ||
*/ | ||
protected async renderView(data: unknown, $ctx: BaseContext) { | ||
const {response, endpoint} = $ctx; | ||
try { | ||
const {path, options} = endpoint.view; | ||
if (resolved) { | ||
return resolved.transform(data, ctx); | ||
} | ||
return await response.render(path, {...options, ...(data as object), $ctx}); | ||
} catch (err) { | ||
throw new TemplateRenderError(endpoint.targetName, endpoint.propertyKey, err); | ||
} | ||
return data; | ||
} | ||
@@ -48,19 +64,31 @@ | ||
*/ | ||
async serialize(data: unknown, ctx: BaseContext) { | ||
protected async serialize(data: unknown, ctx: BaseContext) { | ||
const {response, endpoint} = ctx; | ||
if (endpoint) { | ||
if (endpoint.view) { | ||
data = await renderView(data, ctx); | ||
} else if (isSerializable(data)) { | ||
const responseOpts = endpoint.getResponseOptions(response.statusCode, { | ||
includes: this.getIncludes(ctx) | ||
}); | ||
const responseOpts = endpoint.getResponseOptions(response.statusCode, { | ||
includes: this.getIncludes(ctx) | ||
}); | ||
data = serialize(data, { | ||
useAlias: true, | ||
additionalProperties: this.additionalProperties, | ||
...responseOpts, | ||
endpoint: true | ||
}); | ||
data = serialize(data, { | ||
useAlias: true, | ||
additionalProperties: this.additionalProperties, | ||
...responseOpts, | ||
endpoint: true | ||
}); | ||
return data; | ||
} | ||
protected resolve(data: any, ctx: BaseContext) { | ||
const {response} = ctx; | ||
if (ctx.endpoint?.operation) { | ||
const bestContentType = this.contentTypeResolver(data, ctx); | ||
bestContentType && response.contentType(bestContentType); | ||
const resolved = this.container.resolve(bestContentType); | ||
if (resolved) { | ||
return resolved.transform(data, ctx); | ||
} | ||
@@ -72,3 +100,3 @@ } | ||
private getIncludes(ctx: BaseContext) { | ||
protected getIncludes(ctx: BaseContext) { | ||
if (ctx.request.query.includes) { | ||
@@ -75,0 +103,0 @@ return [].concat(ctx.request.query.includes).flatMap((include: string) => include.split(",")); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
52630
961
42