diff --git a/source/Reloaded.Mod.Launcher.Lib/Update.cs b/source/Reloaded.Mod.Launcher.Lib/Update.cs index 98a086ca..42021d8a 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Update.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Update.cs @@ -264,15 +264,32 @@ public static async Task GetMissingDependenciesToDow // Get Dependencies var resolver = DependencyResolverFactory.GetInstance(IoC.Get()); - var results = new List>(); + var taskToDependencyMap = new Dictionary, string>(); foreach (var dependencyItem in missingDeps.Items) foreach (var dependency in dependencyItem.Dependencies) - results.Add(resolver.ResolveAsync(dependency, dependencyItem.Mod.PluginData, token)); + { + var task = resolver.ResolveAsync(dependency, dependencyItem.Mod.PluginData, token); + taskToDependencyMap[task] = dependency; + } - await Task.WhenAll(results); + // Handle each result individually to avoid stopping on failures + var resolveResults = new List(); + foreach (var kvp in taskToDependencyMap) + { + try + { + var taskResult = await kvp.Key; + resolveResults.Add(taskResult); + } + catch (Exception ex) + { + // Create error result for unexpected exceptions that weren't caught by resolvers + resolveResults.Add(ModDependencyResolveResult.FromError(kvp.Value, ex, "UnknownResolver")); + } + } // Merge Results - var result = ModDependencyResolveResult.Combine(results.Select(x => x.Result));; + var result = ModDependencyResolveResult.Combine(resolveResults); if (result.NotFoundDependencies.Count <= 0) return result; diff --git a/source/Reloaded.Mod.Loader.Tests/Reloaded.Mod.Loader.Tests.csproj b/source/Reloaded.Mod.Loader.Tests/Reloaded.Mod.Loader.Tests.csproj index 04785267..0bea4f4a 100644 --- a/source/Reloaded.Mod.Loader.Tests/Reloaded.Mod.Loader.Tests.csproj +++ b/source/Reloaded.Mod.Loader.Tests/Reloaded.Mod.Loader.Tests.csproj @@ -8,6 +8,7 @@ ..\Output\Tests\ disable $(NoWarn);NU1605;NU1701;MSB3270 + true diff --git a/source/Reloaded.Mod.Loader.Tests/Update/Providers/GameBanana/GameBananaDependencyResolverTests.cs b/source/Reloaded.Mod.Loader.Tests/Update/Providers/GameBanana/GameBananaDependencyResolverTests.cs index 5e358d4a..1e8b8e08 100644 --- a/source/Reloaded.Mod.Loader.Tests/Update/Providers/GameBanana/GameBananaDependencyResolverTests.cs +++ b/source/Reloaded.Mod.Loader.Tests/Update/Providers/GameBanana/GameBananaDependencyResolverTests.cs @@ -68,4 +68,33 @@ public async Task ResolveAsync_WithNoPackage_ReturnsMissing() Assert.NotEmpty(result.NotFoundDependencies); Assert.Empty(result.FoundDependencies); } + + [Fact] + public async Task ResolveAsync_WithInvalidConfig_ReturnsError() + { + // Arrange + var config = new GameBananaUpdateResolverFactory.GameBananaConfig() + { + ItemId = -1, // Invalid item ID that should cause InitializeAsync to fail + ItemType = "Mod" + }; + var clonedDependency = _testEnvironmoent.TestModConfigBTuple.DeepClone(); + Singleton.Instance.SetConfiguration(clonedDependency, config); + var clonedOriginal = _testEnvironmoent.TestModConfigATuple.DeepClone(); + + var gameBanana = new GameBananaDependencyMetadataWriter(); + gameBanana.Update(clonedOriginal.Config, [clonedDependency.Config]); + + // Act + var resolver = new GameBananaDependencyResolver(); + var result = await resolver.ResolveAsync(clonedDependency.Config.ModId, clonedOriginal.Config.PluginData); + + // Assert + Assert.Single(result.Errors); + Assert.Equal(clonedDependency.Config.ModId, result.Errors[0].PackageId); + Assert.Equal(nameof(GameBananaDependencyResolver), result.Errors[0].Resolver); + Assert.NotNull(result.Errors[0].Exception); + Assert.Contains(clonedDependency.Config.ModId, result.NotFoundDependencies); + Assert.Empty(result.FoundDependencies); + } } \ No newline at end of file diff --git a/source/Reloaded.Mod.Loader.Update/Interfaces/IDependencyResolver.cs b/source/Reloaded.Mod.Loader.Update/Interfaces/IDependencyResolver.cs index 34ec9dcf..c41d5166 100644 --- a/source/Reloaded.Mod.Loader.Update/Interfaces/IDependencyResolver.cs +++ b/source/Reloaded.Mod.Loader.Update/Interfaces/IDependencyResolver.cs @@ -29,6 +29,11 @@ public class ModDependencyResolveResult /// public HashSet NotFoundDependencies { get; } = new HashSet(); + /// + /// List of errors that occurred during dependency resolution. + /// + public List Errors { get; } = new List(); + /// /// Combines the results of multiple resolve operations. /// @@ -59,6 +64,9 @@ public static ModDependencyResolveResult Combine(IEnumerable + /// Creates a result with an error for a specific package. + /// + /// The package ID that failed to resolve. + /// The exception that occurred during resolution. + /// The resolver that caused the error. + /// A result containing the error information. + public static ModDependencyResolveResult FromError(string packageId, Exception exception, string resolver) + { + var result = new ModDependencyResolveResult(); + result.Errors.Add(new DependencyResolveError(packageId, exception, resolver)); + result.NotFoundDependencies.Add(packageId); + return result; + } +} + +/// +/// Represents an error that occurred during dependency resolution. +/// +public class DependencyResolveError +{ + /// + /// The package ID that failed to resolve. + /// + public string PackageId { get; } + + /// + /// The exception that occurred during resolution. + /// + public Exception Exception { get; } + + /// + /// The name of the resolver that caused the error. + /// + public string Resolver { get; } + + /// + /// Creates a new dependency resolve error. + /// + /// The package ID that failed to resolve. + /// The exception that occurred during resolution. + /// The name of the resolver that caused the error. + public DependencyResolveError(string packageId, Exception exception, string resolver) + { + PackageId = packageId; + Exception = exception; + Resolver = resolver; + } } \ No newline at end of file diff --git a/source/Reloaded.Mod.Loader.Update/Providers/GameBanana/GameBananaDependencyResolver.cs b/source/Reloaded.Mod.Loader.Update/Providers/GameBanana/GameBananaDependencyResolver.cs index 5658f4fc..8615b829 100644 --- a/source/Reloaded.Mod.Loader.Update/Providers/GameBanana/GameBananaDependencyResolver.cs +++ b/source/Reloaded.Mod.Loader.Update/Providers/GameBanana/GameBananaDependencyResolver.cs @@ -27,12 +27,19 @@ public async Task ResolveAsync(string packageId, Dic ModType = gbConfig.Config.ItemType }, new CommonPackageResolverSettings() { MetadataFileName = gbConfig.ReleaseMetadataName }); - await resolver.InitializeAsync(); - - result.FoundDependencies.Add(new UpdateDownloadablePackage(resolver) + try + { + await resolver.InitializeAsync(); + + result.FoundDependencies.Add(new UpdateDownloadablePackage(resolver) + { + Id = packageId + }); + } + catch (Exception ex) { - Id = packageId - }); + return ModDependencyResolveResult.FromError(packageId, ex, nameof(GameBananaDependencyResolver)); + } return result; } diff --git a/source/Reloaded.Mod.Loader.Update/Providers/GitHub/GitHubDependencyResolver.cs b/source/Reloaded.Mod.Loader.Update/Providers/GitHub/GitHubDependencyResolver.cs index b5afc008..701c7112 100644 --- a/source/Reloaded.Mod.Loader.Update/Providers/GitHub/GitHubDependencyResolver.cs +++ b/source/Reloaded.Mod.Loader.Update/Providers/GitHub/GitHubDependencyResolver.cs @@ -31,11 +31,19 @@ public async Task ResolveAsync(string packageId, Dic InheritVersionFromTag = gitConfig.Config.UseReleaseTag }, new CommonPackageResolverSettings() { MetadataFileName = gitConfig.ReleaseMetadataName }); - await ((IPackageResolver)resolver).InitializeAsync(); - result.FoundDependencies.Add(new UpdateDownloadablePackage(resolver) + try { - Id = packageId - }); + await ((IPackageResolver)resolver).InitializeAsync(); + + result.FoundDependencies.Add(new UpdateDownloadablePackage(resolver) + { + Id = packageId + }); + } + catch (Exception ex) + { + return ModDependencyResolveResult.FromError(packageId, ex, nameof(GitHubDependencyResolver)); + } return result; }