lib/apiScenario/bodyTransformer.ts (109 lines of code) (raw):
import { inject, injectable } from "inversify";
import { cloneDeep } from "lodash";
import * as jp from "json-pointer";
import { TYPES } from "../inversifyUtils";
import { Schema } from "../swagger/swaggerTypes";
import { SchemaValidator } from "../swaggerValidator/schemaValidator";
import { jsonPathToPointer } from "../util/jsonUtils";
import { jsonPatchApply } from "./diffUtils";
@injectable()
export class BodyTransformer {
public constructor(@inject(TYPES.schemaValidator) private validator: SchemaValidator) {}
public async resourceToRequest(resource: any, responseSchema: Schema): Promise<any> {
const validateFn = await this.validator.compileAsync(responseSchema);
// Readonly field cannot be set in response, so we could filter readonly fields
const errors = validateFn(
{ isResponse: false, includeErrors: ["READONLY_PROPERTY_NOT_ALLOWED_IN_REQUEST"] },
resource
);
const result = cloneDeep(resource);
for (const err of errors) {
for (const jsonPath of err.jsonPathsInPayload) {
const jsonPointer = jsonPathToPointer(jsonPath);
jsonPatchApply(result, [{ remove: jsonPointer }]);
}
}
// console.log(body);
// console.log(errors);
// console.log(result);
return result;
}
public async resourceToResponse(resource: any, requestSchema: Schema): Promise<any> {
const validateFn = await this.validator.compileAsync(requestSchema);
// Writeonly field cannot be set in request, so we could filter writeonly fields
const errors = validateFn(
{
isResponse: false,
includeErrors: ["WRITEONLY_PROPERTY_NOT_ALLOWED_IN_RESPONSE", "SECRET_PROPERTY"],
},
resource
);
const result = cloneDeep(resource);
for (const err of errors) {
for (const jsonPath of err.jsonPathsInPayload) {
const jsonPointer = jsonPathToPointer(jsonPath);
jsonPatchApply(result, [{ remove: jsonPointer }]);
}
}
// console.log(body);
// console.log(errors);
// console.log(result);
return result;
}
public deepMerge(dst: any, src: any): { result: any; inconsistentWarningPaths: string[] } {
const inconsistentPaths: string[] = [];
const result = this.innerDeepMerge(src, dst, [], inconsistentPaths);
return {
result,
inconsistentWarningPaths: inconsistentPaths,
};
}
private innerDeepMerge(dst: any, src: any, path: string[], inconsistentPaths: string[]): any {
if (typeof src !== typeof dst) {
inconsistentPaths.push(jp.compile(path));
return src ?? dst;
}
if (Array.isArray(dst) || Array.isArray(src)) {
if (!Array.isArray(src) || !Array.isArray(dst)) {
inconsistentPaths.push(jp.compile(path));
return src;
}
let length = dst.length;
if (dst.length !== src.length) {
inconsistentPaths.push(jp.compile(path));
if (src.length < dst.length) {
length = src.length;
}
}
const result = new Array(length);
for (let idx = 0; idx < length; idx++) {
result[idx] = this.innerDeepMerge(
dst[idx],
src[idx],
path.concat(idx.toString()),
inconsistentPaths
);
}
return result;
}
if (typeof dst === "object" && typeof src === "object") {
const result: any = { ...dst };
if (dst !== null && src !== null) {
for (const key of Object.keys(dst)) {
if (key in src) {
result[key] = this.innerDeepMerge(
dst[key],
src[key],
path.concat([key]),
inconsistentPaths
);
}
}
}
if (src !== null && dst !== null) {
for (const key of Object.keys(src)) {
if (!(key in dst)) {
result[key] = src[key];
}
}
}
return result;
}
if (dst !== src) {
inconsistentPaths.push(jp.compile(path));
}
return src;
}
}