-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
uniqueid.ts
106 lines (90 loc) · 3.11 KB
/
uniqueid.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { unresolved } from './encoding';
import { md5hash } from './md5';
/**
* Resources with this ID are hidden from humans
*
* They do not appear in the human-readable part of the logical ID,
* but they are included in the hash calculation.
*/
const HIDDEN_FROM_HUMAN_ID = 'Resource';
/**
* Resources with this ID are complete hidden from the logical ID calculation.
*/
const HIDDEN_ID = 'Default';
const PATH_SEP = '/';
const HASH_LEN = 8;
const MAX_HUMAN_LEN = 240; // max ID len is 255
const MAX_ID_LEN = 255;
/**
* Calculates a unique ID for a set of textual components.
*
* This is done by calculating a hash on the full path and using it as a suffix
* of a length-limited "human" rendition of the path components.
*
* @param components The path components
* @returns a unique alpha-numeric identifier with a maximum length of 255
*/
export function makeUniqueId(components: string[]) {
components = components.filter(x => x !== HIDDEN_ID);
if (components.length === 0) {
throw new Error('Unable to calculate a unique id for an empty set of components');
}
// Lazy require in order to break a module dependency cycle
const unresolvedTokens = components.filter(c => unresolved(c));
if (unresolvedTokens.length > 0) {
throw new Error(`ID components may not include unresolved tokens: ${unresolvedTokens.join(',')}`);
}
// top-level resources will simply use the `name` as-is in order to support
// transparent migration of cloudformation templates to the CDK without the
// need to rename all resources.
if (components.length === 1) {
// we filter out non-alpha characters but that is actually a bad idea
// because it could create conflicts ("A-B" and "AB" will render the same
// logical ID). sadly, changing it in the 1.x version line is impossible
// because it will be a breaking change. we should consider for v2.0.
// https:/aws/aws-cdk/issues/6421
const candidate = removeNonAlphanumeric(components[0]);
// if our candidate is short enough, use it as is. otherwise, fall back to
// the normal mode.
if (candidate.length <= MAX_ID_LEN) {
return candidate;
}
}
const hash = pathHash(components);
const human = removeDupes(components)
.filter(x => x !== HIDDEN_FROM_HUMAN_ID)
.map(removeNonAlphanumeric)
.join('')
.slice(0, MAX_HUMAN_LEN);
return human + hash;
}
/**
* Take a hash of the given path.
*
* The hash is limited in size.
*/
function pathHash(path: string[]): string {
const md5 = md5hash(path.join(PATH_SEP));
return md5.slice(0, HASH_LEN).toUpperCase();
}
/**
* Removes all non-alphanumeric characters in a string.
*/
function removeNonAlphanumeric(s: string) {
return s.replace(/[^A-Za-z0-9]/g, '');
}
/**
* Remove duplicate "terms" from the path list
*
* If the previous path component name ends with this component name, skip the
* current component.
*/
function removeDupes(path: string[]): string[] {
const ret = new Array<string>();
for (const component of path) {
if (ret.length === 0 || !ret[ret.length - 1].endsWith(component)) {
ret.push(component);
}
}
return ret;
}