UNPKG

@redocly/respect-core

Version:
195 lines 8.21 kB
import { getOperationFromDescriptionBySource, getRequestBodySchema, getRequestDataFromOpenApi, } from '../description-parser/index.js'; import { parseRequestBody, resolveReusableComponentItem, isParameterWithIn, handlePayloadReplacements, } from '../context-parser/index.js'; import { getServerUrl } from './get-server-url.js'; import { createRuntimeExpressionCtx, collectSecretValues } from './context/index.js'; import { evaluateRuntimeExpressionPayload } from '../runtime-expressions/index.js'; import { resolveXSecurityParameters } from './resolve-x-security-parameters.js'; export async function prepareRequest(ctx, step, workflowName) { const { stepId, operationId, operationPath, 'x-operation': xOperation } = step; const activeWorkflow = ctx.workflows.find((workflow) => workflow.workflowId === workflowName); const workflowLevelParameters = (activeWorkflow && activeWorkflow.parameters) || []; const openapiOperation = xOperation ? undefined : getOperationFromDescriptionBySource({ operationId, operationPath, }, ctx); let path = ''; let method; const serverUrl = getServerUrl({ ctx, descriptionName: openapiOperation?.descriptionName, openapiOperation, xOperation, }); if (xOperation) { method = xOperation.method; } else if (openapiOperation) { path = openapiOperation?.path; method = openapiOperation?.method; } else { // this should never happen, making typescript happy throw new Error('No operation found'); } if (!serverUrl && !path.includes('http')) { throw new Error('No servers found in API description'); } if (!method) { throw new Error('"method" is required to make a request'); } const requestDataFromOpenAPI = openapiOperation && getRequestDataFromOpenApi(openapiOperation, ctx.options.logger); const { payload: stepRequestBodyPayload, // encoding: stepRequestBodyEncoding, contentType: stepRequestBodyContentType, replacements, } = await parseRequestBody(step['requestBody'], ctx); const requestBody = stepRequestBodyPayload || requestDataFromOpenAPI?.requestBody; const contentType = stepRequestBodyContentType || requestDataFromOpenAPI?.contentType; const parameters = joinParameters( // order is important here, the last one wins typeof requestBody === 'object' ? [{ in: 'header', name: 'content-type', value: 'application/json' }] : [], serverUrl?.parameters || [], requestDataFromOpenAPI?.contentTypeParameters || [], // if step.parameters is defined, we do not auto-populate parameters from the openapi operation step.parameters ? [] : requestDataFromOpenAPI?.parameters || [], resolveParameters(workflowLevelParameters, ctx), stepRequestBodyContentType ? [{ in: 'header', name: 'content-type', value: stepRequestBodyContentType }] : [], resolveParameters(step.parameters || [], ctx)); // save local $steps context before evaluating runtime expressions if (!ctx.$steps[stepId]) { ctx.$steps[stepId] = {}; } // save local $workflows context if (!ctx.$workflows[workflowName].steps[stepId]) { ctx.$workflows[workflowName].steps[stepId] = {}; } // Supporting temporal extension of query method https://httpwg.org/http-extensions/draft-ietf-httpbis-safe-method-w-body.html if (method?.toLowerCase() === 'x-query') { method = 'query'; } ctx.$workflows[workflowName].steps[stepId].request = { body: requestBody, header: groupParametersValuesByName(parameters, 'header'), path: groupParametersValuesByName(parameters, 'path'), query: groupParametersValuesByName(parameters, 'query'), url: serverUrl?.url && path ? `${serverUrl?.url}${path}` : serverUrl?.url, method, }; const ctxWithInputs = { ...ctx, $inputs: { ...(ctx.$inputs || {}), ...(ctx.$workflows[workflowName]?.inputs || {}), }, }; const expressionContext = createRuntimeExpressionCtx({ ctx: ctxWithInputs, workflowId: workflowName, step, }); const workflowLevelXSecurityParameters = activeWorkflow?.['x-security'] || []; const xSecurityParameters = resolveXSecurityParameters({ ctx: ctxWithInputs, runtimeContext: expressionContext, step, operation: openapiOperation, workflowLevelXSecurityParameters: workflowLevelXSecurityParameters, }); const evaluatedParameters = joinParameters(parameters, xSecurityParameters).map((parameter) => { return { ...parameter, value: evaluateRuntimeExpressionPayload({ payload: parameter.value, context: expressionContext, logger: ctx.options.logger, }), }; }); for (const param of openapiOperation?.parameters || []) { const { schema, name } = param; collectSecretValues(ctx, schema, groupParametersValuesByName(parameters, param.in), [name]); } const evaluatedBody = evaluateRuntimeExpressionPayload({ payload: requestBody, context: expressionContext, contentType, logger: ctx.options.logger, }); if (replacements && typeof evaluatedBody === 'object') { handlePayloadReplacements({ payload: evaluatedBody, replacements, expressionContext, logger: ctx.options.logger, }); } if (contentType && openapiOperation?.requestBody) { const requestBodySchema = getRequestBodySchema(contentType, openapiOperation); if (typeof requestBody === 'object') { collectSecretValues(ctx, requestBodySchema, requestBody); } } // set evaluated values to the context ctx.$workflows[workflowName].steps[stepId].request = { body: evaluatedBody, header: groupParametersValuesByName(evaluatedParameters, 'header'), path: groupParametersValuesByName(evaluatedParameters, 'path'), query: groupParametersValuesByName(evaluatedParameters, 'query'), url: serverUrl?.url && path ? `${serverUrl?.url}${path}` : serverUrl?.url, method, }; return { serverUrl, path, method, parameters: extractCookieParametersFromHeaderParameters(evaluatedParameters), requestBody: evaluatedBody, openapiOperation, }; } function joinParameters(...parameters) { const parametersWithNames = parameters.flat().filter((param) => 'name' in param); const parameterMap = parametersWithNames.reduce((map, param) => { const key = `${param.name}:${param.in}`; map[key] = param; return map; }, {}); return Object.values(parameterMap); } function groupParametersValuesByName(parameters, inValue) { return parameters.reduce((acc, param) => { if (param.in === inValue && 'name' in param) { acc[param.in === 'header' ? param.name.toLowerCase() : param.name] = param.value; } return acc; }, {}); } function resolveParameters(parameters, ctx) { return parameters .map((parameter) => { const resolvedParameter = resolveReusableComponentItem(parameter, ctx); if (!isParameterWithIn(resolvedParameter)) { return undefined; } return resolvedParameter; }) .filter((parameter) => parameter !== undefined); } function extractCookieParametersFromHeaderParameters(parameters) { const result = []; for (const parameter of parameters) { if (parameter.in === 'header' && parameter.name.toLowerCase() === 'cookie') { const cookieParameters = String(parameter.value) .split(';') .map((cookie) => { const [key, value] = cookie.split('='); return { name: key, value, in: 'cookie' }; }); result.push(...cookieParameters); } else { result.push(parameter); } } return result; } //# sourceMappingURL=prepare-request.js.map