diff --git a/docs/design/spdx-model/spdx-element.md b/docs/design/spdx-model/spdx-element.md index 638b51c..e552e48 100644 --- a/docs/design/spdx-model/spdx-element.md +++ b/docs/design/spdx-model/spdx-element.md @@ -8,8 +8,9 @@ identity handling across all element types. ## Design -`SpdxElement` is a public abstract class. Concrete element types (`SpdxDocument`, `SpdxPackage`, -`SpdxFile`, `SpdxSnippet`, `SpdxRelationship`, `SpdxAnnotation`) inherit from it. +`SpdxElement` is a public abstract class. It is directly inherited by `SpdxDocument`, `SpdxRelationship`, +and `SpdxAnnotation`. `SpdxLicenseElement` is an abstract class that also inherits from `SpdxElement`, and +is in turn inherited by `SpdxPackage`, `SpdxFile`, and `SpdxSnippet`. Data members: diff --git a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonDeserializer.cs b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonDeserializer.cs index fcf3dfb..dd453b2 100644 --- a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonDeserializer.cs +++ b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonDeserializer.cs @@ -249,14 +249,14 @@ public static SpdxSnippet DeserializeSnippet(JsonNode? json) { Id = ParseString(json, SpdxConstants.FieldSpdxId), SnippetFromFile = ParseString(json, SpdxConstants.FieldSnippetFromFile), - SnippetByteStart = Convert.ToInt32(Find(json, SpdxConstants.FieldRanges, SpdxConstants.FieldStartPointer, - SpdxConstants.FieldOffset)?.ToString() ?? ""), - SnippetByteEnd = Convert.ToInt32(Find(json, SpdxConstants.FieldRanges, SpdxConstants.FieldEndPointer, - SpdxConstants.FieldOffset)?.ToString() ?? ""), - SnippetLineStart = Convert.ToInt32(Find(json, SpdxConstants.FieldRanges, SpdxConstants.FieldStartPointer, - SpdxConstants.FieldLineNumber)?.ToString() ?? ""), - SnippetLineEnd = Convert.ToInt32(Find(json, SpdxConstants.FieldRanges, SpdxConstants.FieldEndPointer, - SpdxConstants.FieldLineNumber)?.ToString() ?? ""), + SnippetByteStart = int.TryParse(Find(json, SpdxConstants.FieldRanges, SpdxConstants.FieldStartPointer, + SpdxConstants.FieldOffset)?.ToString(), out var byteStart) ? byteStart : 0, + SnippetByteEnd = int.TryParse(Find(json, SpdxConstants.FieldRanges, SpdxConstants.FieldEndPointer, + SpdxConstants.FieldOffset)?.ToString(), out var byteEnd) ? byteEnd : 0, + SnippetLineStart = int.TryParse(Find(json, SpdxConstants.FieldRanges, SpdxConstants.FieldStartPointer, + SpdxConstants.FieldLineNumber)?.ToString(), out var lineStart) ? lineStart : 0, + SnippetLineEnd = int.TryParse(Find(json, SpdxConstants.FieldRanges, SpdxConstants.FieldEndPointer, + SpdxConstants.FieldLineNumber)?.ToString(), out var lineEnd) ? lineEnd : 0, ConcludedLicense = ParseString(json, SpdxConstants.FieldLicenseConcluded), LicenseInfoInSnippet = ParseStringArray(json, SpdxConstants.FieldLicenseInfoInSnippets), LicenseComments = ParseOptionalString(json, SpdxConstants.FieldLicenseComments), diff --git a/src/DemaConsulting.SpdxModel/SpdxExternalDocumentReference.cs b/src/DemaConsulting.SpdxModel/SpdxExternalDocumentReference.cs index f7bcdb4..69a6e7e 100644 --- a/src/DemaConsulting.SpdxModel/SpdxExternalDocumentReference.cs +++ b/src/DemaConsulting.SpdxModel/SpdxExternalDocumentReference.cs @@ -33,7 +33,7 @@ public sealed class SpdxExternalDocumentReference /// Equality comparer for the same external document reference /// /// - /// This considers packages as being the same if they have the same document. + /// This considers external document references as being the same if they have the same document. /// public static readonly IEqualityComparer Same = new SpdxExternalDocumentReferenceSame(); diff --git a/src/DemaConsulting.SpdxModel/SpdxPackage.cs b/src/DemaConsulting.SpdxModel/SpdxPackage.cs index 41d7939..d34ed32 100644 --- a/src/DemaConsulting.SpdxModel/SpdxPackage.cs +++ b/src/DemaConsulting.SpdxModel/SpdxPackage.cs @@ -304,7 +304,7 @@ public void Enhance(SpdxPackage other) { VerificationCode.Enhance(other.VerificationCode); } - else + else if (VerificationCode == null) { VerificationCode = other.VerificationCode?.DeepCopy(); } diff --git a/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs b/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs index 3e469c4..2a3d78a 100644 --- a/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs +++ b/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs @@ -45,8 +45,10 @@ public static void Add(SpdxDocument document, SpdxRelationship relationship) throw new ArgumentException($"Element {relationship.Id} not found in SPDX document", nameof(relationship)); } - // Ensure the relationship related-element ID matches an element - if (document.GetElement(relationship.RelatedSpdxElement) == null) + // Ensure the relationship related-element ID matches an element, is NOASSERTION, or uses the DocumentRef- external-reference prefix + if (!relationship.RelatedSpdxElement.StartsWith("DocumentRef-") && + relationship.RelatedSpdxElement != SpdxElement.NoAssertion && + document.GetElement(relationship.RelatedSpdxElement) == null) { throw new ArgumentException($"Element {relationship.RelatedSpdxElement} not found in SPDX document", nameof(relationship)); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs index fea7945..76e941d 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs @@ -105,6 +105,50 @@ public void Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults() Assert.AreEqual("SPDXRef-DoapSource", snippet.SnippetFromFile); } + /// + /// Tests deserializing a snippet that has only byte ranges and no optional line-number ranges. + /// This is a regression test for a thrown when line-number + /// range fields were absent and the old code used Convert.ToInt32(""). + /// + [TestMethod] + public void Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero() + { + // Arrange: Create a JSON snippet with only byte ranges (no lineNumber entries) + var json = new JsonObject + { + ["SPDXID"] = "SPDXRef-Snippet", + ["copyrightText"] = "Copyright 2008-2010 John Smith", + ["licenseConcluded"] = "GPL-2.0-only", + ["licenseInfoInSnippets"] = new JsonArray { "GPL-2.0-only" }, + ["ranges"] = new JsonArray + { + new JsonObject + { + ["endPointer"] = new JsonObject + { + ["offset"] = 420, + ["reference"] = "SPDXRef-DoapSource" + }, + ["startPointer"] = new JsonObject + { + ["offset"] = 310, + ["reference"] = "SPDXRef-DoapSource" + } + } + }, + ["snippetFromFile"] = "SPDXRef-DoapSource" + }; + + // Act: Deserialize the JSON object to an SpdxSnippet object (must not throw) + var snippet = Spdx2JsonDeserializer.DeserializeSnippet(json); + + // Assert: Byte ranges are correct and absent line ranges default to 0 + Assert.AreEqual(310, snippet.SnippetByteStart); + Assert.AreEqual(420, snippet.SnippetByteEnd); + Assert.AreEqual(0, snippet.SnippetLineStart); + Assert.AreEqual(0, snippet.SnippetLineEnd); + } + /// /// Tests deserializing multiple snippets. /// diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs index 00f490a..8feb76c 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs @@ -172,7 +172,7 @@ public void SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrec } /// - /// Tests the method reports bad annotators + /// Tests the method reports missing external document ID /// [TestMethod] public void SpdxExternalDocumentReference_Validate_MissingId() @@ -193,7 +193,7 @@ public void SpdxExternalDocumentReference_Validate_MissingId() } /// - /// Tests the method reports bad annotators + /// Tests the method reports missing document URI /// [TestMethod] public void SpdxExternalDocumentReference_Validate_MissingDocument()