dependabot-nuget 0.310.0 → 0.312.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -1
  3. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/DotNetPackageCorrelation.Test.csproj +1 -1
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +2 -2
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/NuGetUpdater.Cli.Test.csproj +1 -1
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +7 -0
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/BadResponseException.cs +12 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +121 -9
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ClosePullRequest.cs +13 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreatePullRequest.cs +20 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +1 -2
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +26 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/MessageBase.cs +1 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +12 -2
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatePullRequest.cs +16 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +19 -50
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +33 -7
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +49 -13
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +41 -11
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +13 -1
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +1 -2
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +6 -25
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +13 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/VersionFinderTests.cs +74 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +1 -1
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +3 -9
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +3 -4
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +58 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +37 -2
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +116 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +231 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +32 -8
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +714 -11
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +44 -13
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs +2 -39
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs +9 -5
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackageReferenceUpdaterTests.cs +99 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateOperationBaseTests.cs +3 -3
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +1 -13
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/LoggerTests.cs +0 -1
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +66 -8
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +1 -1
  44. data/helpers/lib/NuGetUpdater/global.json +1 -1
  45. metadata +30 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60d194f7c1aa9c0a61df000bad1d3030110a66a73cb86615c97fe0a3d053cc56
4
- data.tar.gz: d87118a0fb76c9b571b27ec5a66a3ae65d808e17daa9d68cb734962ee3aa5fd7
3
+ metadata.gz: acb9d775c29330f4cae905aa23b7ad9c36f0ed161d8cc1c2fbfc7391fb52646b
4
+ data.tar.gz: 97b60783a35914969fe10efc5235a91d29a470faf40e4520cf331bf6bafe21f1
5
5
  SHA512:
6
- metadata.gz: 6d9fcc54e90b7c3ea93eedab675ce512a989176b02c676d442f9b9edcbbb5653dd9bacc777d77c52f1061ede4048523dfc9693a50c01ce089c5205dcf1cdfddb
7
- data.tar.gz: 12abc6e3b3c04933d6d341ea9532d9dbae1cc121310c1f4252e0f7d9f2776861c1b3cb696d813a1c37717363c219c4196743665001ecf7ade03d8bf40aaf58bc
6
+ metadata.gz: d1d117e611b42497834c3276f08e35e3a70a3b2d0f960ac3d666dde3c12eb32697f911e0af3409e76500977f4c1db835c4979a85879e86bbd00e0ec0df4cc34c
7
+ data.tar.gz: b3f8f76bc8be51fc23493c1660cb41e5f6b9c10721941bd98c72c49652c807bb320def722cdc3c08d822f2cd2ed0dbe993b75cd6d43d0f1593de6890a753af32
@@ -38,7 +38,7 @@
38
38
  <PackageVersion Include="System.Text.Json" Version="9.0.3" />
39
39
  <PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
40
40
  <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.3" />
41
- <PackageVersion Include="xunit" Version="2.9.3" />
41
+ <PackageVersion Include="xunit.v3" Version="2.0.1" />
42
42
  <PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
43
43
  </ItemGroup>
44
44
  </Project>
@@ -7,7 +7,7 @@
7
7
 
8
8
  <ItemGroup>
9
9
  <PackageReference Include="Microsoft.NET.Test.Sdk" />
10
- <PackageReference Include="xunit" />
10
+ <PackageReference Include="xunit.v3" />
11
11
  <PackageReference Include="xunit.runner.visualstudio" />
12
12
  </ItemGroup>
13
13
 
@@ -58,7 +58,7 @@ public partial class EntryPointTests
58
58
  );
59
59
  }
60
60
 
61
- private static async Task RunAsync(TestFile[] files, Job job, string[] expectedUrls, MockNuGetPackage[]? packages = null)
61
+ private static async Task RunAsync(TestFile[] files, Job job, string[] expectedUrls, MockNuGetPackage[]? packages = null, string? repoContentsPath = null)
62
62
  {
63
63
  using var tempDirectory = new TemporaryDirectory();
64
64
 
@@ -90,7 +90,7 @@ public partial class EntryPointTests
90
90
  "--job-path",
91
91
  jobPath,
92
92
  "--repo-contents-path",
93
- tempDirectory.DirectoryPath,
93
+ repoContentsPath ?? tempDirectory.DirectoryPath,
94
94
  "--api-url",
95
95
  http.BaseUrl,
96
96
  "--job-id",
@@ -13,7 +13,7 @@
13
13
 
14
14
  <ItemGroup>
15
15
  <PackageReference Include="Microsoft.NET.Test.Sdk" />
16
- <PackageReference Include="xunit" />
16
+ <PackageReference Include="xunit.v3" />
17
17
  <PackageReference Include="xunit.runner.visualstudio" />
18
18
  </ItemGroup>
19
19
 
@@ -1,5 +1,7 @@
1
1
  using System.Collections.Immutable;
2
2
 
3
+ using Newtonsoft.Json;
4
+
3
5
  using NuGet.Common;
4
6
  using NuGet.Configuration;
5
7
  using NuGet.Frameworks;
@@ -98,6 +100,11 @@ internal static class VersionFinder
98
100
  // if anything goes wrong here, the package source obviously doesn't contain the requested package
99
101
  continue;
100
102
  }
103
+ catch (JsonReaderException ex)
104
+ {
105
+ // unable to parse server response
106
+ throw new BadResponseException(ex.Message, source.Source);
107
+ }
101
108
 
102
109
  var feedVersions = (await feed.GetVersions(
103
110
  packageId,
@@ -0,0 +1,12 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ internal class BadResponseException : Exception
4
+ {
5
+ public string Uri { get; }
6
+
7
+ public BadResponseException(string message, string uri)
8
+ : base(message)
9
+ {
10
+ Uri = uri;
11
+ }
12
+ }
@@ -1,9 +1,11 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Text.Json;
2
3
  using System.Xml.Linq;
3
4
  using System.Xml.XPath;
4
5
 
5
6
  using Microsoft.Build.Logging.StructuredLogger;
6
7
 
8
+ using NuGet.Frameworks;
7
9
  using NuGet.Versioning;
8
10
 
9
11
  using NuGetUpdater.Core.Utilities;
@@ -39,6 +41,8 @@ internal static class SdkProjectDiscovery
39
41
  // these packages are resolved during restore, but aren't really updatable and shouldn't be reported as dependencies
40
42
  private static readonly HashSet<string> NonReportedPackgeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
41
43
  {
44
+ "Microsoft.NETCore.Platforms",
45
+ "Microsoft.NETCore.Targets",
42
46
  "NETStandard.Library"
43
47
  };
44
48
 
@@ -84,6 +88,9 @@ internal static class SdkProjectDiscovery
84
88
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesReplacedBySdkPerProject = new(PathComparer.Instance);
85
89
  // projectPath tfm packageName packageVersion
86
90
 
91
+ Dictionary<string, Dictionary<string, HashSet<string>>> packageDependencies = new(PathComparer.Instance);
92
+ // projectPath tfm packageNames
93
+
87
94
  Dictionary<string, Dictionary<string, string>> resolvedProperties = new(PathComparer.Instance);
88
95
  // projectPath propertyName propertyValue
89
96
 
@@ -228,6 +235,28 @@ internal static class SdkProjectDiscovery
228
235
  }
229
236
  }
230
237
  }
238
+
239
+ // track all referenced projects in case they have no assemblies and can't be otherwise reported
240
+ if (addItem.Name.Equals("PackageDependencies", StringComparison.OrdinalIgnoreCase))
241
+ {
242
+ var projectEvaluation = GetNearestProjectEvaluation(node);
243
+ if (projectEvaluation is not null)
244
+ {
245
+ var specificPackageDeps = packageDependencies.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
246
+ var tfm = GetPropertyValueFromProjectEvaluation(projectEvaluation, "TargetFramework");
247
+ if (tfm is not null)
248
+ {
249
+ var packagesByTfm = specificPackageDeps.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
250
+ foreach (var package in addItem.Children.OfType<Item>())
251
+ {
252
+ if (!NonReportedPackgeNames.Contains(package.Name))
253
+ {
254
+ packagesByTfm.Add(package.Name);
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
231
260
  }
232
261
  break;
233
262
  case Target target when target.Name == "_HandlePackageFileConflicts":
@@ -330,7 +359,7 @@ internal static class SdkProjectDiscovery
330
359
  }
331
360
 
332
361
  // and done
333
- var projectDiscoveryResults = BuildResults(
362
+ var projectDiscoveryResults = await BuildResults(
334
363
  repoRootPath,
335
364
  workspacePath,
336
365
  packagesPerProject,
@@ -338,6 +367,7 @@ internal static class SdkProjectDiscovery
338
367
  packagesReplacedBySdkPerProject,
339
368
  topLevelPackagesPerProject,
340
369
  resolvedProperties,
370
+ packageDependencies,
341
371
  referencedProjects,
342
372
  importedFiles,
343
373
  additionalFiles
@@ -345,7 +375,7 @@ internal static class SdkProjectDiscovery
345
375
  return projectDiscoveryResults;
346
376
  }
347
377
 
348
- private static ImmutableArray<ProjectDiscoveryResult> BuildResults(
378
+ private static async Task<ImmutableArray<ProjectDiscoveryResult>> BuildResults(
349
379
  string repoRootPath,
350
380
  string workspacePath,
351
381
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject,
@@ -353,13 +383,14 @@ internal static class SdkProjectDiscovery
353
383
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesReplacedBySdkPerProject,
354
384
  Dictionary<string, Dictionary<string, HashSet<string>>> topLevelPackagesPerProject,
355
385
  Dictionary<string, Dictionary<string, string>> resolvedProperties,
386
+ Dictionary<string, Dictionary<string, HashSet<string>>> packageDependencies,
356
387
  Dictionary<string, HashSet<string>> referencedProjects,
357
388
  Dictionary<string, HashSet<string>> importedFiles,
358
389
  Dictionary<string, HashSet<string>> additionalFiles
359
390
  )
360
391
  {
361
392
  var projectDiscoveryResults = new List<ProjectDiscoveryResult>();
362
- foreach (var projectPath in packagesPerProject.Keys.OrderBy(p => p)) //packagesPerProject.Keys.OrderBy(p => p).Select(projectPath =>
393
+ foreach (var projectPath in packagesPerProject.Keys.OrderBy(p => p))
363
394
  {
364
395
  // gather some project-level information
365
396
  var packagesByTfm = packagesPerProject[projectPath];
@@ -400,18 +431,99 @@ internal static class SdkProjectDiscovery
400
431
  .SelectMany(kvp => kvp.Value)
401
432
  .ToHashSet(StringComparer.OrdinalIgnoreCase);
402
433
 
434
+ var propertiesForProject = resolvedProperties.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase));
435
+ var assetsJson = new Lazy<JsonElement?>(() =>
436
+ {
437
+ if (propertiesForProject.TryGetValue("ProjectAssetsFile", out var assetsFilePath))
438
+ {
439
+ var assetsContent = File.ReadAllText(assetsFilePath);
440
+ var assets = JsonDocument.Parse(assetsContent).RootElement;
441
+ return assets;
442
+ }
443
+
444
+ return null;
445
+ });
446
+
403
447
  // create dependencies
404
448
  var tfms = packagesByTfm.Keys.OrderBy(tfm => tfm).ToImmutableArray();
405
- var dependencies = tfms.SelectMany(tfm =>
449
+ var groupedDependencies = new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase);
450
+ foreach (var tfm in tfms)
406
451
  {
407
- return packagesByTfm[tfm].Keys.OrderBy(p => p).Select(packageName =>
452
+ var parsedTfm = NuGetFramework.Parse(tfm);
453
+ var packages = packagesByTfm[tfm];
454
+
455
+ // augment with any packages that might not have reported assemblies
456
+ var assetsPackageVersions = new Lazy<Dictionary<string, string>>(() =>
457
+ {
458
+ var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
459
+ if (assetsJson.Value is { } assets &&
460
+ assets.TryGetProperty("targets", out var tfmObjects))
461
+ {
462
+ foreach (var tfmObject in tfmObjects.EnumerateObject())
463
+ {
464
+ // TFM might have a RID suffix after a slash that we can't parse
465
+ var tfmParts = tfmObject.Name.Split('/');
466
+ var reportedTargetFramework = NuGetFramework.Parse(tfmParts[0]);
467
+ if (reportedTargetFramework == parsedTfm)
468
+ {
469
+ foreach (var packageObject in tfmObject.Value.EnumerateObject())
470
+ {
471
+ var parts = packageObject.Name.Split('/');
472
+ if (parts.Length == 2)
473
+ {
474
+ var packageName = parts[0];
475
+ var packageVersion = parts[1];
476
+ result[packageName] = packageVersion;
477
+ }
478
+ }
479
+ }
480
+ }
481
+ }
482
+
483
+ return result;
484
+ });
485
+ var packageDepsForProject = packageDependencies.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase));
486
+ var packageDepsForTfm = packageDepsForProject.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
487
+ foreach (var packageDepName in packageDepsForTfm)
488
+ {
489
+ if (packages.ContainsKey(packageDepName))
490
+ {
491
+ // we already know about this
492
+ continue;
493
+ }
494
+
495
+ // otherwise find the corresponding version through project.assets.json
496
+ if (assetsPackageVersions.Value.TryGetValue(packageDepName, out var packageDepVersion))
497
+ {
498
+ packages[packageDepName] = packageDepVersion;
499
+ }
500
+ }
501
+
502
+ foreach (var package in packages)
408
503
  {
409
- var packageVersion = packagesByTfm[tfm][packageName]!;
504
+ var packageName = package.Key;
505
+ var packageVersion = package.Value;
410
506
  var isTopLevel = topLevelPackageNames.Contains(packageName);
411
507
  var dependencyType = isTopLevel ? DependencyType.PackageReference : DependencyType.Unknown;
412
- return new Dependency(packageName, packageVersion, dependencyType, TargetFrameworks: [tfm], IsDirect: isTopLevel, IsTransitive: !isTopLevel);
413
- });
414
- }).ToImmutableArray();
508
+ var combinedTfms = new HashSet<string>([tfm], StringComparer.OrdinalIgnoreCase);
509
+ if (groupedDependencies.TryGetValue(packageName, out var existingDependency) &&
510
+ existingDependency.Version == packageVersion &&
511
+ existingDependency.Type == dependencyType &&
512
+ existingDependency.TargetFrameworks is not null)
513
+ {
514
+ // same dependency, combine tfms
515
+ combinedTfms.AddRange(existingDependency.TargetFrameworks);
516
+ }
517
+
518
+ var normalizedTfms = combinedTfms.OrderBy(t => t).ToImmutableArray();
519
+ groupedDependencies[package.Key] = new Dependency(packageName, packageVersion, dependencyType, TargetFrameworks: normalizedTfms, IsDirect: isTopLevel, IsTransitive: !isTopLevel);
520
+ }
521
+ }
522
+
523
+ var dependencies = groupedDependencies.Values
524
+ .OrderBy(d => d.Name)
525
+ .ThenBy(d => d.Version)
526
+ .ToImmutableArray();
415
527
 
416
528
  // others
417
529
  var properties = resolvedProperties[projectPath]
@@ -1,4 +1,5 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Text;
2
3
  using System.Text.Json.Serialization;
3
4
 
4
5
  namespace NuGetUpdater.Core.Run.ApiModel;
@@ -9,4 +10,16 @@ public sealed record ClosePullRequest : MessageBase
9
10
  public required ImmutableArray<string> DependencyNames { get; init; }
10
11
 
11
12
  public string Reason { get; init; } = "up_to_date";
13
+
14
+ public override string GetReport()
15
+ {
16
+ var report = new StringBuilder();
17
+ report.AppendLine($"{nameof(ClosePullRequest)}: {Reason}");
18
+ foreach (var dependencyName in DependencyNames)
19
+ {
20
+ report.AppendLine($"- {dependencyName}");
21
+ }
22
+
23
+ return report.ToString().Trim();
24
+ }
12
25
  }
@@ -1,5 +1,8 @@
1
+ using System.Text;
1
2
  using System.Text.Json.Serialization;
2
3
 
4
+ using NuGet.Versioning;
5
+
3
6
  namespace NuGetUpdater.Core.Run.ApiModel;
4
7
 
5
8
  public sealed record CreatePullRequest : MessageBase
@@ -15,4 +18,21 @@ public sealed record CreatePullRequest : MessageBase
15
18
  public required string PrTitle { get; init; }
16
19
  [JsonPropertyName("pr-body")]
17
20
  public required string PrBody { get; init; }
21
+
22
+ public override string GetReport()
23
+ {
24
+ var dependencyNames = Dependencies
25
+ .OrderBy(d => d.Name, StringComparer.OrdinalIgnoreCase)
26
+ .ThenBy(d => NuGetVersion.Parse(d.Version!))
27
+ .Select(d => $"{d.Name}/{d.Version}")
28
+ .ToArray();
29
+ var report = new StringBuilder();
30
+ report.AppendLine(nameof(CreatePullRequest));
31
+ foreach (var d in dependencyNames)
32
+ {
33
+ report.AppendLine($"- {d}");
34
+ }
35
+
36
+ return report.ToString().Trim();
37
+ }
18
38
  }
@@ -2,7 +2,6 @@ using System.Collections.Immutable;
2
2
  using System.Text.Json;
3
3
  using System.Text.Json.Serialization;
4
4
 
5
- using NuGet.Credentials;
6
5
  using NuGet.Versioning;
7
6
 
8
7
  namespace NuGetUpdater.Core.Run.ApiModel;
@@ -32,7 +31,7 @@ public sealed record Job
32
31
  public bool RejectExternalCode { get; init; } = false;
33
32
  public bool RepoPrivate { get; init; } = false;
34
33
  public CommitOptions? CommitMessageOptions { get; init; } = null;
35
- public ImmutableArray<Dictionary<string, string>>? CredentialsMetadata { get; init; } = null;
34
+ public ImmutableArray<Dictionary<string, object>>? CredentialsMetadata { get; init; } = null;
36
35
  public int MaxUpdaterRunTime { get; init; } = 0;
37
36
 
38
37
  public IEnumerable<string> GetAllDirectories()
@@ -1,4 +1,5 @@
1
1
  using System.Net;
2
+ using System.Text;
2
3
  using System.Text.Json.Serialization;
3
4
 
4
5
  using Microsoft.Build.Exceptions;
@@ -20,11 +21,36 @@ public abstract record JobErrorBase : MessageBase
20
21
  [JsonPropertyName("error-details")]
21
22
  public Dictionary<string, object> Details { get; init; } = new();
22
23
 
24
+ public override string GetReport()
25
+ {
26
+ var report = new StringBuilder();
27
+ report.AppendLine($"Error type: {Type}");
28
+ foreach (var (key, value) in Details)
29
+ {
30
+ if (this is UnknownError && key == "error-backtrace")
31
+ {
32
+ // there's nothing meaningful in this field
33
+ continue;
34
+ }
35
+
36
+ var valueString = value.ToString();
37
+ if (value is IEnumerable<string> strings)
38
+ {
39
+ valueString = string.Join(", ", strings);
40
+ }
41
+
42
+ report.AppendLine($"- {key}: {valueString}");
43
+ }
44
+
45
+ return report.ToString().Trim();
46
+ }
47
+
23
48
  public static JobErrorBase ErrorFromException(Exception ex, string jobId, string currentDirectory)
24
49
  {
25
50
  return ex switch
26
51
  {
27
52
  BadRequirementException badRequirement => new BadRequirement(badRequirement.Message),
53
+ BadResponseException badResponse => new PrivateSourceBadResponse([badResponse.Uri]),
28
54
  DependencyNotFoundException dependencyNotFound => new DependencyNotFound(string.Join(", ", dependencyNotFound.Dependencies)),
29
55
  HttpRequestException httpRequest => httpRequest.StatusCode switch
30
56
  {
@@ -2,4 +2,5 @@ namespace NuGetUpdater.Core.Run.ApiModel;
2
2
 
3
3
  public abstract record MessageBase
4
4
  {
5
+ public abstract string GetReport();
5
6
  }
@@ -1,13 +1,23 @@
1
+ using System.Text.Json.Serialization;
2
+
1
3
  namespace NuGetUpdater.Core.Run.ApiModel;
2
4
 
3
5
  public record UnknownError : JobErrorBase
4
6
  {
7
+ [JsonIgnore]
8
+ public Exception Exception { get; init; }
9
+
5
10
  public UnknownError(Exception ex, string jobId)
6
11
  : base("unknown_error")
7
12
  {
13
+ Exception = ex;
14
+
15
+ // The following object is parsed by the server and the `error-backtrace` property is expected to be a Ruby
16
+ // stacktrace. Since we're not in Ruby we can set an empty string there and append the .NET stacktrace to
17
+ // the message.
8
18
  Details["error-class"] = ex.GetType().Name;
9
- Details["error-message"] = ex.Message;
10
- Details["error-backtrace"] = ex.StackTrace ?? "<unknown>";
19
+ Details["error-message"] = ex.ToString();
20
+ Details["error-backtrace"] = "";
11
21
  Details["package-manager"] = "nuget";
12
22
  Details["job-id"] = jobId;
13
23
  }
@@ -1,4 +1,5 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Text;
2
3
  using System.Text.Json.Serialization;
3
4
 
4
5
  namespace NuGetUpdater.Core.Run.ApiModel;
@@ -25,4 +26,19 @@ public sealed record UpdatePullRequest : MessageBase
25
26
 
26
27
  [JsonPropertyName("dependency-group")]
27
28
  public required string? DependencyGroup { get; init; }
29
+
30
+ public override string GetReport()
31
+ {
32
+ var dependencyNames = DependencyNames
33
+ .Order(StringComparer.OrdinalIgnoreCase)
34
+ .ToArray();
35
+ var report = new StringBuilder();
36
+ report.AppendLine(nameof(UpdatePullRequest));
37
+ foreach (var d in dependencyNames)
38
+ {
39
+ report.AppendLine($"- {d}");
40
+ }
41
+
42
+ return report.ToString().Trim();
43
+ }
28
44
  }
@@ -2,8 +2,6 @@ using System.Text;
2
2
  using System.Text.Json;
3
3
  using System.Text.Json.Serialization;
4
4
 
5
- using NuGetUpdater.Core.Run.ApiModel;
6
-
7
5
  namespace NuGetUpdater.Core.Run;
8
6
 
9
7
  public class HttpApiHandler : IApiHandler
@@ -25,39 +23,27 @@ public class HttpApiHandler : IApiHandler
25
23
  _jobId = jobId;
26
24
  }
27
25
 
28
- public async Task RecordUpdateJobError(JobErrorBase error)
29
- {
30
- await PostAsJson("record_update_job_error", error);
31
- }
32
-
33
- public async Task UpdateDependencyList(UpdatedDependencyList updatedDependencyList)
26
+ public async Task SendAsync(string endpoint, object body, string method)
34
27
  {
35
- await PostAsJson("update_dependency_list", updatedDependencyList);
36
- }
37
-
38
- public async Task IncrementMetric(IncrementMetric incrementMetric)
39
- {
40
- await PostAsJson("increment_metric", incrementMetric);
41
- }
42
-
43
- public async Task CreatePullRequest(CreatePullRequest createPullRequest)
44
- {
45
- await PostAsJson("create_pull_request", createPullRequest);
46
- }
47
-
48
- public async Task ClosePullRequest(ClosePullRequest closePullRequest)
49
- {
50
- await PostAsJson("close_pull_request", closePullRequest);
51
- }
52
-
53
- public async Task UpdatePullRequest(UpdatePullRequest updatePullRequest)
54
- {
55
- await PostAsJson("update_pull_request", updatePullRequest);
56
- }
28
+ var uri = $"{_apiUrl}/update_jobs/{_jobId}/{endpoint}";
29
+ var payload = Serialize(body);
30
+ var content = new StringContent(payload, Encoding.UTF8, "application/json");
31
+ var httpMethod = new HttpMethod(method);
32
+ var message = new HttpRequestMessage(httpMethod, uri)
33
+ {
34
+ Content = content
35
+ };
36
+ var response = await HttpClient.SendAsync(message);
37
+ if (!response.IsSuccessStatusCode)
38
+ {
39
+ var responseContent = await response.Content.ReadAsStringAsync();
40
+ if (!string.IsNullOrEmpty(responseContent))
41
+ {
42
+ responseContent = string.Concat(": ", responseContent);
43
+ }
57
44
 
58
- public async Task MarkAsProcessed(MarkAsProcessed markAsProcessed)
59
- {
60
- await PatchAsJson("mark_as_processed", markAsProcessed);
45
+ throw new HttpRequestException(message: $"{(int)response.StatusCode} ({response.StatusCode}){responseContent}", inner: null, statusCode: response.StatusCode);
46
+ }
61
47
  }
62
48
 
63
49
  internal static string Serialize(object body)
@@ -69,21 +55,4 @@ public class HttpApiHandler : IApiHandler
69
55
  var payload = JsonSerializer.Serialize(wrappedBody, SerializerOptions);
70
56
  return payload;
71
57
  }
72
-
73
- private Task PostAsJson(string endpoint, object body) => SendAsJson(endpoint, body, "POST");
74
- private Task PatchAsJson(string endpoint, object body) => SendAsJson(endpoint, body, "PATCH");
75
-
76
- private async Task SendAsJson(string endpoint, object body, string method)
77
- {
78
- var uri = $"{_apiUrl}/update_jobs/{_jobId}/{endpoint}";
79
- var payload = Serialize(body);
80
- var content = new StringContent(payload, Encoding.UTF8, "application/json");
81
- var httpMethod = new HttpMethod(method);
82
- var message = new HttpRequestMessage(httpMethod, uri)
83
- {
84
- Content = content
85
- };
86
- var response = await HttpClient.SendAsync(message);
87
- var _ = response.EnsureSuccessStatusCode();
88
- }
89
58
  }
@@ -4,11 +4,37 @@ namespace NuGetUpdater.Core.Run;
4
4
 
5
5
  public interface IApiHandler
6
6
  {
7
- Task RecordUpdateJobError(JobErrorBase error);
8
- Task UpdateDependencyList(UpdatedDependencyList updatedDependencyList);
9
- Task IncrementMetric(IncrementMetric incrementMetric);
10
- Task CreatePullRequest(CreatePullRequest createPullRequest);
11
- Task ClosePullRequest(ClosePullRequest closePullRequest);
12
- Task UpdatePullRequest(UpdatePullRequest updatePullRequest);
13
- Task MarkAsProcessed(MarkAsProcessed markAsProcessed);
7
+ Task SendAsync(string endpoint, object body, string method);
8
+ }
9
+
10
+ public static class IApiHandlerExtensions
11
+ {
12
+ public static async Task RecordUpdateJobError(this IApiHandler handler, JobErrorBase error)
13
+ {
14
+ await handler.PostAsJson("record_update_job_error", error);
15
+ if (error is UnknownError unknown)
16
+ {
17
+ await handler.PostAsJson("record_update_job_unknown_error", error);
18
+ var increment = new IncrementMetric()
19
+ {
20
+ Metric = "updater.update_job_unknown_error",
21
+ Tags =
22
+ {
23
+ ["package_manager"] = "nuget",
24
+ ["class_name"] = unknown.Exception.GetType().Name
25
+ },
26
+ };
27
+ await handler.IncrementMetric(increment);
28
+ }
29
+ }
30
+
31
+ public static Task UpdateDependencyList(this IApiHandler handler, UpdatedDependencyList updatedDependencyList) => handler.PostAsJson("update_dependency_list", updatedDependencyList);
32
+ public static Task IncrementMetric(this IApiHandler handler, IncrementMetric incrementMetric) => handler.PostAsJson("increment_metric", incrementMetric);
33
+ public static Task CreatePullRequest(this IApiHandler handler, CreatePullRequest createPullRequest) => handler.PostAsJson("create_pull_request", createPullRequest);
34
+ public static Task ClosePullRequest(this IApiHandler handler, ClosePullRequest closePullRequest) => handler.PostAsJson("close_pull_request", closePullRequest);
35
+ public static Task UpdatePullRequest(this IApiHandler handler, UpdatePullRequest updatePullRequest) => handler.PostAsJson("update_pull_request", updatePullRequest);
36
+ public static Task MarkAsProcessed(this IApiHandler handler, MarkAsProcessed markAsProcessed) => handler.PatchAsJson("mark_as_processed", markAsProcessed);
37
+
38
+ private static Task PostAsJson(this IApiHandler handler, string endpoint, object body) => handler.SendAsync(endpoint, body, "POST");
39
+ private static Task PatchAsJson(this IApiHandler handler, string endpoint, object body) => handler.SendAsync(endpoint, body, "PATCH");
14
40
  }