UNPKG

ng-openapi-gen

Version:

An OpenAPI 3.0 and 3.1 codegen for Angular 16+

293 lines 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Operation = void 0; const lodash_1 = require("lodash"); const content_1 = require("./content"); const gen_utils_1 = require("./gen-utils"); const logger_1 = require("./logger"); const openapi_typings_1 = require("./openapi-typings"); const operation_variant_1 = require("./operation-variant"); const parameter_1 = require("./parameter"); const request_body_1 = require("./request-body"); const response_1 = require("./response"); const security_1 = require("./security"); /** * An operation descriptor */ class Operation { constructor(openApi, path, pathSpec, method, id, spec, options) { this.openApi = openApi; this.path = path; this.pathSpec = pathSpec; this.method = method; this.id = id; this.spec = spec; this.options = options; this.parameters = []; this.parametersRequired = false; this.security = []; this.allResponses = []; this.variants = []; this.logger = new logger_1.Logger(options.silent); this.path = this.path.replace(/\'/g, '\\\''); this.tags = spec.tags || []; this.pathVar = `${(0, lodash_1.upperFirst)(id)}Path`; this.methodName = spec['x-operation-name'] || this.id; // Add both the common and specific parameters const allParams = [ ...this.collectParameters(false, pathSpec.parameters), ...this.collectParameters(true, spec.parameters), ]; // Maybe there were duplicated parameters? In this case, let specific parameters replace the general ones this.parameters = []; allParams.forEach(param => { let skip = false; if (!param.specific) { skip = !!allParams.find(p => p !== param && p.name === param.name && p.specific); } if (!skip) { this.parameters.push(param); } }); if (this.parameters.find(p => p.required)) { this.parametersRequired = true; } this.hasParameters = this.parameters.length > 0; this.security = spec.security ? this.collectSecurity(spec.security) : this.collectSecurity(openApi.security); let body = spec.requestBody; if (body) { if ((0, openapi_typings_1.isReferenceObject)(body)) { body = (0, gen_utils_1.resolveRef)(this.openApi, body.$ref); } body = body; this.requestBody = new request_body_1.RequestBody(body, this.collectContent(body.content), this.options); if (body.required) { this.parametersRequired = true; } } const responses = this.collectResponses(); this.successResponse = responses.success; this.allResponses = responses.all; this.pathExpression = this.toPathExpression(); this.deprecated = !!spec.deprecated; // Now calculate the variants: request body content x success response content this.calculateVariants(); } skipImport() { // All models are imported return false; } collectParameters(specific, params) { const result = []; if (params) { for (let param of params) { if ((0, openapi_typings_1.isReferenceObject)(param)) { param = (0, gen_utils_1.resolveRef)(this.openApi, param.$ref); } param = param; if (param.in === 'cookie') { this.logger.warn(`Ignoring cookie parameter ${this.id}.${param.name} as cookie parameters cannot be sent in XmlHttpRequests.`); } else if (!this.paramIsExcluded(param)) { const parameter = new parameter_1.Parameter(param, this.options, this.openApi); parameter.specific = specific; result.push(parameter); } } } return result; } collectSecurity(params) { if (!params) { return []; } return params.map((param) => { return Object.keys(param).map(key => { const scope = param[key]; const security = (0, gen_utils_1.resolveRef)(this.openApi, `#/components/securitySchemes/${key}`); return new security_1.Security(key, security, scope); }); }); } paramIsExcluded(param) { const excludedParameters = this.options.excludeParameters || []; return excludedParameters.includes(param.name); } collectContent(desc) { const result = []; if (desc) { for (const type of Object.keys(desc)) { result.push(new content_1.Content(type, desc[type], this.options, this.openApi)); } } return result; } collectResponses() { let successResponse = undefined; const allResponses = []; const responses = this.spec.responses || {}; const responseByType = new Map(); for (const statusCode of Object.keys(responses)) { const response = this.getResponse(responses[statusCode], statusCode); allResponses.push(response); const statusInt = Number.parseInt(statusCode.trim(), 10); if (statusInt >= 200 && statusInt < 300 && !responseByType.has('successResponse')) { responseByType.set('successResponse', response); } else if (statusCode === 'default') { responseByType.set('defaultResponse', response); } } successResponse = responseByType.get('successResponse') ?? responseByType.get('defaultResponse'); return { success: successResponse, all: allResponses }; } getResponse(responseObject, statusCode) { let responseDesc = undefined; if ((0, openapi_typings_1.isReferenceObject)(responseObject)) { responseDesc = (0, gen_utils_1.resolveRef)(this.openApi, responseObject.$ref); } else { responseDesc = responseObject; } const response = new response_1.Response(statusCode, responseDesc.description || '', this.collectContent(responseDesc.content), this.options); return response; } /** * Returns a path expression to be evaluated, for example: * "/a/{var1}/b/{var2}/" returns "/a/${params.var1}/b/${params.var2}" */ toPathExpression() { return (this.path || '').replace(/\{([^}]+)}/g, (_, pName) => { const param = this.parameters.find(p => p.name === pName); const paramName = param ? param.var : pName; return '${params.' + paramName + '}'; }); } contentsByMethodPart(hasContent) { const map = new Map(); if (hasContent) { const content = hasContent.content; if (content && content.length > 0) { for (const type of content) { if (type && type.mediaType) { const part = this.variantMethodPart(type); if (map.has(part)) { this.logger.warn(`Overwriting variant method part '${part}' for media type '${map.get(part)?.mediaType}' by media type '${type.mediaType}'.`); } map.set(part, type); } } } } if (map.size === 0) { map.set('', null); } else if (map.size === 1) { const content = [...map.values()][0]; map.clear(); map.set('', content); } return map; } calculateVariants() { // It is possible to have multiple content types which end up in the same method. // For example: application/json, application/foo-bar+json, text/json ... const requestVariants = this.contentsByMethodPart(this.requestBody); const responseVariants = this.contentsByMethodPart(this.successResponse); // Calculate total number of variants - if there's only one combination, no suffixes needed const totalVariants = Math.max(1, requestVariants.size) * Math.max(1, responseVariants.size); // Check if we have a potential ambiguity: both request and response have single content types // that would result in the same method suffix, causing duplicate method names const hasAmbiguity = totalVariants > 1 && requestVariants.size === 1 && responseVariants.size === 1 && [...requestVariants.keys()][0] === '' && [...responseVariants.keys()][0] === '' && [...requestVariants.values()][0] !== null && [...responseVariants.values()][0] !== null; if (hasAmbiguity) { // Additional check: are the media types actually the same? const requestMediaType = [...requestVariants.values()][0]?.mediaType; const responseMediaType = [...responseVariants.values()][0]?.mediaType; if (requestMediaType && responseMediaType) { // Calculate what the method parts would be for each const requestPart = this.variantMethodPart([...requestVariants.values()][0]); const responsePart = this.variantMethodPart([...responseVariants.values()][0]); // Only recalculate with preserved suffixes if the method parts would be the same if (requestPart === responsePart) { requestVariants.clear(); responseVariants.clear(); if (this.requestBody?.content && this.requestBody.content.length > 0) { for (const content of this.requestBody.content) { if (content && content.mediaType) { const part = this.variantMethodPart(content); requestVariants.set(part, content); } } } else { requestVariants.set('', null); } if (this.successResponse?.content && this.successResponse.content.length > 0) { for (const content of this.successResponse.content) { if (content && content.mediaType) { const part = this.variantMethodPart(content); responseVariants.set(part, content); } } } else { responseVariants.set('', null); } } } } requestVariants.forEach((requestContent, requestPart) => { responseVariants.forEach((responseContent, responsePart) => { const methodName = this.methodName + requestPart + responsePart; this.variants.push(new operation_variant_1.OperationVariant(this, methodName, requestContent, responseContent, this.options)); }); }); } /** * Returns how the given content is represented on the method name */ variantMethodPart(content) { if (content) { const keep = this.keepFullResponseMediaType(content.mediaType); let type = content.mediaType; type = content.mediaType.replace(/\/\*/, ''); if (type === '*' || type === 'application/octet-stream') { return '$Any'; } if (keep !== 'full') { type = (0, lodash_1.last)(type.split('/')); if (keep !== 'tail') { const plus = type.lastIndexOf('+'); if (plus >= 0) { type = type.substring(plus + 1); } } } return this.options.skipJsonSuffix && type === 'json' ? '' : `$${(0, gen_utils_1.typeName)(type)}`; } else { return ''; } } /** * Returns hint, how the expected response type in the request method names should be abbreviated. */ keepFullResponseMediaType(mediaType) { if (this.options.keepFullResponseMediaType === true) { return 'full'; } if (Array.isArray(this.options.keepFullResponseMediaType)) { for (const check of this.options.keepFullResponseMediaType) { if (check.mediaType === undefined || new RegExp(check.mediaType).test(mediaType)) { return check.use ?? 'short'; } } } return 'short'; } } exports.Operation = Operation; //# sourceMappingURL=operation.js.map