lib/generator/translator.ts (226 lines of code) (raw):
import { JsonLoader } from "../swagger/jsonLoader";
import { log } from "../util/logging";
import {
buildItemOption,
CacheItem,
createLeafItem,
createTrunkItem,
PayloadCache,
reBuildExample,
} from "./exampleCache";
import * as utils from "./util";
import { ExampleRule, getRuleValidator } from "./exampleRule";
import SwaggerMocker from "./swaggerMocker";
export default class Translator {
private jsonLoader: JsonLoader;
private payloadCache: PayloadCache;
private exampleRule?: ExampleRule;
private mocker: SwaggerMocker | undefined;
public constructor(jsonLoader: JsonLoader, payloadCache: PayloadCache, mocker?: SwaggerMocker) {
this.jsonLoader = jsonLoader;
this.payloadCache = payloadCache;
this.mocker = mocker;
}
public setRule(exampleRule?: ExampleRule) {
this.exampleRule = exampleRule;
}
public extractRequest(specItem: any, request: any) {
const path = request.url.split("?")[0];
const pathValues = this.getPathParameters(specItem.path, path);
const queryValues = this.getQueryParameters(specItem, request.query);
const parameters = this.getBodyParameters(specItem, request.body);
const result = {
...parameters,
...pathValues,
...queryValues,
};
this.maskSpecialValue(result);
return result;
}
// for example cache.
public extractParameters(specItem: any, request: any) {
const parameters = this.getMatchedParameters(specItem.content.parameters, request);
const result = {
...parameters,
};
this.maskSpecialValue(result);
return result;
}
private maskSpecialValue(requestExample: any) {
if (requestExample.subscriptionId) {
requestExample.subscriptionId = requestExample.subscriptionId.replace(/[a-z0-9]/g, "0");
}
}
private getMatchedParameters(paramterSchema: any, parameters: any) {
const bodyRes: any = {};
const validator = getRuleValidator(this.exampleRule).onParameter;
paramterSchema.forEach((item: any) => {
const itemSchema = this.getDefSpec(item);
if (parameters[itemSchema.name]) {
if (!validator || validator({ schema: itemSchema })) {
bodyRes[itemSchema.name] = this.filterBodyContent(
parameters[itemSchema.name],
itemSchema.in === "body" ? itemSchema.schema : item
);
}
}
});
return bodyRes;
}
private getQueryParameters(specItem: any, body: any): any {
if (!specItem || !body) {
return;
}
const parametersSpec = specItem.content.parameters
.map((item: any) => this.getDefSpec(item))
.filter((item: any) => "in" in item && item.in === "query");
if (parametersSpec.length === 0) {
log.info("no query parameter definition in spec file");
return;
}
return this.getMatchedParameters(parametersSpec, body);
}
// for payload cache
private getBodyParameters(specItem: any, body: any): any {
if (!specItem || !body) {
return;
}
const parametersSpec = specItem.content.parameters
.map((item: any) => this.getDefSpec(item))
.filter((item: any) => "in" in item && item.in === "body");
if (parametersSpec.length === 0) {
log.info("no body parameter definition in spec file");
return;
}
const bodyParameterName = parametersSpec[0].name;
const exampleBody: any = {};
exampleBody[bodyParameterName] = body;
return this.getMatchedParameters(parametersSpec, exampleBody);
}
/**
* filter body's fields which is not defined in spec file
* @param body
* @param schema
*/
public filterBodyContent(body: any, schema: any, isRequest: boolean = true) {
const cache = this.cacheBodyContent(body, schema, isRequest);
const validator = getRuleValidator(this.exampleRule).onSchema;
return reBuildExample(cache, isRequest, schema, validator);
}
public cacheBodyContent(body: any, schema: any, isRequest: boolean) {
if (!schema) {
return undefined;
}
if (schema.$ref && this.payloadCache.get(schema.$ref.split("#")[1])) {
return this.payloadCache.get(schema.$ref.split("#")[1]);
}
const definitionSpec = this.getDefSpec(schema);
const bodyContent: any = {};
let cacheItem: CacheItem | undefined;
if (utils.isObject(definitionSpec)) {
const properties = this.getProperties(definitionSpec);
if (body) {
Object.keys(body)
.filter((key) => key in properties)
.forEach((key: string) => {
bodyContent[key] = this.cacheBodyContent(body[key], properties[key], isRequest);
});
}
// to mock the properties that not exists in the body.
// it's not needed when generating from payload.
if (this.mocker !== undefined) {
Object.keys(properties)
.filter((key) => !body?.[key])
.forEach((key: string) => {
bodyContent[key] = this.mocker?.getMockCachedObj(key, properties[key], isRequest);
});
}
cacheItem = createTrunkItem(bodyContent, buildItemOption(definitionSpec));
} else if (definitionSpec.type === "array") {
if (body) {
const result = body.map((i: any) => {
return this.cacheBodyContent(i, schema.items, isRequest);
});
cacheItem = createTrunkItem(result, buildItemOption(definitionSpec));
} else if (this.mocker !== undefined) {
return this.mocker?.getMockCachedObj("mock array", schema, isRequest);
}
} else {
if (body !== undefined) {
cacheItem = createLeafItem(body, buildItemOption(definitionSpec));
} else if (this.mocker) {
return this.mocker?.getMockCachedObj("", schema, isRequest);
}
}
const requiredProperties = this.getRequiredProperties(definitionSpec);
if (requiredProperties && requiredProperties.length > 0 && cacheItem !== undefined) {
cacheItem.required = requiredProperties;
}
this.payloadCache.checkAndCache(schema, cacheItem, isRequest);
return cacheItem;
}
/**
* return all properties of the object, including parent's properties defined by 'allOf'
* It will not spread properties' properties.
* @param definitionSpec
*/
private getProperties(definitionSpec: any) {
let properties: any = {};
definitionSpec.allOf?.map((item: any) => {
properties = {
...properties,
...this.getProperties(this.getDefSpec(item)),
};
});
return {
...properties,
...definitionSpec.properties,
};
}
/**
* return all required properties of the object, including parent's properties defined by 'allOf'
* It will not spread properties' properties.
* @param definitionSpec
*/
private getRequiredProperties(definitionSpec: any) {
let requiredProperties: string[] = Array.isArray(definitionSpec.required)
? definitionSpec.required
: [];
definitionSpec.allOf?.map((item: any) => {
requiredProperties = [
...requiredProperties,
...this.getRequiredProperties(this.getDefSpec(item)),
];
});
return requiredProperties;
}
private getDefSpec(item: any) {
if (item) {
return this.jsonLoader.resolveRefObj(item);
}
}
public extractResponse(specItem: any, response: any, statusCode: string) {
const specItemContent = specItem.content;
const resp: any = {};
if (statusCode === "201" || statusCode === "202") {
resp.headers = {
Location:
response.headers && "location" in response.headers
? response.headers.location
: undefined,
"Azure-AsyncOperation":
response.headers && "azure-AsyncOperation" in response.headers
? response.headers["azure-AsyncOperation"]
: undefined,
};
}
if ("schema" in specItemContent.responses[statusCode] && response.body) {
resp.body =
this.filterBodyContent(
response.body,
specItemContent.responses[statusCode].schema,
false
) || {};
}
return resp;
}
private getPathParameters(pathName: string, path: string): object {
const pathTemplateItems = pathName.split("/");
const urlItems = path.split("/") || [];
let res = {};
pathTemplateItems.forEach((item, idx) => {
if (item !== urlItems[idx]) {
const matchParam = /^{(.*)}$/.exec(item);
if (matchParam && matchParam.length >= 2) {
const paramName: string = matchParam[1];
res = {
...res,
[paramName]: urlItems[idx],
};
}
}
});
return res;
}
}