Skip to content

Commit 80c4b5e

Browse files
authored
Update region parsing logic (#326)
1 parent 3526500 commit 80c4b5e

File tree

4 files changed

+40
-19
lines changed

4 files changed

+40
-19
lines changed

src/featureFlag/FeatureFlagSupplier.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { LoggerFactory } from '../telemetry/LoggerFactory';
2+
import { ScopedTelemetry } from '../telemetry/ScopedTelemetry';
3+
import { Telemetry } from '../telemetry/TelemetryDecorator';
24
import { Closeable } from '../utils/Closeable';
35
import { toString } from '../utils/String';
46
import { CompoundFeatureFlag } from './CombinedFeatureFlags';
@@ -17,6 +19,9 @@ import { FeatureFlag, TargetedFeatureFlag } from './FeatureFlagI';
1719
const log = LoggerFactory.getLogger('FeatureFlagSupplier');
1820

1921
export class FeatureFlagSupplier implements Closeable {
22+
@Telemetry()
23+
private readonly telemetry!: ScopedTelemetry;
24+
2025
private readonly _featureFlags = new Map<FeatureFlagConfigKey, FeatureFlag | DynamicFeatureFlag>();
2126
private readonly _targetedFeatureFlags = new Map<
2227
TargetedFeatureFlagConfigKey,
@@ -27,7 +32,7 @@ export class FeatureFlagSupplier implements Closeable {
2732
for (const [key, builder] of Object.entries(FeatureBuilders)) {
2833
const ff = new DynamicFeatureFlag(
2934
key,
30-
() => featureConfigSupplier(key, configSupplier, defaultConfig),
35+
() => featureConfigSupplier(key, configSupplier, defaultConfig, this.telemetry),
3136
builder,
3237
);
3338
this._featureFlags.set(key, ff);
@@ -36,7 +41,7 @@ export class FeatureFlagSupplier implements Closeable {
3641
for (const [key, builder] of Object.entries(TargetedFeatureBuilders)) {
3742
const ff = new DynamicTargetedFeatureFlag(
3843
key,
39-
() => featureConfigSupplier(key, configSupplier, defaultConfig),
44+
() => featureConfigSupplier(key, configSupplier, defaultConfig, this.telemetry),
4045
builder,
4146
);
4247
this._targetedFeatureFlags.set(key, ff);
@@ -77,10 +82,13 @@ function featureConfigSupplier(
7782
key: string,
7883
configSupplier: () => unknown,
7984
defaultConfig: () => unknown,
85+
telemetry: ScopedTelemetry,
8086
): FeatureFlagConfigType | undefined {
87+
telemetry.count('used.config.default', 0);
8188
try {
8289
return FeatureFlagConfigSchema.parse(configSupplier()).features[key];
8390
} catch (err) {
91+
telemetry.count('used.config.default', 1);
8492
log.warn(err, `Failed to parse feature flag config: \n${toString(configSupplier())}. Using defaults instead`);
8593
return FeatureFlagConfigSchema.parse(defaultConfig()).features[key];
8694
}

src/utils/Region.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/resource-type-schemas.html
22
import { LoggerFactory } from '../telemetry/LoggerFactory';
3-
import { dashesToUnderscores, toString } from './String';
3+
import { toString } from './String';
44

55
// Make sure keys and values are exactly the same (ignore casing, '-', '_')
66
export enum AwsRegion {
@@ -51,24 +51,22 @@ export enum AwsRegion {
5151
CN_NORTHWEST_1 = 'cn-northwest-1',
5252
}
5353

54-
function isValidRegion(region: string) {
55-
return region in AwsRegion;
56-
}
54+
const Regions: ReadonlyArray<string> = Object.values(AwsRegion);
5755

5856
export function getRegion(region: unknown): AwsRegion {
59-
let enumKey = String(region).trim();
60-
if (!isValidRegion(enumKey)) {
61-
enumKey = enumKey.toUpperCase();
62-
if (!isValidRegion(enumKey)) {
63-
enumKey = dashesToUnderscores(enumKey);
64-
}
57+
const key = String(region)
58+
.replaceAll('_', '-')
59+
.replaceAll(/[^a-zA-Z0-9-]/g, '')
60+
.toLowerCase()
61+
.trim();
62+
63+
if (key.length < 4 || key.length > 25) {
64+
throw new Error(`Invalid region ${toString(region)} (${key})`);
6565
}
6666

67-
const value = AwsRegion[enumKey as keyof typeof AwsRegion];
68-
if (!value) {
67+
if (!Regions.includes(key)) {
6968
LoggerFactory.getLogger('Region').warn(`Unknown region ${toString(region)}`);
70-
return enumKey.replaceAll('_', '-').toLowerCase() as AwsRegion;
7169
}
7270

73-
return value;
71+
return key as AwsRegion;
7472
}

tst/unit/utils/Errors.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Error: Request cancelled for key: SendDocuments
5656
test('full stack', () => {
5757
expect(
5858
extractLocationFromStack(String.raw`
59-
Error: ENOENT: no such file or directory, scandir 'cfn-lsp/cloudformation-languageserver/bundle/development/.aws-cfn-storage/lmdb'
59+
Error: ENOENT: no such file or directory, scandir 'some-dir/cloudformation-languageserver/bundle/development/.aws-cfn-storage/lmdb'
6060
at readdirSync (node:fs:1584:26)
6161
at node:electron/js2c/node_init:2:16044
6262
at LMDBStoreFactory.cleanupOldVersions (webpack://aws/cloudformation-languageserver/src/datastore/LMDB.ts?d928:98:36)
@@ -66,7 +66,7 @@ Error: ENOENT: no such file or directory, scandir 'cfn-lsp/cloudformation-langua
6666
`),
6767
).toEqual({
6868
'error.message':
69-
"Error: ENOENT: no such file or directory, scandir 'cfn-lsp/cloudformation-languageserver/bundle/development/.aws-cfn-storage/lmdb'",
69+
"Error: ENOENT: no such file or directory, scandir 'some-dir/cloudformation-languageserver/bundle/development/.aws-cfn-storage/lmdb'",
7070
'error.stack0': 'readdirSync node:fs 1584:26',
7171
'error.stack1': 'undefined node:electron/js2c/node_init 2:16044',
7272
'error.stack2':

tst/unit/utils/Region.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,24 @@ describe('Region', () => {
2626
expect(getRegion('EU_WEST_1')).toBe(AwsRegion.EU_WEST_1);
2727
});
2828

29-
it('should not throw when region is unknown or invalid', () => {
29+
it('should not throw when region has a valid pattern', () => {
3030
expect(getRegion('invalid_region')).toBe('invalid-region');
3131
expect(getRegion('US_test-1')).toBe('us-test-1');
3232
});
33+
34+
it('can parse all regions', () => {
35+
for (const reg of Object.keys(AwsRegion)) {
36+
expect(getRegion(reg)).toBe(reg.toLowerCase().replaceAll('_', '-'));
37+
}
38+
39+
for (const reg of Object.values(AwsRegion)) {
40+
expect(getRegion(reg)).toBe(reg);
41+
}
42+
});
43+
44+
it('throws on invalid regions', () => {
45+
expect(() => getRegion('a')).toThrow('Invalid region a (a)');
46+
expect(() => getRegion('a'.repeat(26))).toThrow(`Invalid region ${'a'.repeat(26)} (${'a'.repeat(26)})`);
47+
});
3348
});
3449
});

0 commit comments

Comments
 (0)