Skip to content

Commit e05f837

Browse files
authored
PackageUrlFactory::makeFromComponent() - checksums and download_url (#146)
Factory for `PackageURL` from `Models.Component` can handle additional data sources: * `Models.Component.hashes` map -> `PackageURL.qualifiers.checksum` list * `Models.Component.externalReferences[distribution].url` -> `PackageURL.qualifiers.download_url` * Method `Factories.PackageUrlFactory.makeFromComponent()` got a new optional parameter `sort`, to indicate whether to go the extra mile and bring hashes and qualifiers in alphabetical order. This feature switch is related to reproducible builds. Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 50a6694 commit e05f837

File tree

5 files changed

+117
-13
lines changed

5 files changed

+117
-13
lines changed

HISTORY.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
## unreleased
66

77
* Added
8-
* Getters/properties that represent the corresponding parameters of class constructor. (via [#145])
8+
* New getters/properties that represent the corresponding parameters of class constructor. (via [#145])
99
* `Builders.FromPackageJson.ComponentBuilder.extRefFactory`,
1010
`Builders.FromPackageJson.ComponentBuilder.licenseFactory`
1111
* `Builders.FromPackageJson.ToolBuilder.extRefFactory`
@@ -14,8 +14,15 @@ All notable changes to this project will be documented in this file.
1414
* `Serialize.JsonSerializer.normalizerFactory`
1515
* `Serialize.XmlBaseSerializer.normalizerFactory`,
1616
`Serialize.XmlSerializer.normalizerFactory`
17+
* Factory for `PackageURL` from `Models.Component` can handle additional data sources, now. (via [#146])
18+
* `Models.Component.hashes` map -> `PackageURL.qualifiers.checksum` list
19+
* `Models.Component.externalReferences[distribution].url` -> `PackageURL.qualifiers.download_url`
20+
* Method `Factories.PackageUrlFactory.makeFromComponent()` got a new optional param `sort`,
21+
to indicate whether to go the extra mile and bring hashes and qualifiers in alphabetical order.
22+
This feature switch is related to reproducible builds.
1723

1824
[#145]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/145
25+
[#146]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/146
1926

2027
## 1.1.0 - 2022-07-29
2128

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"homepage": "https://github.com/CycloneDX/cyclonedx-javascript-library#readme",
1919
"repository": {
2020
"type": "git",
21-
"url": "https://github.com/CycloneDX/cyclonedx-javascript-library.git"
21+
"url": "git+https://github.com/CycloneDX/cyclonedx-javascript-library.git"
2222
},
2323
"bugs": {
2424
"url": "https://github.com/CycloneDX/cyclonedx-javascript-library/issues"

src/factories/packageUrl.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,37 @@ export class PackageUrlFactory {
3232
return this.#type
3333
}
3434

35-
makeFromComponent (component: Component): PackageURL | undefined {
35+
makeFromComponent (component: Component, sort: boolean = false): PackageURL | undefined {
36+
/**
37+
* For the list/spec of the well-known keys, see
38+
* {@link https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs}
39+
*/
3640
const qualifiers: PackageURL['qualifiers'] = {}
3741
let subpath: PackageURL['subpath']
3842

39-
for (const e of component.externalReferences) {
40-
if (e.type === ExternalReferenceType.VCS) {
41-
[qualifiers.vcs_url, subpath] = e.url.toString().split('#', 2)
42-
break
43+
const extRefs = component.externalReferences
44+
for (const extRef of (sort ? extRefs.sorted() : extRefs)) {
45+
switch (extRef.type) {
46+
case ExternalReferenceType.VCS:
47+
[qualifiers.vcs_url, subpath] = extRef.url.toString().split('#', 2)
48+
break
49+
case ExternalReferenceType.Distribution:
50+
qualifiers.download_url = extRef.url.toString()
51+
break
4352
}
4453
}
4554

55+
const hashes = component.hashes
56+
if (hashes.size > 0) {
57+
qualifiers.checksum = Array.from(
58+
sort ? hashes.sorted() : hashes,
59+
([hashAlgo, hashCont]) => `${hashAlgo.toLowerCase()}:${hashCont.toLowerCase()}`
60+
).join(',')
61+
}
62+
4663
try {
64+
// Do not beautify the parameters here, because that is in the domain of PackageURL and its representation.
65+
// No need to convert an empty `subpath` string to `undefined` and such.
4766
return new PackageURL(
4867
this.#type,
4968
component.group,

tests/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ instead of the source(`src/`).
99
Test files must follow the pattern `**.{spec,test}.[cm]?js`,
1010
to be picked up.
1111

12+
## Snapshots
13+
14+
Some tests check against snapshots.
15+
To update these, set the env var `CJL_TEST_UPDATE_SNAPSHOTS` to a non-falsy value.
16+
1217
## Run node tests
1318

1419
Test runner is `mocha`,

tests/integration/Factories.PackageUrlFactory.test.js

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ suite('Factories.PackageUrlFactory', () => {
4141
''
4242
)
4343
const expected = undefined
44+
4445
const actual = sut.makeFromComponent(component)
46+
4547
assert.strictEqual(actual, expected)
4648
})
4749

@@ -58,38 +60,109 @@ suite('Factories.PackageUrlFactory', () => {
5860
}
5961
)
6062
const expected = new PackageURL('testing', `@group-${salt}`, `name-${salt}`, `v1+${salt}`, {}, undefined)
63+
64+
const actual = sut.makeFromComponent(component)
65+
66+
assert.deepStrictEqual(actual, expected)
67+
})
68+
69+
test('extRef[vcs] -> qualifiers.vcs-url without subpath', () => {
70+
const component = new Models.Component(
71+
Enums.ComponentType.Library,
72+
`name-${salt}`,
73+
{
74+
externalReferences: new Models.ExternalReferenceRepository([
75+
new Models.ExternalReference('git+https://foo.bar/repo.git', Enums.ExternalReferenceType.VCS)
76+
])
77+
}
78+
)
79+
const expected = new PackageURL('testing', undefined, `name-${salt}`, undefined, { vcs_url: 'git+https://foo.bar/repo.git' }, undefined)
80+
6181
const actual = sut.makeFromComponent(component)
82+
6283
assert.deepStrictEqual(actual, expected)
6384
})
6485

65-
test('vcs-url without subpath', () => {
86+
test('extRef[vcs] -> qualifiers.vcs-url with subpath', () => {
6687
const component = new Models.Component(
6788
Enums.ComponentType.Library,
6889
`name-${salt}`,
6990
{
7091
externalReferences: new Models.ExternalReferenceRepository([
71-
new Models.ExternalReference('git://foo.bar', Enums.ExternalReferenceType.VCS)
92+
new Models.ExternalReference('git+https://foo.bar/repo.git#sub/path', Enums.ExternalReferenceType.VCS)
7293
])
7394
}
7495
)
75-
const expected = new PackageURL('testing', undefined, `name-${salt}`, undefined, { vcs_url: 'git://foo.bar' }, undefined)
96+
const expected = new PackageURL('testing', undefined, `name-${salt}`, undefined, { vcs_url: 'git+https://foo.bar/repo.git' }, 'sub/path')
97+
7698
const actual = sut.makeFromComponent(component)
99+
77100
assert.deepStrictEqual(actual, expected)
78101
})
79102

80-
test('vcs-url with subpath', () => {
103+
test('extRef[distribution] -> qualifiers.download_url', () => {
81104
const component = new Models.Component(
82105
Enums.ComponentType.Library,
83106
`name-${salt}`,
84107
{
85108
externalReferences: new Models.ExternalReferenceRepository([
86-
new Models.ExternalReference('git://foo.bar#sub/path', Enums.ExternalReferenceType.VCS)
109+
new Models.ExternalReference('https://foo.bar/download', Enums.ExternalReferenceType.Distribution)
87110
])
88111
}
89112
)
90-
const expected = new PackageURL('testing', undefined, `name-${salt}`, undefined, { vcs_url: 'git://foo.bar' }, 'sub/path')
113+
const expected = new PackageURL('testing', undefined, `name-${salt}`, undefined, { download_url: 'https://foo.bar/download' }, undefined)
114+
91115
const actual = sut.makeFromComponent(component)
116+
92117
assert.deepStrictEqual(actual, expected)
93118
})
119+
120+
test('hashes -> qualifiers.checksum', () => {
121+
const component = new Models.Component(
122+
Enums.ComponentType.Library,
123+
`name-${salt}`,
124+
{
125+
hashes: new Models.HashRepository([
126+
[Enums.HashAlgorithm['SHA-256'], 'C3AB8FF13720E8AD9047DD39466B3C8974E592C2FA383D4A3960714CAEF0C4F2']
127+
])
128+
}
129+
)
130+
const expected = new PackageURL('testing', undefined, `name-${salt}`, undefined, { checksum: 'sha-256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2' }, undefined)
131+
132+
const actual = sut.makeFromComponent(component)
133+
134+
assert.deepStrictEqual(actual, expected)
135+
})
136+
137+
test('sorted', () => {
138+
const component = new Models.Component(
139+
Enums.ComponentType.Library,
140+
'name',
141+
{
142+
externalReferences: new Models.ExternalReferenceRepository([
143+
new Models.ExternalReference('git+https://foo.bar/repo.git', Enums.ExternalReferenceType.VCS),
144+
new Models.ExternalReference('https://foo.bar/download', Enums.ExternalReferenceType.Distribution)
145+
]),
146+
hashes: new Models.HashRepository([
147+
[Enums.HashAlgorithm['SHA-256'], 'C3AB8FF13720E8AD9047DD39466B3C8974E592C2FA383D4A3960714CAEF0C4F2'],
148+
[Enums.HashAlgorithm.BLAKE3, 'aa51dcd43d5c6c5203ee16906fd6b35db298b9b2e1de3fce81811d4806b76b7d']
149+
])
150+
}
151+
)
152+
const expectedObject = new PackageURL('testing', undefined, 'name', undefined,
153+
{
154+
// expect sorted hash list
155+
checksum: 'blake3:aa51dcd43d5c6c5203ee16906fd6b35db298b9b2e1de3fce81811d4806b76b7d,sha-256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2',
156+
download_url: 'https://foo.bar/download',
157+
vcs_url: 'git+https://foo.bar/repo.git'
158+
}, undefined)
159+
// expect objet's keys in alphabetical oder, expect sorted hash list
160+
const expectedString = 'pkg:testing/name?checksum=blake3:aa51dcd43d5c6c5203ee16906fd6b35db298b9b2e1de3fce81811d4806b76b7d,sha-256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2&download_url=https://foo.bar/download&vcs_url=git+https://foo.bar/repo.git'
161+
162+
const actual = sut.makeFromComponent(component, true)
163+
164+
assert.deepStrictEqual(actual, expectedObject)
165+
assert.deepStrictEqual(actual.toString(), expectedString)
166+
})
94167
})
95168
})

0 commit comments

Comments
 (0)