630 lines
27 KiB
JavaScript
630 lines
27 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.SpecGenerator3 = void 0;
|
|
const runtime_1 = require("@tsoa/runtime");
|
|
const merge_anything_1 = require("merge-anything");
|
|
const ts_deepmerge_1 = require("ts-deepmerge");
|
|
const isVoidType_1 = require("../utils/isVoidType");
|
|
const validatorUtils_1 = require("../utils/validatorUtils");
|
|
const pathUtils_1 = require("./../utils/pathUtils");
|
|
const swaggerUtils_1 = require("./../utils/swaggerUtils");
|
|
const specGenerator_1 = require("./specGenerator");
|
|
/**
|
|
* TODO:
|
|
* Handle formData parameters
|
|
* Handle requestBodies of type other than json
|
|
* Handle requestBodies as reusable objects
|
|
* Handle headers, examples, responses, etc.
|
|
* Cleaner interface between SpecGenerator2 and SpecGenerator3
|
|
* Also accept OpenAPI 3.0.0 metadata, like components/securitySchemes instead of securityDefinitions
|
|
*/
|
|
class SpecGenerator3 extends specGenerator_1.SpecGenerator {
|
|
constructor(metadata, config) {
|
|
super(metadata, config);
|
|
this.metadata = metadata;
|
|
this.config = config;
|
|
}
|
|
GetSpec() {
|
|
let spec = {
|
|
openapi: '3.0.0',
|
|
components: this.buildComponents(),
|
|
info: this.buildInfo(),
|
|
paths: this.buildPaths(),
|
|
servers: this.buildServers(),
|
|
tags: this.config.tags,
|
|
};
|
|
if (this.config.spec) {
|
|
this.config.specMerging = this.config.specMerging || 'immediate';
|
|
const mergeFuncs = {
|
|
immediate: Object.assign,
|
|
recursive: merge_anything_1.merge,
|
|
deepmerge: (spec, merge) => (0, ts_deepmerge_1.merge)(spec, merge),
|
|
};
|
|
spec = mergeFuncs[this.config.specMerging](spec, this.config.spec);
|
|
}
|
|
return spec;
|
|
}
|
|
buildInfo() {
|
|
const info = {
|
|
title: this.config.name || '',
|
|
};
|
|
if (this.config.version) {
|
|
info.version = this.config.version;
|
|
}
|
|
if (this.config.description) {
|
|
info.description = this.config.description;
|
|
}
|
|
if (this.config.termsOfService) {
|
|
info.termsOfService = this.config.termsOfService;
|
|
}
|
|
if (this.config.license) {
|
|
info.license = { name: this.config.license };
|
|
}
|
|
if (this.config.contact) {
|
|
info.contact = this.config.contact;
|
|
}
|
|
return info;
|
|
}
|
|
buildComponents() {
|
|
const components = {
|
|
examples: {},
|
|
headers: {},
|
|
parameters: {},
|
|
requestBodies: {},
|
|
responses: {},
|
|
schemas: this.buildSchema(),
|
|
securitySchemes: {},
|
|
};
|
|
if (this.config.securityDefinitions) {
|
|
components.securitySchemes = this.translateSecurityDefinitions(this.config.securityDefinitions);
|
|
}
|
|
return components;
|
|
}
|
|
translateSecurityDefinitions(definitions) {
|
|
const defs = {};
|
|
Object.keys(definitions).forEach(key => {
|
|
if (definitions[key].type === 'basic') {
|
|
defs[key] = {
|
|
scheme: 'basic',
|
|
type: 'http',
|
|
};
|
|
}
|
|
else if (definitions[key].type === 'oauth2') {
|
|
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
|
|
const definition = definitions[key];
|
|
const oauth = (defs[key] || {
|
|
type: 'oauth2',
|
|
description: definitions[key].description,
|
|
flows: (this.hasOAuthFlows(definition) && definition.flows) || {},
|
|
});
|
|
if (this.hasOAuthFlow(definition) && definition.flow === 'password') {
|
|
oauth.flows.password = { tokenUrl: definition.tokenUrl, scopes: definition.scopes || {} };
|
|
}
|
|
else if (this.hasOAuthFlow(definition) && definition.flow === 'accessCode') {
|
|
oauth.flows.authorizationCode = { tokenUrl: definition.tokenUrl, authorizationUrl: definition.authorizationUrl, scopes: definition.scopes || {} };
|
|
}
|
|
else if (this.hasOAuthFlow(definition) && definition.flow === 'application') {
|
|
oauth.flows.clientCredentials = { tokenUrl: definition.tokenUrl, scopes: definition.scopes || {} };
|
|
}
|
|
else if (this.hasOAuthFlow(definition) && definition.flow === 'implicit') {
|
|
oauth.flows.implicit = { authorizationUrl: definition.authorizationUrl, scopes: definition.scopes || {} };
|
|
}
|
|
defs[key] = oauth;
|
|
}
|
|
else {
|
|
defs[key] = definitions[key];
|
|
}
|
|
});
|
|
return defs;
|
|
}
|
|
hasOAuthFlow(definition) {
|
|
return !!definition.flow;
|
|
}
|
|
hasOAuthFlows(definition) {
|
|
return !!definition.flows;
|
|
}
|
|
buildServers() {
|
|
const basePath = (0, pathUtils_1.normalisePath)(this.config.basePath, '/', undefined, false);
|
|
const scheme = this.config.schemes ? this.config.schemes[0] : 'https';
|
|
const hosts = this.config.servers ? this.config.servers : this.config.host ? [this.config.host] : undefined;
|
|
const convertHost = (host) => ({ url: `${scheme}://${host}${basePath}` });
|
|
return (hosts?.map(convertHost) || [{ url: basePath }]);
|
|
}
|
|
buildSchema() {
|
|
const schema = {};
|
|
Object.keys(this.metadata.referenceTypeMap).map(typeName => {
|
|
const referenceType = this.metadata.referenceTypeMap[typeName];
|
|
if (referenceType.dataType === 'refObject') {
|
|
const required = referenceType.properties.filter(p => this.isRequiredWithoutDefault(p) && !this.hasUndefined(p)).map(p => p.name);
|
|
schema[referenceType.refName] = {
|
|
description: referenceType.description,
|
|
properties: this.buildProperties(referenceType.properties),
|
|
required: required && required.length > 0 ? Array.from(new Set(required)) : undefined,
|
|
type: 'object',
|
|
};
|
|
if (referenceType.additionalProperties) {
|
|
schema[referenceType.refName].additionalProperties = this.buildAdditionalProperties(referenceType.additionalProperties);
|
|
}
|
|
else {
|
|
// Since additionalProperties was not explicitly set in the TypeScript interface for this model
|
|
// ...we need to make a decision
|
|
schema[referenceType.refName].additionalProperties = this.determineImplicitAdditionalPropertiesValue();
|
|
}
|
|
if (referenceType.example) {
|
|
schema[referenceType.refName].example = referenceType.example;
|
|
}
|
|
}
|
|
else if (referenceType.dataType === 'refEnum') {
|
|
const enumTypes = this.determineTypesUsedInEnum(referenceType.enums);
|
|
if (enumTypes.size === 1) {
|
|
schema[referenceType.refName] = {
|
|
description: referenceType.description,
|
|
enum: referenceType.enums,
|
|
type: enumTypes.has('string') ? 'string' : 'number',
|
|
};
|
|
if (this.config.xEnumVarnames && referenceType.enumVarnames !== undefined && referenceType.enums.length === referenceType.enumVarnames.length) {
|
|
schema[referenceType.refName]['x-enum-varnames'] = referenceType.enumVarnames;
|
|
}
|
|
}
|
|
else {
|
|
schema[referenceType.refName] = {
|
|
description: referenceType.description,
|
|
anyOf: [
|
|
{
|
|
type: 'number',
|
|
enum: referenceType.enums.filter(e => typeof e === 'number'),
|
|
},
|
|
{
|
|
type: 'string',
|
|
enum: referenceType.enums.filter(e => typeof e === 'string'),
|
|
},
|
|
],
|
|
};
|
|
}
|
|
if (referenceType.example) {
|
|
schema[referenceType.refName].example = referenceType.example;
|
|
}
|
|
}
|
|
else if (referenceType.dataType === 'refAlias') {
|
|
const swaggerType = this.getSwaggerType(referenceType.type);
|
|
const format = referenceType.format;
|
|
const validators = Object.keys(referenceType.validators)
|
|
.filter(validatorUtils_1.shouldIncludeValidatorInSchema)
|
|
.reduce((acc, key) => {
|
|
return {
|
|
...acc,
|
|
[key]: referenceType.validators[key].value,
|
|
};
|
|
}, {});
|
|
schema[referenceType.refName] = {
|
|
...swaggerType,
|
|
default: referenceType.default || swaggerType.default,
|
|
example: referenceType.example,
|
|
format: format || swaggerType.format,
|
|
description: referenceType.description,
|
|
...validators,
|
|
};
|
|
}
|
|
else {
|
|
(0, runtime_1.assertNever)(referenceType);
|
|
}
|
|
if (referenceType.deprecated) {
|
|
schema[referenceType.refName].deprecated = true;
|
|
}
|
|
});
|
|
return schema;
|
|
}
|
|
buildPaths() {
|
|
const paths = {};
|
|
this.metadata.controllers.forEach(controller => {
|
|
const normalisedControllerPath = (0, pathUtils_1.normalisePath)(controller.path, '/');
|
|
// construct documentation using all methods except @Hidden
|
|
controller.methods
|
|
.filter(method => !method.isHidden)
|
|
.forEach(method => {
|
|
const normalisedMethodPath = (0, pathUtils_1.normalisePath)(method.path, '/');
|
|
let path = (0, pathUtils_1.normalisePath)(`${normalisedControllerPath}${normalisedMethodPath}`, '/', '', false);
|
|
path = (0, pathUtils_1.convertColonPathParams)(path);
|
|
paths[path] = paths[path] || {};
|
|
this.buildMethod(controller.name, method, paths[path], controller.produces);
|
|
});
|
|
});
|
|
return paths;
|
|
}
|
|
buildMethod(controllerName, method, pathObject, defaultProduces) {
|
|
const pathMethod = (pathObject[method.method] = this.buildOperation(controllerName, method, defaultProduces));
|
|
pathMethod.description = method.description;
|
|
pathMethod.summary = method.summary;
|
|
pathMethod.tags = method.tags;
|
|
// Use operationId tag otherwise fallback to generated. Warning: This doesn't check uniqueness.
|
|
pathMethod.operationId = method.operationId || pathMethod.operationId;
|
|
if (method.deprecated) {
|
|
pathMethod.deprecated = method.deprecated;
|
|
}
|
|
if (method.security) {
|
|
pathMethod.security = method.security;
|
|
}
|
|
const bodyParams = method.parameters.filter(p => p.in === 'body');
|
|
const bodyPropParams = method.parameters.filter(p => p.in === 'body-prop');
|
|
const formParams = method.parameters.filter(p => p.in === 'formData');
|
|
const queriesParams = method.parameters.filter(p => p.in === 'queries');
|
|
pathMethod.parameters = method.parameters
|
|
.filter(p => {
|
|
return ['body', 'formData', 'request', 'body-prop', 'res', 'queries', 'request-prop'].indexOf(p.in) === -1;
|
|
})
|
|
.map(p => this.buildParameter(p));
|
|
if (queriesParams.length > 1) {
|
|
throw new Error('Only one queries parameter allowed per controller method.');
|
|
}
|
|
if (queriesParams.length === 1) {
|
|
pathMethod.parameters.push(...this.buildQueriesParameter(queriesParams[0]));
|
|
}
|
|
if (bodyParams.length > 1) {
|
|
throw new Error('Only one body parameter allowed per controller method.');
|
|
}
|
|
if (bodyParams.length > 0 && formParams.length > 0) {
|
|
throw new Error('Either body parameter or form parameters allowed per controller method - not both.');
|
|
}
|
|
if (bodyPropParams.length > 0) {
|
|
if (!bodyParams.length) {
|
|
bodyParams.push({
|
|
in: 'body',
|
|
name: 'body',
|
|
parameterName: 'body',
|
|
required: true,
|
|
type: {
|
|
dataType: 'nestedObjectLiteral',
|
|
properties: [],
|
|
},
|
|
validators: {},
|
|
deprecated: false,
|
|
});
|
|
}
|
|
const type = bodyParams[0].type;
|
|
bodyPropParams.forEach((bodyParam) => {
|
|
type.properties.push(bodyParam);
|
|
});
|
|
}
|
|
if (bodyParams.length > 0) {
|
|
pathMethod.requestBody = this.buildRequestBody(controllerName, method, bodyParams[0]);
|
|
}
|
|
else if (formParams.length > 0) {
|
|
pathMethod.requestBody = this.buildRequestBodyWithFormData(controllerName, method, formParams);
|
|
}
|
|
method.extensions.forEach(ext => (pathMethod[ext.key] = ext.value));
|
|
}
|
|
buildOperation(controllerName, method, defaultProduces) {
|
|
const swaggerResponses = {};
|
|
method.responses.forEach((res) => {
|
|
swaggerResponses[res.name] = {
|
|
description: res.description,
|
|
};
|
|
if (res.schema && !(0, isVoidType_1.isVoidType)(res.schema)) {
|
|
swaggerResponses[res.name].content = {};
|
|
const produces = res.produces || defaultProduces || [swaggerUtils_1.DEFAULT_RESPONSE_MEDIA_TYPE];
|
|
for (const p of produces) {
|
|
const { content } = swaggerResponses[res.name];
|
|
swaggerResponses[res.name].content = {
|
|
...content,
|
|
[p]: {
|
|
schema: this.getSwaggerType(res.schema, this.config.useTitleTagsForInlineObjects ? this.getOperationId(controllerName, method) + 'Response' : undefined),
|
|
},
|
|
};
|
|
}
|
|
if (res.examples) {
|
|
let exampleCounter = 1;
|
|
const examples = res.examples.reduce((acc, ex, currentIndex) => {
|
|
const exampleLabel = res.exampleLabels?.[currentIndex];
|
|
return { ...acc, [exampleLabel === undefined ? `Example ${exampleCounter++}` : exampleLabel]: { value: ex } };
|
|
}, {});
|
|
for (const p of produces) {
|
|
/* eslint-disable @typescript-eslint/dot-notation */
|
|
(swaggerResponses[res.name].content || {})[p]['examples'] = examples;
|
|
}
|
|
}
|
|
}
|
|
if (res.headers) {
|
|
const headers = {};
|
|
if (res.headers.dataType === 'refObject') {
|
|
headers[res.headers.refName] = {
|
|
schema: this.getSwaggerTypeForReferenceType(res.headers),
|
|
description: res.headers.description,
|
|
};
|
|
}
|
|
else if (res.headers.dataType === 'nestedObjectLiteral') {
|
|
res.headers.properties.forEach((each) => {
|
|
headers[each.name] = {
|
|
schema: this.getSwaggerType(each.type),
|
|
description: each.description,
|
|
required: this.isRequiredWithoutDefault(each),
|
|
};
|
|
});
|
|
}
|
|
else {
|
|
(0, runtime_1.assertNever)(res.headers);
|
|
}
|
|
swaggerResponses[res.name].headers = headers;
|
|
}
|
|
});
|
|
const operation = {
|
|
operationId: this.getOperationId(controllerName, method),
|
|
responses: swaggerResponses,
|
|
};
|
|
return operation;
|
|
}
|
|
buildRequestBodyWithFormData(controllerName, method, parameters) {
|
|
const required = [];
|
|
const properties = {};
|
|
for (const parameter of parameters) {
|
|
const mediaType = this.buildMediaType(controllerName, method, parameter);
|
|
properties[parameter.name] = mediaType.schema;
|
|
if (this.isRequiredWithoutDefault(parameter)) {
|
|
required.push(parameter.name);
|
|
}
|
|
if (parameter.deprecated) {
|
|
properties[parameter.name].deprecated = parameter.deprecated;
|
|
}
|
|
}
|
|
const requestBody = {
|
|
required: required.length > 0,
|
|
content: {
|
|
'multipart/form-data': {
|
|
schema: {
|
|
type: 'object',
|
|
properties,
|
|
// An empty list required: [] is not valid.
|
|
// If all properties are optional, do not specify the required keyword.
|
|
...(required && required.length && { required }),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
return requestBody;
|
|
}
|
|
buildRequestBody(controllerName, method, parameter) {
|
|
const mediaType = this.buildMediaType(controllerName, method, parameter);
|
|
const consumes = method.consumes || swaggerUtils_1.DEFAULT_REQUEST_MEDIA_TYPE;
|
|
const requestBody = {
|
|
description: parameter.description,
|
|
required: this.isRequiredWithoutDefault(parameter),
|
|
content: {
|
|
[consumes]: mediaType,
|
|
},
|
|
};
|
|
return requestBody;
|
|
}
|
|
buildMediaType(controllerName, method, parameter) {
|
|
const validators = Object.keys(parameter.validators)
|
|
.filter(validatorUtils_1.shouldIncludeValidatorInSchema)
|
|
.reduce((acc, key) => {
|
|
return {
|
|
...acc,
|
|
[key]: parameter.validators[key].value,
|
|
};
|
|
}, {});
|
|
const mediaType = {
|
|
schema: {
|
|
...this.getSwaggerType(parameter.type, this.config.useTitleTagsForInlineObjects ? this.getOperationId(controllerName, method) + 'RequestBody' : undefined),
|
|
...validators,
|
|
...(parameter.description && { description: parameter.description }),
|
|
},
|
|
};
|
|
const parameterExamples = parameter.example;
|
|
const parameterExampleLabels = parameter.exampleLabels;
|
|
if (parameterExamples === undefined) {
|
|
mediaType.example = parameterExamples;
|
|
}
|
|
else if (parameterExamples.length === 1) {
|
|
mediaType.example = parameterExamples[0];
|
|
}
|
|
else {
|
|
let exampleCounter = 1;
|
|
mediaType.examples = parameterExamples.reduce((acc, ex, currentIndex) => {
|
|
const exampleLabel = parameterExampleLabels?.[currentIndex];
|
|
return { ...acc, [exampleLabel === undefined ? `Example ${exampleCounter++}` : exampleLabel]: { value: ex } };
|
|
}, {});
|
|
}
|
|
return mediaType;
|
|
}
|
|
buildQueriesParameter(source) {
|
|
if (source.type.dataType === 'refObject' || source.type.dataType === 'nestedObjectLiteral') {
|
|
const properties = source.type.properties;
|
|
return properties.map(property => this.buildParameter(this.queriesPropertyToQueryParameter(property)));
|
|
}
|
|
throw new Error(`Queries '${source.name}' parameter must be an object.`);
|
|
}
|
|
buildParameter(source) {
|
|
const parameter = {
|
|
description: source.description,
|
|
in: source.in,
|
|
name: source.name,
|
|
required: this.isRequiredWithoutDefault(source),
|
|
schema: {
|
|
default: source.default,
|
|
format: undefined,
|
|
},
|
|
};
|
|
if (source.deprecated) {
|
|
parameter.deprecated = true;
|
|
}
|
|
const parameterType = this.getSwaggerType(source.type);
|
|
if (parameterType.format) {
|
|
parameter.schema.format = this.throwIfNotDataFormat(parameterType.format);
|
|
}
|
|
if (parameterType.$ref) {
|
|
parameter.schema = parameterType;
|
|
return parameter;
|
|
}
|
|
const validatorObjs = {};
|
|
Object.keys(source.validators)
|
|
.filter(validatorUtils_1.shouldIncludeValidatorInSchema)
|
|
.forEach(key => {
|
|
validatorObjs[key] = source.validators[key].value;
|
|
});
|
|
if (source.type.dataType === 'any') {
|
|
parameter.schema.type = 'string';
|
|
}
|
|
else {
|
|
if (parameterType.type) {
|
|
parameter.schema.type = this.throwIfNotDataType(parameterType.type);
|
|
}
|
|
parameter.schema.items = parameterType.items;
|
|
parameter.schema.enum = parameterType.enum;
|
|
}
|
|
parameter.schema = Object.assign({}, parameter.schema, validatorObjs);
|
|
const parameterExamples = source.example;
|
|
const parameterExampleLabels = source.exampleLabels;
|
|
if (parameterExamples === undefined) {
|
|
parameter.example = parameterExamples;
|
|
}
|
|
else if (parameterExamples.length === 1) {
|
|
parameter.example = parameterExamples[0];
|
|
}
|
|
else {
|
|
let exampleCounter = 1;
|
|
parameter.examples = parameterExamples.reduce((acc, ex, currentIndex) => {
|
|
const exampleLabel = parameterExampleLabels?.[currentIndex];
|
|
return { ...acc, [exampleLabel === undefined ? `Example ${exampleCounter++}` : exampleLabel]: { value: ex } };
|
|
}, {});
|
|
}
|
|
return parameter;
|
|
}
|
|
buildProperties(source) {
|
|
const properties = {};
|
|
source.forEach(property => {
|
|
let swaggerType = this.getSwaggerType(property.type);
|
|
const format = property.format;
|
|
swaggerType.description = property.description;
|
|
swaggerType.example = property.example;
|
|
swaggerType.format = format || swaggerType.format;
|
|
if (!swaggerType.$ref) {
|
|
swaggerType.default = property.default;
|
|
Object.keys(property.validators)
|
|
.filter(validatorUtils_1.shouldIncludeValidatorInSchema)
|
|
.forEach(key => {
|
|
swaggerType = { ...swaggerType, [key]: property.validators[key].value };
|
|
});
|
|
}
|
|
if (property.deprecated) {
|
|
swaggerType.deprecated = true;
|
|
}
|
|
if (property.extensions) {
|
|
property.extensions.forEach(property => {
|
|
swaggerType[property.key] = property.value;
|
|
});
|
|
}
|
|
properties[property.name] = swaggerType;
|
|
});
|
|
return properties;
|
|
}
|
|
getSwaggerTypeForReferenceType(referenceType) {
|
|
return { $ref: `#/components/schemas/${encodeURIComponent(referenceType.refName)}` };
|
|
}
|
|
getSwaggerTypeForPrimitiveType(dataType) {
|
|
if (dataType === 'any') {
|
|
// Setting additionalProperties causes issues with code generators for OpenAPI 3
|
|
// Therefore, we avoid setting it explicitly (since it's the implicit default already)
|
|
return {};
|
|
}
|
|
else if (dataType === 'file') {
|
|
return { type: 'string', format: 'binary' };
|
|
}
|
|
return super.getSwaggerTypeForPrimitiveType(dataType);
|
|
}
|
|
isNull(type) {
|
|
return type.dataType === 'enum' && type.enums.length === 1 && type.enums[0] === null;
|
|
}
|
|
// Join disparate enums with the same type into one.
|
|
//
|
|
// grouping enums is helpful because it makes the spec more readable and it
|
|
// bypasses a failure in openapi-generator caused by using anyOf with
|
|
// duplicate types.
|
|
groupEnums(types) {
|
|
const returnTypes = [];
|
|
const enumValuesByType = {};
|
|
for (const type of types) {
|
|
if (type.enum && type.type) {
|
|
for (const enumValue of type.enum) {
|
|
if (!enumValuesByType[type.type]) {
|
|
enumValuesByType[type.type] = {};
|
|
}
|
|
enumValuesByType[type.type][String(enumValue)] = enumValue;
|
|
}
|
|
}
|
|
// preserve non-enum types
|
|
else {
|
|
returnTypes.push(type);
|
|
}
|
|
}
|
|
Object.keys(enumValuesByType).forEach(dataType => returnTypes.push({
|
|
type: dataType,
|
|
enum: Object.values(enumValuesByType[dataType]),
|
|
}));
|
|
return returnTypes;
|
|
}
|
|
removeDuplicateSwaggerTypes(types) {
|
|
if (types.length === 1) {
|
|
return types;
|
|
}
|
|
else {
|
|
const typesSet = new Set();
|
|
for (const type of types) {
|
|
typesSet.add(JSON.stringify(type));
|
|
}
|
|
return Array.from(typesSet).map(typeString => JSON.parse(typeString));
|
|
}
|
|
}
|
|
getSwaggerTypeForUnionType(type, title) {
|
|
// Filter out nulls and undefineds
|
|
const actualSwaggerTypes = this.removeDuplicateSwaggerTypes(this.groupEnums(type.types
|
|
.filter(x => !this.isNull(x))
|
|
.filter(x => x.dataType !== 'undefined')
|
|
.map(x => this.getSwaggerType(x))));
|
|
const nullable = type.types.some(x => this.isNull(x));
|
|
if (nullable) {
|
|
if (actualSwaggerTypes.length === 1) {
|
|
const [swaggerType] = actualSwaggerTypes;
|
|
// for ref union with null, use an allOf with a single
|
|
// element since you can't attach nullable directly to a ref.
|
|
// https://swagger.io/docs/specification/using-ref/#syntax
|
|
if (swaggerType.$ref) {
|
|
return { allOf: [swaggerType], nullable };
|
|
}
|
|
// Note that null must be explicitly included in the list of enum values. Using nullable: true alone is not enough here.
|
|
// https://swagger.io/docs/specification/data-models/enums/
|
|
if (swaggerType.enum) {
|
|
swaggerType.enum.push(null);
|
|
}
|
|
return { ...(title && { title }), ...swaggerType, nullable };
|
|
}
|
|
else {
|
|
return { ...(title && { title }), anyOf: actualSwaggerTypes, nullable };
|
|
}
|
|
}
|
|
else {
|
|
if (actualSwaggerTypes.length === 1) {
|
|
return { ...(title && { title }), ...actualSwaggerTypes[0] };
|
|
}
|
|
else {
|
|
return { ...(title && { title }), anyOf: actualSwaggerTypes };
|
|
}
|
|
}
|
|
}
|
|
getSwaggerTypeForIntersectionType(type, title) {
|
|
return { allOf: type.types.map(x => this.getSwaggerType(x)), ...(title && { title }) };
|
|
}
|
|
getSwaggerTypeForEnumType(enumType, title) {
|
|
const types = this.determineTypesUsedInEnum(enumType.enums);
|
|
if (types.size === 1) {
|
|
const type = types.values().next().value;
|
|
const nullable = enumType.enums.includes(null) ? true : false;
|
|
return { ...(title && { title }), type, enum: enumType.enums.map(member => (0, swaggerUtils_1.getValue)(type, member)), nullable };
|
|
}
|
|
else {
|
|
const valuesDelimited = Array.from(types).join(',');
|
|
throw new Error(`Enums can only have string or number values, but enum had ${valuesDelimited}`);
|
|
}
|
|
}
|
|
}
|
|
exports.SpecGenerator3 = SpecGenerator3;
|
|
//# sourceMappingURL=specGenerator3.js.map
|