@redocly/openapi-core
Version:
See https://github.com/Redocly/redocly-cli
139 lines • 5.37 kB
JavaScript
import { isPlainObject } from '../../utils/is-plain-object.js';
export const OutputsDefined = () => {
const definedWorkflowOutputs = new Map();
const definedStepOutputs = new Map();
// Store validation tasks to run after all outputs are collected
const deferredValidationTasks = [];
// Match $workflows.{id}.outputs.{key} pattern
function matchWorkflowOutput({ value, report, location, path, definedWorkflowOutputs, }) {
const workflowOutputPattern = /\$workflows\.([^.\s]+)\.outputs\.([^.\s}\]#]+)/g;
let match;
while ((match = workflowOutputPattern.exec(value)) !== null) {
const [fullMatch, workflowId, outputKey] = match;
const definedKeys = definedWorkflowOutputs.get(workflowId);
if (!definedKeys) {
report({
message: `Workflow "${workflowId}" referenced in runtime expression "${fullMatch}" is not defined or has no outputs.`,
location: location.child(path),
});
}
else if (!definedKeys.includes(outputKey)) {
report({
message: `Output key "${outputKey}" is not defined in workflow "${workflowId}". Available outputs: [ ${definedKeys.join(', ')} ].`,
location: location.child(path),
});
}
}
}
// Match $steps.{id}.outputs.{key} pattern
function matchStepOutput({ value, report, location, path, definedStepOutputs, }) {
const stepOutputPattern = /\$steps\.([^.\s]+)\.outputs\.([^.\s}\]#]+)/g;
let match;
while ((match = stepOutputPattern.exec(value)) !== null) {
const [fullMatch, stepId, outputKey] = match;
const definedKeys = definedStepOutputs.get(stepId);
if (!definedKeys) {
report({
message: `Step "${stepId}" referenced in runtime expression "${fullMatch}" is not defined or has no outputs.`,
location: location.child(path),
});
}
else if (!definedKeys.includes(outputKey)) {
report({
message: `Output key "${outputKey}" is not defined in step "${stepId}". Available outputs: [ ${definedKeys.join(', ')} ].`,
location: location.child(path),
});
}
}
}
function checkRuntimeExpressions(value, ctx, path = []) {
if (typeof value === 'string') {
matchWorkflowOutput({
value,
report: ctx.report,
location: ctx.location,
path,
definedWorkflowOutputs,
});
matchStepOutput({
value,
report: ctx.report,
location: ctx.location,
path,
definedStepOutputs,
});
}
else if (isPlainObject(value) || Array.isArray(value)) {
for (const [key, val] of Object.entries(value)) {
checkRuntimeExpressions(val, ctx, [...path, key]);
}
}
}
return {
Step: {
enter(step, { report: _report, location: _location }) {
if (!step.outputs)
return;
definedStepOutputs.set(step.stepId, Object.keys(step.outputs));
},
},
Workflow: {
enter(workflow, { report: _report, location: _location }) {
if (!workflow.outputs)
return;
definedWorkflowOutputs.set(workflow.workflowId, Object.keys(workflow.outputs));
},
},
Parameters: {
enter(parameters, ctx) {
deferredValidationTasks.push(() => {
checkRuntimeExpressions(parameters, ctx);
});
},
},
RequestBody: {
enter(requestBody, ctx) {
deferredValidationTasks.push(() => {
checkRuntimeExpressions(requestBody, ctx);
});
},
},
CriterionObject: {
enter(criteria, ctx) {
deferredValidationTasks.push(() => {
checkRuntimeExpressions(criteria.condition, ctx);
});
},
},
Outputs: {
enter(outputs, ctx) {
deferredValidationTasks.push(() => {
checkRuntimeExpressions(outputs, ctx);
});
},
},
ExtendedSecurity: {
enter(extendedSecurity, ctx) {
deferredValidationTasks.push(() => {
checkRuntimeExpressions(extendedSecurity, ctx);
});
},
},
ExtendedOperation: {
enter(extendedOperation, ctx) {
deferredValidationTasks.push(() => {
checkRuntimeExpressions(extendedOperation, ctx);
});
},
},
Root: {
leave() {
// Run all deferred validations after all outputs are collected
for (const task of deferredValidationTasks) {
task();
}
},
},
};
};
//# sourceMappingURL=outputs-defined.js.map