From 9f28128db52615c7c631e7cdb5937eae6a4cf28a Mon Sep 17 00:00:00 2001 From: Matthew Edmondson Date: Fri, 9 Oct 2026 04:26:53 +0000 Subject: [PATCH] fix: register each generator once when AddCoder is called twice [patch] AddLanguageGenerators appended a descriptor per call, so calling AddCoder from both a library and its host resolved fourteen generators and the documented .Single(g => g.LanguageId == ...) lookup threw. Register the generators with TryAddEnumerable and the serializers with TryAddSingleton, and make the idempotency test assert one registration of each. Fixes #150 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ACXyQBPWSqFJuF51Mx6FNh --- .../ServiceCollectionExtensionsTests.cs | 10 ++++++++- Coder/ServiceCollectionExtensions.cs | 22 ++++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Coder.Test/ServiceCollectionExtensionsTests.cs b/Coder.Test/ServiceCollectionExtensionsTests.cs index 0f90ce0..5707c51 100644 --- a/Coder.Test/ServiceCollectionExtensionsTests.cs +++ b/Coder.Test/ServiceCollectionExtensionsTests.cs @@ -200,7 +200,15 @@ public void AddCoder_ShouldBeIdempotent() YamlSerializer? serializer = serviceProvider.GetService(); YamlDeserializer? deserializer = serviceProvider.GetService(); - Assert.IsTrue(generators.Count > 0, "Generators should still be registered"); + Assert.HasCount(7, generators, "Each generator should be registered once however often AddCoder is called"); + foreach (string languageId in new[] { "python", "csharp", "javascript", "cpp", "c", "rust", "go" }) + { + ILanguageGenerator generator = generators.Single(g => g.LanguageId == languageId); + Assert.IsNotNull(generator, $"{languageId} should resolve to exactly one generator"); + } + + Assert.HasCount(1, serviceProvider.GetServices(), "Serializer should be registered once"); + Assert.HasCount(1, serviceProvider.GetServices(), "Deserializer should be registered once"); Assert.IsNotNull(serializer, "Serializer should still be registered"); Assert.IsNotNull(deserializer, "Deserializer should still be registered"); } diff --git a/Coder/ServiceCollectionExtensions.cs b/Coder/ServiceCollectionExtensions.cs index ead643d..843ec0f 100644 --- a/Coder/ServiceCollectionExtensions.cs +++ b/Coder/ServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ namespace ktsu.Coder; using ktsu.Coder.Languages; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; /// /// Extension methods for configuring Coder services in dependency injection containers. @@ -19,14 +20,15 @@ public static IServiceCollection AddLanguageGenerators(this IServiceCollection s { Ensure.NotNull(services); - // Register all available language generators - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + // Register all available language generators. TryAddEnumerable skips a generator that is + // already registered, so calling this twice still resolves one of each. + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); return services; } @@ -40,8 +42,8 @@ public static IServiceCollection AddCoderSerialization(this IServiceCollection s { Ensure.NotNull(services); - services.AddSingleton(); - services.AddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); return services; }