oav/lib/validators/resolveNestedDefinitions.ts

152 строки
5.6 KiB
TypeScript

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as uuid from "uuid";
import {
arrayMap,
getInfo,
getPath,
MutableStringMap,
PartialFactory,
propertySetMap,
stringMapMap,
stringMapMerge,
} from "@azure-tools/openapi-tools-common";
import { pathToPtr } from "json-refs";
import {
DefinitionsObject,
OperationObject,
ParameterObject,
PathItemObject,
ResponseObject,
ResponseSchemaObject,
SchemaObject,
SwaggerObject,
} from "yasway";
import { generatedPrefix } from "./cloudError";
import { getSchemaObjectInfo, setSchemaInfo, setSchemaTitle } from "./specTransformer";
const skipIfUndefined = <T>(f: (v: T) => T): ((v: T | undefined) => T | undefined) => (v) =>
v !== undefined ? f(v) : undefined;
export const resolveNestedDefinitions = (spec: SwaggerObject): SwaggerObject => {
const generatedDefinitions: MutableStringMap<SchemaObject> = {};
// a function to resolve nested schema objects
const resolveNestedSchemaObject = (schemaObject: SchemaObject) => {
// ignore references
if (schemaObject.$ref !== undefined) {
setSchemaTitle(schemaObject);
return schemaObject;
}
// ignore primitive types
switch (schemaObject.type) {
case "integer":
case "number":
case "string":
case "boolean":
case "null":
setSchemaTitle(schemaObject);
return schemaObject;
}
// here schemaObject.type is one of {undefined, "object", "array"}.
// Because it's a nested schema object, we create an extra definition and return a reference.
const result = resolveSchemaObject(schemaObject);
const info = getInfo(result);
const suffix = info === undefined ? uuid.v4() : getPath(info).join(".");
const definitionName = `${generatedPrefix}nested.${suffix}`;
if (result !== undefined) {
generatedDefinitions[definitionName] = result;
}
const refResult = { $ref: pathToPtr(["definitions", definitionName]) };
setSchemaInfo(refResult, getSchemaObjectInfo(schemaObject));
return refResult;
};
// a function to resolve SchemaObject array
const resolveOptionalSchemaObjectArray = (
schemaObjectArray: readonly SchemaObject[] | undefined
): readonly SchemaObject[] | undefined =>
schemaObjectArray !== undefined
? arrayMap(schemaObjectArray, resolveNestedSchemaObject)
: undefined;
// a function to resolve SchemaObject (top-level and nested)
const resolveSchemaObject = (schemaObject: SchemaObject): SchemaObject => {
const result = propertySetMap<SchemaObject>(schemaObject, {
properties: skipIfUndefined((properties) =>
stringMapMap(properties, resolveNestedSchemaObject)
),
additionalProperties: (additionalProperties) =>
additionalProperties === undefined || typeof additionalProperties !== "object"
? additionalProperties
: resolveNestedSchemaObject(additionalProperties),
items: skipIfUndefined(resolveNestedSchemaObject),
allOf: resolveOptionalSchemaObjectArray,
anyOf: resolveOptionalSchemaObjectArray,
oneOf: resolveOptionalSchemaObjectArray,
});
setSchemaTitle(result);
return result;
};
const resolveParameterObject = (parameterObject: ParameterObject) =>
propertySetMap(parameterObject, {
schema: skipIfUndefined(resolveSchemaObject),
});
const resolveResponseObject = (responseObject: ResponseObject) =>
propertySetMap(responseObject, {
schema: (schema: ResponseSchemaObject | undefined) =>
schema === undefined || schema.type === "file" ? schema : resolveSchemaObject(schema),
});
const resolveOptionalParameterArray = (parameters: readonly ParameterObject[] | undefined) =>
parameters !== undefined ? arrayMap(parameters, resolveParameterObject) : undefined;
const resolveOptionalOperationObject = (operationObject: OperationObject | undefined) =>
operationObject !== undefined
? propertySetMap<OperationObject>(operationObject, {
parameters: resolveOptionalParameterArray,
responses: (responses) => stringMapMap(responses, resolveResponseObject),
})
: undefined;
const resolveDefinitions = (definitions: DefinitionsObject | undefined) =>
stringMapMap(definitions, resolveSchemaObject);
// transformations for Open API 2.0
const swaggerObjectTransformation: PartialFactory<SwaggerObject> = {
definitions: resolveDefinitions,
parameters: (parameters) => stringMapMap(parameters, resolveParameterObject),
responses: (responses) => stringMapMap(responses, resolveResponseObject),
paths: (paths) =>
stringMapMap(paths, (path) =>
propertySetMap<PathItemObject>(path, {
get: resolveOptionalOperationObject,
put: resolveOptionalOperationObject,
post: resolveOptionalOperationObject,
delete: resolveOptionalOperationObject,
options: resolveOptionalOperationObject,
head: resolveOptionalOperationObject,
patch: resolveOptionalOperationObject,
parameters: resolveOptionalParameterArray,
})
),
};
// create extra definitions and the temporary spec
const specWithNoGeneratedDefinitions = propertySetMap(spec, swaggerObjectTransformation);
const addGeneratedDefinitions = (definitions: DefinitionsObject | undefined) =>
stringMapMerge(definitions, generatedDefinitions);
// Merge definitions and generatedDefinitions.
// It should be the last step when all generated definitions are known
return propertySetMap(specWithNoGeneratedDefinitions, {
definitions: addGeneratedDefinitions,
});
};