Skip to content

Commit

Permalink
ComponentVersion became optional
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
  • Loading branch information
jkowalleck committed Sep 15, 2022
1 parent a080107 commit 52d71d7
Show file tree
Hide file tree
Showing 17 changed files with 181 additions and 76 deletions.
5 changes: 4 additions & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ All notable changes to this project will be documented in this file.
* Changed
* Method `\CycloneDX\Core\Serialize\{DOM,JSON}\Normalizers\ExternalReferenceNormalizer::normalize` throw `DomainException` when `ExternalReference`'s type was not supported by the spec. (via [#65])
This is considered a non-breaking change, because the behaviour was already documented in the API, even though there was no need for an implementation before.
* Class `\CycloneDX\Core\Models\Component`'s property `version` is optional now, to reflect CycloneDX v1.4. (via [#118])
This affects constructor arguments, and affects methods `{get,set}Version()`.
* Added
* New class constant `\CycloneDX\Core\Spec\Version::V_1_4` for CycloneDX v1.4. (via [#65])
* New class `\CycloneDX\Core\Spec\Spec14` to reflect CycloneDX v1.4. (via [#65])
* Support for CycloneDX v1.4 in `CycloneDX\Core\Validation\Validators\{Json,Xml}StrictValidator`. (via [#65])
* Support for CycloneDX v1.4 in `\CycloneDX\Core\Validation\Validators\{Json,Xml}StrictValidator`. (via [#65])
* New methods in class `\CycloneDX\Core\Spec\Spec1{1,2,3}` (via [#65])
* `::getSupportsExternalReferenceTypes()`
* `::isSupportsExternalReferenceType()`
* New class constant `CycloneDX\Core\Enums\ExternalReferenceType::RELEASE_NOTES` to reflect CycloneDX v1.4. (via [#65])

[#65]: https://github.com/CycloneDX/cyclonedx-php-library/pull/65
[#118]: https://github.com/CycloneDX/cyclonedx-php-library/pull/118

## 1.6.2 - 2022-09-12

Expand Down
10 changes: 4 additions & 6 deletions src/Core/Models/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ class Component
* The component version. The version should ideally comply with semantic versioning
* but is not enforced.
*
* @var string
*
* @psalm-suppress PropertyNotSetInConstructor
* @var string|null
*/
private $version;

Expand Down Expand Up @@ -303,15 +301,15 @@ public function setHashRepository(?HashRepository $hashRepository): self
return $this;
}

public function getVersion(): string
public function getVersion(): ?string
{
return $this->version;
}

/**
* @return $this
*/
public function setVersion(string $version): self
public function setVersion(?string $version): self
{
$this->version = $version;

Expand Down Expand Up @@ -368,7 +366,7 @@ public function setExternalReferenceRepository(?ExternalReferenceRepository $ext
*
* @throws DomainException if type is unknown
*/
public function __construct(string $type, string $name, string $version)
public function __construct(string $type, string $name, ?string $version = null)
{
$this->setType($type);
$this->setName($name);
Expand Down
11 changes: 9 additions & 2 deletions src/Core/Serialize/DOM/Normalizers/ComponentNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ public function normalize(Component $component): DOMElement

$type = $component->getType();
if (false === $spec->isSupportedComponentType($type)) {
$reportFQN = "$group/$name@$version";
$reportFQN = "$group/$name";
if (null !== $version) {
$reportFQN .= "@$version";
}
throw new DomainException("Component '$reportFQN' has unsupported type: $type");
}

Expand All @@ -80,7 +83,11 @@ public function normalize(Component $component): DOMElement
// publisher
$this->simpleDomSafeTextElement($document, 'group', $group),
$this->simpleDomSafeTextElement($document, 'name', $name),
$this->simpleDomSafeTextElement($document, 'version', $version),
$this->simpleDomSafeTextElement($document, 'version',
null === $version && $spec->requiresComponentVersion()
? ''
: $version
),
$this->simpleDomSafeTextElement($document, 'description', $component->getDescription()),
// scope
$this->normalizeHashes($component->getHashRepository()),
Expand Down
9 changes: 7 additions & 2 deletions src/Core/Serialize/JSON/Normalizers/ComponentNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ public function normalize(Component $component): array

$type = $component->getType();
if (false === $spec->isSupportedComponentType($type)) {
$reportFQN = "$group/$name@$version";
$reportFQN = "$group/$name";
if (null !== $version) {
$reportFQN .= "@$version";
}
throw new DomainException("Component '$reportFQN' has unsupported type: $type");
}

Expand All @@ -67,7 +70,9 @@ public function normalize(Component $component): array
'bom-ref' => $bomRef,
'type' => $type,
'name' => $name,
'version' => $version,
'version' => null === $version && $spec->requiresComponentVersion()
? ''
: $version,
'group' => $group,
'description' => $component->getDescription(),
'licenses' => $this->normalizeLicense($component->getLicense()),
Expand Down
5 changes: 5 additions & 0 deletions src/Core/Spec/Spec11.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,9 @@ public function supportsExternalReferenceHashes(): bool
{
return false;
}

public function requiresComponentVersion(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/Core/Spec/Spec12.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,9 @@ public function supportsExternalReferenceHashes(): bool
{
return false;
}

public function requiresComponentVersion(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/Core/Spec/Spec13.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,9 @@ public function supportsExternalReferenceHashes(): bool
{
return true;
}

public function requiresComponentVersion(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/Core/Spec/Spec14.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,9 @@ public function supportsExternalReferenceHashes(): bool
{
return true;
}

public function requiresComponentVersion(): bool
{
return false;
}
}
5 changes: 5 additions & 0 deletions src/Core/Spec/SpecInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,9 @@ public function isSupportsExternalReferenceType(string $referenceType): bool;
* version < 1.3 does not support hashes in ExternalReference.
*/
public function supportsExternalReferenceHashes(): bool;

/**
* version < 1.4 requires components to have a version, later it became optional.
*/
public function requiresComponentVersion(): bool;
}
81 changes: 49 additions & 32 deletions tests/Core/Serialize/DOM/Normalizers/ComponentNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ public function testNormalizeThrowsOnUnsupportedType(): void
Component::class,
[
'getName' => 'foo',
'getVersion' => 'some-version',
'getType' => 'FakeType',
]
);
$spec = $this->createMock(SpecInterface::class);
$factory = $this->createConfiguredMock(NormalizerFactory::class, ['getSpec' => $spec]);
$normalizer = new Normalizers\ComponentNormalizer($factory);

$spec->expects(self::once())->method('isSupportedComponentType')
$spec->expects(self::once())
->method('isSupportedComponentType')
->with('FakeType')
->willReturn(false);

Expand All @@ -72,13 +72,16 @@ public function testNormalizeThrowsOnUnsupportedType(): void
$normalizer->normalize($component);
}

public function testNormalizeMinimal(): void
/**
* @dataProvider dbNormalizeMinimal
*/
public function testNormalizeMinimal(string $expected, bool $requiresComponentVersion): void
{
$component = $this->createConfiguredMock(
Component::class,
[
'getName' => 'myName',
'getVersion' => 'some-version',
'getVersion' => null,
'getType' => 'FakeType',
'getGroup' => null,
'getDescription' => null,
Expand All @@ -94,16 +97,28 @@ public function testNormalizeMinimal(): void
);
$normalizer = new Normalizers\ComponentNormalizer($factory);

$spec->expects(self::once())->method('isSupportedComponentType')
$spec->expects(self::once())
->method('isSupportedComponentType')
->with('FakeType')
->willReturn(true);
$spec->method('requiresComponentVersion')
->willReturn($requiresComponentVersion);

$got = $normalizer->normalize($component);
$actual = $normalizer->normalize($component);

self::assertStringEqualsDomNode(
'<component type="FakeType"><name>myName</name><version>some-version</version></component>',
$got
);
self::assertStringEqualsDomNode($expected, $actual);
}

public function dbNormalizeMinimal(): \Generator
{
yield 'mandatory ComponentVersion' => [
'<component type="FakeType"><name>myName</name><version></version></component>',
true,
];
yield 'optional ComponentVersion' => [
'<component type="FakeType"><name>myName</name></component>',
false,
];
}

/**
Expand Down Expand Up @@ -148,13 +163,16 @@ public function testNormalizeFull(): void
);
$normalizer = new Normalizers\ComponentNormalizer($factory);

$spec->expects(self::once())->method('isSupportedComponentType')
$spec->expects(self::once())
->method('isSupportedComponentType')
->with('FakeType')
->willReturn(true);
$licenseExpressionNormalizer->expects(self::once())->method('normalize')
$licenseExpressionNormalizer->expects(self::once())
->method('normalize')
->with($component->getLicense())
->willReturn($factory->getDocument()->createElement('FakeLicense', 'dummy'));
$hashRepositoryNormalizer->expects(self::once())->method('normalize')
$hashRepositoryNormalizer->expects(self::once())
->method('normalize')
->with($component->getHashRepository())
->willReturn([$factory->getDocument()->createElement('FakeHash', 'dummy')]);

Expand Down Expand Up @@ -185,7 +203,6 @@ public function testNormalizeUnsupportedLicenseExpression(): void
Component::class,
[
'getName' => 'myName',
'getVersion' => 'some-version',
'getType' => 'FakeType',
'getLicense' => $this->createConfiguredMock(LicenseExpression::class, ['getExpression' => 'myLicense']),
]
Expand Down Expand Up @@ -217,10 +234,12 @@ public function testNormalizeUnsupportedLicenseExpression(): void
return true;
};

$spec->expects(self::once())->method('isSupportedComponentType')
$spec->expects(self::once())
->method('isSupportedComponentType')
->with('FakeType')
->willReturn(true);
$licenseNormalizer->expects(self::once())->method('normalize')
$licenseNormalizer->expects(self::once())
->method('normalize')
->with($this->callback($transformedLicenseTest))
->willReturn([$factory->getDocument()->createElement('FakeLicense', 'dummy')]);

Expand All @@ -229,7 +248,6 @@ public function testNormalizeUnsupportedLicenseExpression(): void
self::assertStringEqualsDomNode(
'<component type="FakeType">'.
'<name>myName</name>'.
'<version>some-version</version>'.
'<licenses><FakeLicense>dummy</FakeLicense></licenses>'.
'</component>',
$got
Expand All @@ -242,7 +260,6 @@ public function testNormalizeDisjunctiveLicenses(): void
Component::class,
[
'getName' => 'myName',
'getVersion' => 'some-version',
'getType' => 'FakeType',
'getLicense' => $this->createConfiguredMock(DisjunctiveLicenseRepository::class, ['count' => 1]),
]
Expand All @@ -259,10 +276,12 @@ public function testNormalizeDisjunctiveLicenses(): void
);
$normalizer = new Normalizers\ComponentNormalizer($factory);

$spec->expects(self::once())->method('isSupportedComponentType')
$spec->expects(self::once())
->method('isSupportedComponentType')
->with('FakeType')
->willReturn(true);
$licenseNormalizer->expects(self::once())->method('normalize')
$licenseNormalizer->expects(self::once())
->method('normalize')
->with($component->getLicense())
->willReturn([$factory->getDocument()->createElement('FakeLicense', 'dummy')]);

Expand All @@ -271,7 +290,6 @@ public function testNormalizeDisjunctiveLicenses(): void
self::assertStringEqualsDomNode(
'<component type="FakeType">'.
'<name>myName</name>'.
'<version>some-version</version>'.
'<licenses><FakeLicense>dummy</FakeLicense></licenses>'.
'</component>',
$got
Expand All @@ -284,7 +302,6 @@ public function testNormalizeDisjunctiveLicensesEmpty(): void
Component::class,
[
'getName' => 'myName',
'getVersion' => 'some-version',
'getType' => 'FakeType',
'getLicense' => $this->createConfiguredMock(DisjunctiveLicenseRepository::class, ['count' => 0]),
]
Expand All @@ -301,17 +318,18 @@ public function testNormalizeDisjunctiveLicensesEmpty(): void
);
$normalizer = new Normalizers\ComponentNormalizer($factory);

$spec->expects(self::once())->method('isSupportedComponentType')
$spec->expects(self::once())
->method('isSupportedComponentType')
->with('FakeType')
->willReturn(true);
$licenseNormalizer->expects(self::never())->method('normalize');
$licenseNormalizer->expects(self::never())
->method('normalize');

$got = $normalizer->normalize($component);

self::assertStringEqualsDomNode(
'<component type="FakeType">'.
'<name>myName</name>'.
'<version>some-version</version>'.
'</component>',
$got
);
Expand All @@ -325,7 +343,6 @@ public function testNormalizeExternalReferences(): void
Component::class,
[
'getName' => 'myName',
'getVersion' => 'some-version',
'getType' => 'FakeType',
'getExternalReferenceRepository' => $this->createConfiguredMock(ExternalReferenceRepository::class, ['count' => 1]),
]
Expand All @@ -342,7 +359,8 @@ public function testNormalizeExternalReferences(): void
);
$normalizer = new Normalizers\ComponentNormalizer($factory);

$spec->expects(self::once())->method('isSupportedComponentType')
$spec->expects(self::once())
->method('isSupportedComponentType')
->with('FakeType')
->willReturn(true);
$externalReferenceRepositoryNormalizer->expects(self::once())
Expand All @@ -355,7 +373,6 @@ public function testNormalizeExternalReferences(): void
self::assertStringEqualsDomNode(
'<component type="FakeType">'.
'<name>myName</name>'.
'<version>some-version</version>'.
'<externalReferences><FakeExternalReference>dummy</FakeExternalReference></externalReferences>'.
'</component>',
$actual
Expand All @@ -368,7 +385,6 @@ public function testNormalizeExternalReferencesOmitIfEmpty(): void
Component::class,
[
'getName' => 'myName',
'getVersion' => 'some-version',
'getType' => 'FakeType',
'getExternalReferenceRepository' => $this->createConfiguredMock(ExternalReferenceRepository::class, ['count' => 0]),
]
Expand All @@ -385,17 +401,18 @@ public function testNormalizeExternalReferencesOmitIfEmpty(): void
);
$normalizer = new Normalizers\ComponentNormalizer($factory);

$spec->expects(self::once())->method('isSupportedComponentType')
$spec->expects(self::once())
->method('isSupportedComponentType')
->with('FakeType')
->willReturn(true);
$externalReferenceRepositoryNormalizer->expects(self::never())->method('normalize');
$externalReferenceRepositoryNormalizer->expects(self::never())
->method('normalize');

$actual = $normalizer->normalize($component);

self::assertStringEqualsDomNode(
'<component type="FakeType">'.
'<name>myName</name>'.
'<version>some-version</version>'.
'</component>',
$actual
);
Expand Down
Loading

0 comments on commit 52d71d7

Please sign in to comment.