Skip to content

Commit d565545

Browse files
committed
Add IValidateOptions for DistributedCacheEventStreamStoreOptions
1 parent 52b6806 commit d565545

5 files changed

Lines changed: 50 additions & 3 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Microsoft.Extensions.Caching.Distributed;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Options;
4+
using ModelContextProtocol.Server;
5+
6+
namespace ModelContextProtocol.AspNetCore;
7+
8+
/// <summary>
9+
/// Validates that <see cref="DistributedCacheEventStreamStoreOptions.Cache"/> is set.
10+
/// </summary>
11+
internal sealed class DistributedCacheEventStreamStoreOptionsValidator : IValidateOptions<DistributedCacheEventStreamStoreOptions>
12+
{
13+
public ValidateOptionsResult Validate(string? name, DistributedCacheEventStreamStoreOptions options)
14+
{
15+
if (options.Cache is null)
16+
{
17+
return ValidateOptionsResult.Fail(
18+
$"The '{nameof(DistributedCacheEventStreamStoreOptions)}.{nameof(DistributedCacheEventStreamStoreOptions.Cache)}' property must be set. " +
19+
$"Register an {nameof(IDistributedCache)} in DI or set the {nameof(DistributedCacheEventStreamStoreOptions.Cache)} property " +
20+
$"in the '{nameof(HttpMcpServerBuilderExtensions.WithDistributedCacheEventStreamStore)}' configure callback.");
21+
}
22+
23+
return ValidateOptionsResult.Success;
24+
}
25+
}

src/ModelContextProtocol.AspNetCore/HttpMcpServerBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public static IMcpServerBuilder WithDistributedCacheEventStreamStore(this IMcpSe
8989
ArgumentNullException.ThrowIfNull(builder);
9090

9191
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<DistributedCacheEventStreamStoreOptions>, DistributedCacheEventStreamStoreOptionsSetup>());
92+
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<DistributedCacheEventStreamStoreOptions>, DistributedCacheEventStreamStoreOptionsValidator>());
9293
builder.Services.AddSingleton<ISseEventStreamStore, DistributedCacheEventStreamStore>();
9394

9495
if (configureOptions is not null)

src/ModelContextProtocol/Server/DistributedCacheEventStreamStore.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ public DistributedCacheEventStreamStore(IOptions<DistributedCacheEventStreamStor
4040

4141
var optionsValue = options.Value;
4242
_cache = optionsValue.Cache ?? throw new InvalidOperationException(
43-
$"The '{nameof(DistributedCacheEventStreamStoreOptions)}.{nameof(DistributedCacheEventStreamStoreOptions.Cache)}' property must be set. " +
44-
$"When using 'WithDistributedCacheEventStreamStore()', the cache will be automatically configured from DI. " +
45-
$"When constructing manually, set the '{nameof(DistributedCacheEventStreamStoreOptions.Cache)}' property on the options object.");
43+
$"The '{nameof(DistributedCacheEventStreamStoreOptions)}.{nameof(DistributedCacheEventStreamStoreOptions.Cache)}' property must be set.");
4644
_options = optionsValue;
4745
_logger = logger ?? NullLogger<DistributedCacheEventStreamStore>.Instance;
4846
}

tests/ModelContextProtocol.AspNetCore.Tests/HttpMcpServerBuilderExtensionsTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ public void WithDistributedCacheEventStreamStore_WorksWithoutDICache_WhenCacheSe
6161
Assert.IsType<DistributedCacheEventStreamStore>(store);
6262
}
6363

64+
[Fact]
65+
public void WithDistributedCacheEventStreamStore_ThrowsOptionsValidationException_WhenNoCacheConfigured()
66+
{
67+
Builder.Services
68+
.AddMcpServer()
69+
.WithHttpTransport()
70+
.WithDistributedCacheEventStreamStore();
71+
72+
using var app = Builder.Build();
73+
74+
var ex = Assert.Throws<OptionsValidationException>(
75+
() => app.Services.GetRequiredService<ISseEventStreamStore>());
76+
Assert.StartsWith($"The '{nameof(DistributedCacheEventStreamStoreOptions)}.{nameof(DistributedCacheEventStreamStoreOptions.Cache)}'", ex.Message);
77+
}
78+
6479
[Fact]
6580
public void EventStreamStore_IsPopulatedFromDI_ViaPostConfigure()
6681
{

tests/ModelContextProtocol.Tests/Server/DistributedCacheEventStreamStoreTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ public void Constructor_ThrowsArgumentNullException_WhenOptionsIsNull()
3434
Assert.Throws<ArgumentNullException>("options", () => new DistributedCacheEventStreamStore(null!));
3535
}
3636

37+
[Fact]
38+
public void Constructor_ThrowsInvalidOperationException_WhenCacheIsNull()
39+
{
40+
var options = Options.Create(new DistributedCacheEventStreamStoreOptions());
41+
var ex = Assert.Throws<InvalidOperationException>(() => new DistributedCacheEventStreamStore(options));
42+
Assert.StartsWith($"The '{nameof(DistributedCacheEventStreamStoreOptions)}.{nameof(DistributedCacheEventStreamStoreOptions.Cache)}'", ex.Message);
43+
}
44+
3745
[Fact]
3846
public async Task CreateStreamAsync_ThrowsArgumentNullException_WhenOptionsIsNull()
3947
{

0 commit comments

Comments
 (0)