-
Notifications
You must be signed in to change notification settings - Fork 127
Expand file tree
/
Copy pathTypedComponent.cs
More file actions
146 lines (124 loc) · 6.53 KB
/
TypedComponent.cs
File metadata and controls
146 lines (124 loc) · 6.53 KB
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#nullable disable
namespace Microsoft.ComponentDetection.Contracts.TypedComponent;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.ComponentDetection.Contracts.BcdeModels;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using PackageUrl;
using JsonConverterAttribute = Newtonsoft.Json.JsonConverterAttribute;
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
using SystemTextJson = System.Text.Json.Serialization;
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
[JsonConverter(typeof(TypedComponentConverter))] // Newtonsoft.Json
[DebuggerDisplay("{DebuggerDisplay,nq}")]
[SystemTextJson.JsonConverter(typeof(TypedComponentSystemTextJsonConverter))] // System.Text.Json
public abstract class TypedComponent
{
#pragma warning disable IDE0032 // Use auto property - backing fields needed for lazy ??= initialization
[JsonIgnore] // Newtonsoft.Json
[SystemTextJson.JsonIgnore] // System.Text.Json
private string id;
[JsonIgnore] // Newtonsoft.Json
[SystemTextJson.JsonIgnore] // System.Text.Json
private string baseId;
#pragma warning restore IDE0032
internal TypedComponent()
{
// Reserved for deserialization.
}
/// <summary>Gets the type of the component, must be well known.</summary>
[JsonConverter(typeof(StringEnumConverter))] // Newtonsoft.Json
[JsonProperty("type", Order = int.MinValue)] // Newtonsoft.Json
[SystemTextJson.JsonIgnore] // System.Text.Json - type is handled by TypedComponentSystemTextJsonConverter
public abstract ComponentType Type { get; }
/// <summary>
/// Gets the unique identifier for this component, incorporating both required identity fields
/// (e.g., name, version, type) and optional provenance metadata (download URL, source URL) when available.
/// When no optional metadata is present, this is identical to <see cref="BaseId"/>.
/// When optional metadata is present, the format is: <c>BaseId [optionalProp1:value1 optionalProp2:value2]</c>.
/// </summary>
[JsonProperty("id")] // Newtonsoft.Json
[SystemTextJson.JsonPropertyName("id")] // System.Text.Json
public string Id => this.id ??= this.ComputeId();
/// <summary>
/// Gets the base identifier for this component, derived solely from required identity fields
/// (e.g., name, version, type). Use this when comparing components by package identity alone,
/// without considering provenance metadata such as download or source URLs.
/// </summary>
[JsonIgnore] // Newtonsoft.Json
[SystemTextJson.JsonIgnore] // System.Text.Json
public string BaseId => this.baseId ??= this.ComputeBaseId();
[SystemTextJson.JsonPropertyName("packageUrl")]
public virtual PackageURL PackageUrl { get; }
/// <summary>Gets or sets SPDX license expression(s) declared by the package author.</summary>
[JsonProperty("licenses", NullValueHandling = NullValueHandling.Ignore)]
[SystemTextJson.JsonIgnore(Condition = SystemTextJson.JsonIgnoreCondition.WhenWritingNull)]
[SystemTextJson.JsonPropertyName("licenses")]
public virtual IList<string> Licenses { get; set; }
/// <summary>Gets or sets structured author/creator identity (SPDX 3.0.1 originatedBy).</summary>
[JsonProperty("authorsInfo", NullValueHandling = NullValueHandling.Ignore)]
[SystemTextJson.JsonIgnore(Condition = SystemTextJson.JsonIgnoreCondition.WhenWritingNull)]
[SystemTextJson.JsonPropertyName("authorsInfo")]
public virtual IList<ActorInfo> AuthorsInfo { get; set; }
/// <summary>Gets or sets the direct download URL for the package binary.</summary>
[JsonProperty("downloadUrl", NullValueHandling = NullValueHandling.Ignore)]
[SystemTextJson.JsonIgnore(Condition = SystemTextJson.JsonIgnoreCondition.WhenWritingNull)]
[SystemTextJson.JsonPropertyName("downloadUrl")]
public virtual Uri DownloadUrl { get; set; }
/// <summary>Gets or sets the source code repository URL.</summary>
[JsonProperty("sourceUrl", NullValueHandling = NullValueHandling.Ignore)]
[SystemTextJson.JsonIgnore(Condition = SystemTextJson.JsonIgnoreCondition.WhenWritingNull)]
[SystemTextJson.JsonPropertyName("sourceUrl")]
public virtual Uri SourceUrl { get; set; }
[JsonIgnore] // Newtonsoft.Json
[SystemTextJson.JsonIgnore] // System.Text.Json
internal string DebuggerDisplay => $"{this.Id}";
protected string ValidateRequiredInput(string input, string fieldName, string componentType)
{
return string.IsNullOrWhiteSpace(input)
? throw new ArgumentNullException(fieldName, this.NullPropertyExceptionMessage(fieldName, componentType))
: input;
}
protected T ValidateRequiredInput<T>(T input, string fieldName, string componentType)
{
// Null coalescing for generic types is not available until C# 8
return EqualityComparer<T>.Default.Equals(input, default(T)) ? throw new ArgumentNullException(fieldName, this.NullPropertyExceptionMessage(fieldName, componentType)) : input;
}
protected string NullPropertyExceptionMessage(string propertyName, string componentType)
{
return $"Property {propertyName} of component type {componentType} is required";
}
/// <summary>Computes the base identity string from required fields. Subclasses must implement this.</summary>
/// <returns>The base identity string for this component.</returns>
protected abstract string ComputeBaseId();
/// <summary>
/// Returns optional properties to include in the extended component identity.
/// Subclasses may override to exclude properties already present in <see cref="ComputeBaseId"/>.
/// </summary>
/// <returns>Key-value pairs to append to the base identity.</returns>
protected virtual IEnumerable<KeyValuePair<string, string>> GetExtendedIdProperties()
{
if (this.DownloadUrl != null)
{
yield return new KeyValuePair<string, string>(nameof(this.DownloadUrl), this.DownloadUrl.ToString());
}
if (this.SourceUrl != null)
{
yield return new KeyValuePair<string, string>(nameof(this.SourceUrl), this.SourceUrl.ToString());
}
}
private string ComputeId()
{
var baseId = this.baseId ?? this.ComputeBaseId();
var extras = this.GetExtendedIdProperties().ToList();
if (extras.Count == 0)
{
return baseId;
}
return baseId + " [" + string.Join(" ", extras.Select(e => $"{e.Key}:{e.Value}")) + "]";
}
}