Skip to content

Commit

Permalink
fix(Variables): Resolve variables in resolved address & params values
Browse files Browse the repository at this point in the history
  • Loading branch information
medikoo committed Dec 2, 2021
1 parent 6204672 commit 63d54e1
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 16 deletions.
82 changes: 66 additions & 16 deletions lib/configuration/variables/resolve.js
Expand Up @@ -256,20 +256,14 @@ class VariablesResolver {
sourceData.params.map(async (param) => {
if (!param.variables) return;
await this.resolveVariables(resolutionBatchId, propertyPath, param);
if (param.error) throw param.error;
await this.resolveInternalResult(resolutionBatchId, propertyPath, param);
})
);
if (sourceData.params.some((param) => param.variables)) {
throw new ServerlessError('Cannot resolve variable', 'MISSING_VARIABLE_DEPENDENCY');
}
}
if (sourceData.address && sourceData.address.variables) {
// Ensure to have all eventual variables in address resolved
await this.resolveVariables(resolutionBatchId, propertyPath, sourceData.address);
if (sourceData.address.error) throw sourceData.address.error;
if (sourceData.address.variables) {
throw new ServerlessError('Cannot resolve variable', 'MISSING_VARIABLE_DEPENDENCY');
}
await this.resolveInternalResult(resolutionBatchId, propertyPath, sourceData.address);
}
return JSON.parse(
JSON.stringify(
Expand Down Expand Up @@ -309,6 +303,68 @@ class VariablesResolver {
)
);
}

async resolveInternalResult(resolutionBatchId, propertyPath, valueMeta, nestTracker = 10) {
if (valueMeta.error) throw valueMeta.error;
if (valueMeta.variables) {
throw new ServerlessError('Cannot resolve variable', 'MISSING_VARIABLE_DEPENDENCY');
}
if (!nestTracker) {
throw new ServerlessError(
`Cannot resolve variable at "${humanizePropertyPath(
propertyPath.split('\0')
)}": Excessive variables nest depth`,
'EXCESSIVE_RESOLVED_VARIABLES_NEST_DEPTH'
);
}
if (typeof valueMeta.value === 'string') {
const valueVariables = (() => {
try {
if (valueMeta.sourceValues) return resolveSourceValuesVariables(valueMeta.sourceValues);
return parse(valueMeta.value);
} catch (error) {
error.message = `Cannot resolve variable at "${humanizePropertyPath(
propertyPath.split('\0')
)}": Approached variable syntax error in resolved value "${valueMeta.value}": ${
error.message
}`;
delete valueMeta.value;
valueMeta.error = error;
throw error;
}
})();

if (!valueVariables) return;
valueMeta.variables = valueVariables;
delete valueMeta.sourceValues;
await this.resolveVariables(resolutionBatchId, propertyPath, valueMeta);
await this.resolveInternalResult(resolutionBatchId, propertyPath, valueMeta, --nestTracker);
return;
}
const valueEntries = (() => {
if (isPlainObject(valueMeta.value)) return Object.entries(valueMeta.value);
return Array.isArray(valueMeta.value) ? valueMeta.value.entries() : null;
})();
if (!valueEntries) return;
const propertyVariablesMeta = parseEntries(valueEntries, [], new Map());
for (const [propertyKeyPath, propertyValueMeta] of propertyVariablesMeta) {
await this.resolveVariables(resolutionBatchId, propertyPath, propertyValueMeta);
await this.resolveInternalResult(
resolutionBatchId,
propertyPath,
propertyValueMeta,
--nestTracker
);
const propertyKeyPathKeys = propertyKeyPath.split('\0');
const targetKey = propertyKeyPathKeys[propertyKeyPathKeys.length - 1];
let targetObject = valueMeta.value;
for (const parentKey of propertyKeyPathKeys.slice(0, -1)) {
targetObject = targetObject[parentKey];
}
targetObject[targetKey] = propertyValueMeta.value;
}
}

validateCrossPropertyDependency(dependentPropertyPath, dependencyPropertyPath) {
if (dependentPropertyPath === dependencyPropertyPath) {
throw new ServerlessError(
Expand Down Expand Up @@ -531,10 +587,7 @@ Object.defineProperties(
variables: variableData,
};
await this.resolveVariables(resolutionBatchId, propertyPath, meta);
if (meta.error) throw meta.error;
if (meta.variables) {
throw new ServerlessError('Cannot resolve variable', 'MISSING_VARIABLE_DEPENDENCY');
}
await this.resolveInternalResult(resolutionBatchId, propertyPath, meta);
return meta.value;
},
resolveVariablesInString: async (stringValue) => {
Expand All @@ -550,10 +603,7 @@ Object.defineProperties(
variables: variableData,
};
await this.resolveVariables(resolutionBatchId, propertyPath, meta);
if (meta.error) throw meta.error;
if (meta.variables) {
throw new ServerlessError('Cannot resolve variable', 'MISSING_VARIABLE_DEPENDENCY');
}
await this.resolveInternalResult(resolutionBatchId, propertyPath, meta);
return meta.value;
},
});
Expand Down
19 changes: 19 additions & 0 deletions test/unit/lib/configuration/variables/resolve.test.js
Expand Up @@ -40,6 +40,12 @@ describe('test/unit/lib/configuration/variables/resolve.test.js', () => {
resolvesResultVariablesStringInvalid: '${sourceResultVariables(stringInvalid)}',
resolveDeepVariablesConcat:
'${sourceResultVariables(string)}foo${sourceResultVariables(string)}',
resolveDeepVariablesConcatInParam:
'${sourceParam(${sourceResultVariables(string)}foo${sourceResultVariables(string)})}',
resolveDeepVariablesConcatInAddress:
'${sourceAddress:${sourceResultVariables(string)}foo${sourceResultVariables(string)}}',
infiniteDeepVariablesConcat:
'${sourceAddress:${sourceInfiniteString:}foo${sourceResultVariables(string)}}',
resolveVariablesInString: "${sourceResolveVariablesInString('${sourceProperty(foo)}')}",
resolvesVariables: '${sourceResolveVariable("sourceParam(${sourceDirect:})")}',
resolvesVariablesFallback: '${sourceResolveVariable("sourceMissing:, null"), null}',
Expand Down Expand Up @@ -156,6 +162,9 @@ describe('test/unit/lib/configuration/variables/resolve.test.js', () => {
sourceInfinite: {
resolve: () => ({ value: { nest: '${sourceInfinite:}' } }),
},
sourceInfiniteString: {
resolve: () => ({ value: '${sourceInfiniteString:}' }),
},
sourceShared: {
resolve: () => ({
value: {
Expand Down Expand Up @@ -259,6 +268,8 @@ describe('test/unit/lib/configuration/variables/resolve.test.js', () => {

it('should resolve variables in resolved strings which are subject to concatenation', () => {
expect(configuration.resolveDeepVariablesConcat).to.equal('234foo234');
expect(configuration.resolveDeepVariablesConcatInParam).to.equal('432oof432');
expect(configuration.resolveDeepVariablesConcatInAddress).to.equal('432oof432');
});

it('should provide working resolveVariablesInString util', () => {
Expand Down Expand Up @@ -344,6 +355,12 @@ describe('test/unit/lib/configuration/variables/resolve.test.js', () => {
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
});

it('should error on infinite variables resolution recursion', () => {
const valueMeta = variablesMeta.get('infiniteDeepVariablesConcat');
expect(valueMeta).to.not.have.property('variables');
expect(valueMeta.error.code).to.equal('EXCESSIVE_RESOLVED_VARIABLES_NEST_DEPTH');
});

it('should mark deep circular dependency among properties with error', () => {
const valueMeta = variablesMeta.get('propertyDeepCircularA');
expect(valueMeta).to.not.have.property('variables');
Expand Down Expand Up @@ -410,6 +427,7 @@ describe('test/unit/lib/configuration/variables/resolve.test.js', () => {
'propertyDeepCircularC',
'propertyRoot',
'resolvesResultVariablesStringInvalid',
'infiniteDeepVariablesConcat',
'resolvesVariablesInvalid1',
'resolvesVariablesInvalid2',
'missing',
Expand All @@ -435,6 +453,7 @@ describe('test/unit/lib/configuration/variables/resolve.test.js', () => {
'sourceAddress',
'sourceProperty',
'sourceResultVariables',
'sourceInfiniteString',
'sourceResolveVariablesInString',
'sourceResolveVariable',
'sourceIncomplete',
Expand Down

0 comments on commit 63d54e1

Please sign in to comment.