| | | 1 | | using System.Collections; |
| | | 2 | | using System.Text.RegularExpressions; |
| | | 3 | | using Spdx3.Exceptions; |
| | | 4 | | using Spdx3.Model.Core.Classes; |
| | | 5 | | using Spdx3.Model.Core.Enums; |
| | | 6 | | using Spdx3.Model.SimpleLicensing.Classes; |
| | | 7 | | using Spdx3.Model.Software.Classes; |
| | | 8 | | |
| | | 9 | | namespace Spdx3.Model.Lite; |
| | | 10 | | |
| | | 11 | | /// <summary> |
| | | 12 | | /// Visitor that checks a catalog for compliance with the Spdx Lite domain requirements. |
| | | 13 | | /// See https://spdx.github.io/spdx-spec/v3.0.1/annexes/spdx-lite/ |
| | | 14 | | /// </summary> |
| | | 15 | | internal class LiteDomainComplianceVisitor : ILiteDomainComplianceVisitor |
| | | 16 | | { |
| | 37 | 17 | | internal List<LiteDomainComplianceFinding> Findings { get; } = []; |
| | | 18 | | |
| | | 19 | | public void Visit(SpdxDocument spdxDocument) |
| | | 20 | | { |
| | 3 | 21 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, spdxDocument, nameof(spdxDocument.CreationInfo), |
| | 3 | 22 | | spdxDocument.CreationInfo); |
| | 3 | 23 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, spdxDocument, nameof(spdxDocument.SpdxId), |
| | 3 | 24 | | spdxDocument.SpdxId); |
| | 3 | 25 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, spdxDocument, nameof(spdxDocument.Element), |
| | 3 | 26 | | (IList)spdxDocument.Element); |
| | 3 | 27 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, spdxDocument, nameof(spdxDocument.RootElement), |
| | 3 | 28 | | (IList)spdxDocument.RootElement); |
| | 3 | 29 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, spdxDocument, nameof(spdxDocument.Comment), |
| | 3 | 30 | | spdxDocument.Comment); |
| | 3 | 31 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, spdxDocument, |
| | 3 | 32 | | nameof(spdxDocument.DataLicense), spdxDocument.DataLicense); |
| | 3 | 33 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, spdxDocument, nameof(spdxDocument.Name), |
| | 3 | 34 | | spdxDocument.Name); |
| | 3 | 35 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, spdxDocument, |
| | 3 | 36 | | nameof(spdxDocument.NamespaceMap), (IList)spdxDocument.NamespaceMap); |
| | 3 | 37 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, spdxDocument, |
| | 3 | 38 | | nameof(spdxDocument.VerifiedUsing), (IList)spdxDocument.VerifiedUsing); |
| | 3 | 39 | | CheckAtLeastOneMemberOfType(LiteDomainComplianceFindingType.problem, spdxDocument, nameof(spdxDocument.Element), |
| | 3 | 40 | | (IList)spdxDocument.Element, typeof(Sbom)); |
| | 3 | 41 | | CheckContainsOnlyType(LiteDomainComplianceFindingType.recommendation, spdxDocument, |
| | 3 | 42 | | nameof(spdxDocument.Element), (IList)spdxDocument.Element, typeof(Sbom)); |
| | 3 | 43 | | } |
| | | 44 | | |
| | | 45 | | public void Visit(CreationInfo creationInfo) |
| | | 46 | | { |
| | 3 | 47 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, creationInfo, nameof(creationInfo.Created), |
| | 3 | 48 | | creationInfo.Created); |
| | 3 | 49 | | CheckMatchesPattern(LiteDomainComplianceFindingType.problem, creationInfo, nameof(creationInfo.SpecVersion), |
| | 3 | 50 | | creationInfo.SpecVersion, @"^3\.0\.\d+$"); |
| | 3 | 51 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, creationInfo, nameof(creationInfo.CreatedBy), |
| | 3 | 52 | | (IList)creationInfo.CreatedBy); |
| | 3 | 53 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, creationInfo, nameof(creationInfo.Comment), |
| | 3 | 54 | | creationInfo.Comment); |
| | 3 | 55 | | } |
| | | 56 | | |
| | | 57 | | public void Visit(Agent agent) |
| | | 58 | | { |
| | 5 | 59 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, agent, nameof(agent.CreationInfo), |
| | 5 | 60 | | agent.CreationInfo); |
| | 5 | 61 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, agent, nameof(agent.SpdxId), agent.SpdxId); |
| | 5 | 62 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, agent, nameof(agent.Name), agent.Name); |
| | 5 | 63 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, agent, nameof(agent.ExternalIdentifier), |
| | 5 | 64 | | (IList)agent.ExternalIdentifier); |
| | 5 | 65 | | } |
| | | 66 | | |
| | | 67 | | public void Visit(ExternalIdentifier externalIdentifier) |
| | | 68 | | { |
| | 2 | 69 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, externalIdentifier, |
| | 2 | 70 | | nameof(externalIdentifier.ExternalIdentifierType), externalIdentifier.ExternalIdentifierType); |
| | 2 | 71 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, externalIdentifier, |
| | 2 | 72 | | nameof(externalIdentifier.Identifier), externalIdentifier.Identifier); |
| | 2 | 73 | | } |
| | | 74 | | |
| | | 75 | | public void Visit(Hash hash) |
| | | 76 | | { |
| | 3 | 77 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, hash, nameof(hash.Algorithm), hash.Algorithm); |
| | 3 | 78 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, hash, nameof(hash.HashValue), hash.HashValue); |
| | 3 | 79 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, hash, nameof(hash.Comment), hash.Comment); |
| | 3 | 80 | | } |
| | | 81 | | |
| | | 82 | | public void Visit(NamespaceMap namespaceMap) |
| | | 83 | | { |
| | 1 | 84 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, namespaceMap, nameof(namespaceMap.Prefix), |
| | 1 | 85 | | namespaceMap.Prefix); |
| | 1 | 86 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, namespaceMap, nameof(namespaceMap.Namespace), |
| | 1 | 87 | | namespaceMap.Namespace); |
| | 1 | 88 | | } |
| | | 89 | | |
| | | 90 | | public void Visit(Relationship relationship) |
| | | 91 | | { |
| | 4 | 92 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, relationship, nameof(relationship.CreationInfo), |
| | 4 | 93 | | relationship.CreationInfo); |
| | 4 | 94 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, relationship, nameof(relationship.From), |
| | 4 | 95 | | relationship.From); |
| | 4 | 96 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, relationship, |
| | 4 | 97 | | nameof(relationship.RelationshipType), relationship.RelationshipType); |
| | 4 | 98 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, relationship, nameof(relationship.SpdxId), |
| | 4 | 99 | | relationship.SpdxId); |
| | 4 | 100 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, relationship, nameof(relationship.To), |
| | 4 | 101 | | relationship.To); |
| | 4 | 102 | | } |
| | | 103 | | |
| | | 104 | | public void Visit(LicenseExpression licenseExpression) |
| | | 105 | | { |
| | 3 | 106 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, licenseExpression, |
| | 3 | 107 | | nameof(licenseExpression.CreationInfo), licenseExpression.CreationInfo); |
| | 3 | 108 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, licenseExpression, |
| | 3 | 109 | | nameof(licenseExpression.LicenseExpressionText), licenseExpression.LicenseExpressionText); |
| | 3 | 110 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, licenseExpression, |
| | 3 | 111 | | nameof(licenseExpression.SpdxId), licenseExpression.SpdxId); |
| | 3 | 112 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, licenseExpression, |
| | 3 | 113 | | nameof(licenseExpression.LicenseListVersion), licenseExpression.LicenseListVersion); |
| | 3 | 114 | | } |
| | | 115 | | |
| | | 116 | | public void Visit(SimpleLicensingText simpleLicensingText) |
| | | 117 | | { |
| | 0 | 118 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, simpleLicensingText, |
| | 0 | 119 | | nameof(simpleLicensingText.CreationInfo), simpleLicensingText.CreationInfo); |
| | 0 | 120 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, simpleLicensingText, |
| | 0 | 121 | | nameof(simpleLicensingText.LicenseText), simpleLicensingText.LicenseText); |
| | 0 | 122 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, simpleLicensingText, |
| | 0 | 123 | | nameof(simpleLicensingText.SpdxId), simpleLicensingText.SpdxId); |
| | 0 | 124 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, simpleLicensingText, |
| | 0 | 125 | | nameof(simpleLicensingText.Comment), simpleLicensingText.Comment); |
| | 0 | 126 | | } |
| | | 127 | | |
| | | 128 | | public void Visit(Package package) |
| | | 129 | | { |
| | 2 | 130 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, package, nameof(package.CopyrightText), |
| | 2 | 131 | | package.CopyrightText); |
| | 2 | 132 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, package, nameof(package.CreationInfo), |
| | 2 | 133 | | package.CreationInfo); |
| | 2 | 134 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, package, nameof(package.Name), package.Name); |
| | 2 | 135 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, package, nameof(package.PackageVersion), |
| | 2 | 136 | | package.PackageVersion); |
| | 2 | 137 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, package, nameof(package.SpdxId), package.SpdxId); |
| | 2 | 138 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, package, nameof(package.SuppliedBy), |
| | 2 | 139 | | package.SuppliedBy); |
| | | 140 | | |
| | 2 | 141 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, package, nameof(package.AttributionText), |
| | 2 | 142 | | (IList)package.AttributionText); |
| | 2 | 143 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, package, nameof(package.BuiltTime), |
| | 2 | 144 | | package.BuiltTime); |
| | 2 | 145 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, package, nameof(package.Comment), |
| | 2 | 146 | | package.Comment); |
| | 2 | 147 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, package, nameof(package.DownloadLocation), |
| | 2 | 148 | | package.DownloadLocation); |
| | 2 | 149 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, package, nameof(package.HomePage), |
| | 2 | 150 | | package.HomePage); |
| | 2 | 151 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, package, nameof(package.ReleaseTime), |
| | 2 | 152 | | package.ReleaseTime); |
| | 2 | 153 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, package, nameof(package.SupportLevel), |
| | 2 | 154 | | (IList)package.SupportLevel); |
| | 2 | 155 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, package, nameof(package.ValidUntilTime), |
| | 2 | 156 | | package.ValidUntilTime); |
| | 2 | 157 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, package, nameof(package.VerifiedUsing), |
| | 2 | 158 | | (IList)package.VerifiedUsing); |
| | 2 | 159 | | CheckContainsOnlyType(LiteDomainComplianceFindingType.recommendation, package, nameof(package.VerifiedUsing), |
| | 2 | 160 | | (IList)package.VerifiedUsing, typeof(Hash)); |
| | | 161 | | |
| | 2 | 162 | | if (string.IsNullOrWhiteSpace(package.DownloadLocation) && |
| | 2 | 163 | | (package.PackageUrl is null || string.IsNullOrWhiteSpace(package.PackageUrl.ToString()))) |
| | | 164 | | { |
| | 0 | 165 | | Findings.Add(new LiteDomainComplianceFinding(LiteDomainComplianceFindingType.problem, package, |
| | 0 | 166 | | $"Either {nameof(package.DownloadLocation)} or {nameof(package.PackageUrl)} is required")); |
| | | 167 | | } |
| | | 168 | | |
| | 2 | 169 | | if (package.Catalog is null) |
| | | 170 | | { |
| | 0 | 171 | | throw new Spdx3Exception($"Cannot find catalog from Package '{package.SpdxId}'"); |
| | | 172 | | } |
| | | 173 | | |
| | 2 | 174 | | MustHaveExactlyOneRelationshipOfType(package, RelationshipType.hasDeclaredLicense); |
| | 2 | 175 | | MustHaveExactlyOneRelationshipOfType(package, RelationshipType.hasConcludedLicense); |
| | 2 | 176 | | } |
| | | 177 | | |
| | | 178 | | private void MustHaveExactlyOneRelationshipOfType(Package package, RelationshipType relType) |
| | | 179 | | { |
| | 4 | 180 | | var relationshipsOfType = package.Catalog.GetRelationshipsOfType(relType); |
| | | 181 | | |
| | 4 | 182 | | if (relationshipsOfType.Count() != 1) |
| | | 183 | | { |
| | 0 | 184 | | Findings.Add(new LiteDomainComplianceFinding(LiteDomainComplianceFindingType.problem, package, |
| | 0 | 185 | | $"Must have exactly one relationship from this package of type '{relType}'")); |
| | | 186 | | } |
| | | 187 | | else |
| | | 188 | | { |
| | 4 | 189 | | if (relationshipsOfType.First().To.Count() != 1) |
| | | 190 | | { |
| | 0 | 191 | | Findings.Add(new LiteDomainComplianceFinding(LiteDomainComplianceFindingType.problem, package, |
| | 0 | 192 | | $"{nameof(Relationship)} from {nameof(Package)} of type '{relType}' " + |
| | 0 | 193 | | $"must point to exactly one {nameof(AnyLicenseInfo)} object")); |
| | | 194 | | } |
| | | 195 | | else |
| | | 196 | | { |
| | 4 | 197 | | if (!relationshipsOfType.First().To.First().GetType().IsAssignableTo(typeof(AnyLicenseInfo))) |
| | | 198 | | { |
| | 0 | 199 | | Findings.Add(new LiteDomainComplianceFinding(LiteDomainComplianceFindingType.problem, package, |
| | 0 | 200 | | $"{nameof(Relationship)} from {nameof(Package)} of type '{RelationshipType.hasConcludedLicense}' |
| | 0 | 201 | | $"is not pointing to an {nameof(AnyLicenseInfo)} object")); |
| | | 202 | | } |
| | | 203 | | } |
| | | 204 | | } |
| | 4 | 205 | | } |
| | | 206 | | |
| | | 207 | | public void Visit(Sbom sbom) |
| | | 208 | | { |
| | 2 | 209 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, sbom, nameof(sbom.CreationInfo), |
| | 2 | 210 | | sbom.CreationInfo); |
| | 2 | 211 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, sbom, nameof(sbom.SpdxId), sbom.SpdxId); |
| | 2 | 212 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, sbom, nameof(sbom.Element), (IList)sbom.Element); |
| | 2 | 213 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.problem, sbom, nameof(sbom.RootElement), |
| | 2 | 214 | | (IList)sbom.RootElement); |
| | 2 | 215 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, sbom, nameof(sbom.Comment), sbom.Comment); |
| | 2 | 216 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, sbom, nameof(sbom.Name), sbom.Name); |
| | 2 | 217 | | CheckNotNullOrEmpty(LiteDomainComplianceFindingType.recommendation, sbom, nameof(sbom.VerifiedUsing), |
| | 2 | 218 | | (IList)sbom.VerifiedUsing); |
| | 2 | 219 | | CheckAtLeastOneMemberOfType(LiteDomainComplianceFindingType.problem, sbom, nameof(sbom.Element), |
| | 2 | 220 | | (IList)sbom.Element, typeof(Package)); |
| | 2 | 221 | | CheckContainsOnlyType(LiteDomainComplianceFindingType.recommendation, sbom, nameof(sbom.Element), |
| | 2 | 222 | | (IList)sbom.Element, typeof(Package)); |
| | 2 | 223 | | } |
| | | 224 | | |
| | | 225 | | private void CheckNotNullOrEmpty(LiteDomainComplianceFindingType findingType, BaseModelClass obj, |
| | | 226 | | string propertyName, IList? listVal) |
| | | 227 | | { |
| | 32 | 228 | | var verb = findingType == LiteDomainComplianceFindingType.problem ? "requires" : "should have"; |
| | | 229 | | |
| | 32 | 230 | | if (listVal is null || listVal.Count < 1) |
| | | 231 | | { |
| | 13 | 232 | | Findings.Add(new LiteDomainComplianceFinding(findingType, obj, propertyName, |
| | 13 | 233 | | $"List {verb} at least one item")); |
| | | 234 | | } |
| | 32 | 235 | | } |
| | | 236 | | |
| | | 237 | | |
| | | 238 | | private void CheckAtLeastOneMemberOfType(LiteDomainComplianceFindingType findingType, BaseModelClass obj, |
| | | 239 | | string propertyName, IList? listVal, Type requiredType) |
| | | 240 | | { |
| | 5 | 241 | | var verb = findingType == LiteDomainComplianceFindingType.problem ? "requires" : "should have"; |
| | | 242 | | |
| | 5 | 243 | | if (listVal is null || listVal.Count < 1) |
| | | 244 | | { |
| | 1 | 245 | | Findings.Add(new LiteDomainComplianceFinding(findingType, obj, propertyName, |
| | 1 | 246 | | $"List {verb} at least one item of type {requiredType.Name}")); |
| | 1 | 247 | | return; |
| | | 248 | | } |
| | | 249 | | |
| | 12 | 250 | | foreach (var member in listVal) |
| | | 251 | | { |
| | 4 | 252 | | if (member.GetType() == requiredType) |
| | | 253 | | { |
| | 4 | 254 | | return; |
| | | 255 | | } |
| | | 256 | | } |
| | | 257 | | |
| | 0 | 258 | | Findings.Add(new LiteDomainComplianceFinding(findingType, obj, propertyName, |
| | 0 | 259 | | $"List {verb} at least one item of type {requiredType.Name}")); |
| | 4 | 260 | | } |
| | | 261 | | |
| | | 262 | | private void CheckContainsOnlyType(LiteDomainComplianceFindingType findingType, BaseModelClass obj, |
| | | 263 | | string propertyName, IList? listVal, Type desiredType) |
| | | 264 | | { |
| | 7 | 265 | | var verb = findingType == LiteDomainComplianceFindingType.problem ? "must" : "should"; |
| | | 266 | | |
| | 7 | 267 | | if (listVal is null) |
| | | 268 | | { |
| | 0 | 269 | | return; |
| | | 270 | | } |
| | | 271 | | |
| | 24 | 272 | | foreach (var member in listVal) |
| | | 273 | | { |
| | 5 | 274 | | if (member.GetType() != desiredType) |
| | | 275 | | { |
| | 0 | 276 | | Findings.Add(new LiteDomainComplianceFinding(findingType, obj, propertyName, |
| | 0 | 277 | | $"List {verb} contain only items of type {desiredType.Name}")); |
| | 0 | 278 | | return; |
| | | 279 | | } |
| | | 280 | | } |
| | 7 | 281 | | } |
| | | 282 | | |
| | | 283 | | private void CheckMatchesPattern(LiteDomainComplianceFindingType findingType, BaseModelClass obj, |
| | | 284 | | string propertyName, string? strVal, string regexPattern) |
| | | 285 | | { |
| | 3 | 286 | | if (strVal is not null && !Regex.Match(strVal, regexPattern).Success) |
| | | 287 | | { |
| | 0 | 288 | | Findings.Add( |
| | 0 | 289 | | new LiteDomainComplianceFinding(findingType, obj, propertyName, $"Value '{strVal}' is invalid")); |
| | | 290 | | } |
| | 3 | 291 | | } |
| | | 292 | | |
| | | 293 | | private void CheckNotNullOrEmpty(LiteDomainComplianceFindingType findingType, BaseModelClass obj, |
| | | 294 | | string propertyName, object? objVal) |
| | | 295 | | { |
| | 93 | 296 | | var verb = findingType == LiteDomainComplianceFindingType.problem ? "required" : "recommended"; |
| | | 297 | | |
| | 93 | 298 | | if (objVal is null) |
| | | 299 | | { |
| | 16 | 300 | | Findings.Add(new LiteDomainComplianceFinding(findingType, obj, propertyName, $"Value {verb}")); |
| | | 301 | | } |
| | 93 | 302 | | } |
| | | 303 | | |
| | | 304 | | private void CheckNotNullOrEmpty(LiteDomainComplianceFindingType findingType, BaseModelClass obj, string fieldName, |
| | | 305 | | Uri? uriVal) |
| | | 306 | | { |
| | 22 | 307 | | if (uriVal is null) |
| | | 308 | | { |
| | 1 | 309 | | var verb = findingType == LiteDomainComplianceFindingType.problem ? "required" : "recommended"; |
| | 1 | 310 | | Findings.Add(new LiteDomainComplianceFinding(findingType, obj, fieldName, $"Value {verb}")); |
| | 1 | 311 | | return; |
| | | 312 | | } |
| | | 313 | | |
| | 21 | 314 | | if (!uriVal.IsWellFormedOriginalString()) |
| | | 315 | | { |
| | 0 | 316 | | Findings.Add(new LiteDomainComplianceFinding(findingType, obj, fieldName, |
| | 0 | 317 | | $"Value of '{uriVal.ToString()}' is not a well-formed URI")); |
| | | 318 | | } |
| | 21 | 319 | | } |
| | | 320 | | } |