diff --git a/src/Ephemerally.Azure.Cosmos/CosmosContainerEphemeral.cs b/src/Ephemerally.Azure.Cosmos/CosmosContainerEphemeral.cs index 8e12110..1e64a27 100644 --- a/src/Ephemerally.Azure.Cosmos/CosmosContainerEphemeral.cs +++ b/src/Ephemerally.Azure.Cosmos/CosmosContainerEphemeral.cs @@ -6,12 +6,12 @@ public class CosmosContainerEphemeral : Ephemeral { public CosmosContainerEphemeral( Container container, - EphemeralOptions options = default) : - base(container, x => x.Id, options.OrDefault()) + EphemeralOptions options = null) : + base(container, options.OrDefault()) { } protected override Task CleanupSelfAsync() => - Value.Database.TryDeleteContainerAsync(Metadata.FullName); + Value.Database.TryDeleteContainerAsync(Value.Id); protected override Task CleanupAllAsync() => Value.Database.TryCleanupContainersAsync(); diff --git a/src/Ephemerally.Azure.Cosmos/CosmosDatabaseEphemeral.cs b/src/Ephemerally.Azure.Cosmos/CosmosDatabaseEphemeral.cs index 385b736..f813daf 100644 --- a/src/Ephemerally.Azure.Cosmos/CosmosDatabaseEphemeral.cs +++ b/src/Ephemerally.Azure.Cosmos/CosmosDatabaseEphemeral.cs @@ -6,12 +6,12 @@ public class CosmosDatabaseEphemeral : Ephemeral { public CosmosDatabaseEphemeral( Database database, - EphemeralOptions options = default) - : base(database, x => x.Id, options.OrDefault()) + EphemeralOptions options = null) + : base(database, options.OrDefault()) { } protected override Task CleanupSelfAsync() => - Value.Client.TryDeleteDatabaseAsync(Metadata.FullName); + Value.Client.TryDeleteDatabaseAsync(Value.Id); protected override Task CleanupAllAsync() => Value.Client.TryCleanupDatabasesAsync(); diff --git a/src/Ephemerally.Azure.Cosmos/InternalExtensions.cs b/src/Ephemerally.Azure.Cosmos/InternalExtensions.cs index ad9a595..f550318 100644 --- a/src/Ephemerally.Azure.Cosmos/InternalExtensions.cs +++ b/src/Ephemerally.Azure.Cosmos/InternalExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Azure.Cosmos; using System.Diagnostics; +// ReSharper disable MemberCanBePrivate.Global namespace Ephemerally.Azure.Cosmos; @@ -56,16 +57,16 @@ internal static async Task TryDeleteDatabaseAsync(this CosmosClient client } internal static bool IsExpired(this DatabaseProperties container) => - container.Id.GetContainerMetadata().IsExpired(); + container.Id.GetNamedMetadata().IsExpired(); internal static bool IsExpired(this Database container) => - container.Id.GetContainerMetadata().IsExpired(); + container.Id.GetNamedMetadata().IsExpired(); internal static bool IsExpired(this ContainerProperties container) => - container.Id.GetContainerMetadata().IsExpired(); + container.Id.GetNamedMetadata().IsExpired(); internal static bool IsExpired(this Container container) => - container.Id.GetContainerMetadata().IsExpired(); + container.Id.GetNamedMetadata().IsExpired(); internal static async Task> GetExpiredDatabasesAsync(this CosmosClient client) { @@ -87,24 +88,35 @@ internal static async Task TryCleanupDatabasesAsync(this CosmosClient client) } } - internal static async Task TryDeleteContainerAsync(this Database database, string containerId) + extension(Database database) { - try + internal async Task TryDeleteContainerAsync(string containerId) { - await database.GetContainer(containerId).DeleteContainerAsync().ConfigureAwait(false); - return true; + try + { + await database.GetContainer(containerId).DeleteContainerAsync().ConfigureAwait(false); + return true; + } + catch (Exception ex) + { + Debug.WriteLine(ex.ToString()); + return false; + } } - catch (Exception ex) + + internal async Task> GetExpiredContainersAsync() { - Debug.WriteLine(ex.ToString()); - return false; + using var iterator = database.GetContainerQueryIterator(); + return await iterator.GetExpiredContainersAsync().ToListAsync().ConfigureAwait(false); } - } - internal static async Task> GetExpiredContainersAsync(this Database database) - { - using var iterator = database.GetContainerQueryIterator(); - return await iterator.GetExpiredContainersAsync().ToListAsync().ConfigureAwait(false); + internal async Task TryCleanupContainersAsync() + { + foreach (var container in await database.GetExpiredContainersAsync().ConfigureAwait(false)) + { + await database.TryDeleteContainerAsync(container.Id).ConfigureAwait(false); + } + } } internal static IAsyncEnumerable GetExpiredContainersAsync(this FeedIterator iterator) => @@ -113,14 +125,6 @@ internal static IAsyncEnumerable GetExpiredContainersAsync( .SelectResources() .Where(IsExpired); - internal static async Task TryCleanupContainersAsync(this Database database) - { - foreach (var container in await database.GetExpiredContainersAsync().ConfigureAwait(false)) - { - await database.TryDeleteContainerAsync(container.Id).ConfigureAwait(false); - } - } - internal static IAsyncEnumerable OnEach(this IAsyncEnumerable enumerable, Action action) => enumerable.Select(x => { diff --git a/src/Ephemerally.Azure.Cosmos/PublicExtensions.cs b/src/Ephemerally.Azure.Cosmos/PublicExtensions.cs index c202557..c5398f4 100644 --- a/src/Ephemerally.Azure.Cosmos/PublicExtensions.cs +++ b/src/Ephemerally.Azure.Cosmos/PublicExtensions.cs @@ -8,28 +8,28 @@ public static class PublicExtensions { private const string DefaultPartitionKeyPath = "/id"; - public static EphemeralCosmosDatabase ToEphemeral(this Database database, EphemeralOptions options = default) => + public static EphemeralCosmosDatabase ToEphemeral(this Database database, EphemeralOptions options = null) => new(new CosmosDatabaseEphemeral(database, options)); - public static EphemeralCosmosContainer ToEphemeral(this Container container, EphemeralOptions options = default) => + public static EphemeralCosmosContainer ToEphemeral(this Container container, EphemeralOptions options = null) => new(new CosmosContainerEphemeral(container, options)); public static async Task CreateEphemeralDatabaseAsync( this CosmosClient client, - EphemeralCreationOptions options = default) + EphemeralCreationOptions options = null) { - var metadata = options.OrDefault().GetNewMetadata(); + var metadata = options.OrDefault().GetNewNamedMetadata(); var response = await client.CreateDatabaseIfNotExistsAsync(metadata.FullName).ConfigureAwait(false); return client.GetDatabase(response.Resource.Id).ToEphemeral(options); } public static async Task CreateEphemeralContainerAsync( this Database database, - EphemeralCreationOptions options = default, - ContainerProperties containerProperties = default, - ThroughputProperties throughputProperties = default) + EphemeralCreationOptions options = null, + ContainerProperties containerProperties = null, + ThroughputProperties throughputProperties = null) { - var metadata = options.OrDefault().GetNewMetadata(); + var metadata = options.OrDefault().GetNewNamedMetadata(); containerProperties ??= new(); containerProperties.Id ??= metadata.FullName; containerProperties.PartitionKeyPath ??= DefaultPartitionKeyPath; @@ -38,14 +38,14 @@ public static async Task CreateEphemeralContainerAsync } public static IEphemeralMetadata GetEphemeralMetadata(this DatabaseProperties container) => - container.Id.GetContainerMetadata(); + container.Id.GetNamedMetadata(); public static IEphemeralMetadata GetEphemeralMetadata(this Database container) => - container.Id.GetContainerMetadata(); + container.Id.GetNamedMetadata(); public static IEphemeralMetadata GetEphemeralMetadata(this ContainerProperties container) => - container.Id.GetContainerMetadata(); + container.Id.GetNamedMetadata(); public static IEphemeralMetadata GetEphemeralMetadata(this Container container) => - container.Id.GetContainerMetadata(); + container.Id.GetNamedMetadata(); } \ No newline at end of file diff --git a/src/Ephemerally.Redis.Xunit/PublicExtensions.cs b/src/Ephemerally.Redis.Xunit/PublicExtensions.cs index 2d61f61..f3a39ea 100644 --- a/src/Ephemerally.Redis.Xunit/PublicExtensions.cs +++ b/src/Ephemerally.Redis.Xunit/PublicExtensions.cs @@ -5,9 +5,12 @@ namespace Ephemerally; public static class PublicExtensions { - public static ConnectionMultiplexer GetMultiplexer(this IRedisInstanceFixture fixture) => - ConnectionMultiplexer.Connect(fixture.ConnectionString); + extension(IRedisInstanceFixture fixture) + { + public ConnectionMultiplexer GetMultiplexer() => + ConnectionMultiplexer.Connect(fixture.ConnectionString); - public static Task GetMultiplexerAsync(this IRedisInstanceFixture fixture) => - ConnectionMultiplexer.ConnectAsync(fixture.ConnectionString); + public Task GetMultiplexerAsync() => + ConnectionMultiplexer.ConnectAsync(fixture.ConnectionString); + } } \ No newline at end of file diff --git a/src/Ephemerally.Redis/PublicExtensions.cs b/src/Ephemerally.Redis/PublicExtensions.cs index 51fbcdb..d4b9631 100644 --- a/src/Ephemerally.Redis/PublicExtensions.cs +++ b/src/Ephemerally.Redis/PublicExtensions.cs @@ -6,13 +6,16 @@ namespace Ephemerally; public static class PublicExtensions { - public static IEphemeralRedisDatabase AsEphemeral(this IDatabase database) => - database is null or IEphemeralRedisDatabase - ? (IEphemeralRedisDatabase)database - : database.ToEphemeral(); + extension(IDatabase database) + { + public IEphemeralRedisDatabase AsEphemeral() => + database is null or IEphemeralRedisDatabase + ? (IEphemeralRedisDatabase)database + : database.ToEphemeral(); - public static IEphemeralRedisDatabase ToEphemeral(this IDatabase database) => - new EphemeralRedisDatabase(new RedisDatabaseEphemeral(database)); + public IEphemeralRedisDatabase ToEphemeral() => + new EphemeralRedisDatabase(new RedisDatabaseEphemeral(database)); + } public static IEphemeralRedisDatabase GetEphemeralDatabase( this IConnectionMultiplexer multiplexer, @@ -22,22 +25,27 @@ public static IEphemeralRedisDatabase GetEphemeralDatabase( #region EphemeralConnectionMultiplexer - public static EphemeralConnectionMultiplexer AsEphemeralMultiplexer(this IConnectionMultiplexer multiplexer) => - multiplexer as EphemeralConnectionMultiplexer ?? multiplexer.ToEphemeralMultiplexer(); + extension(IConnectionMultiplexer multiplexer) + { + public EphemeralConnectionMultiplexer AsEphemeralMultiplexer() => + multiplexer as EphemeralConnectionMultiplexer ?? multiplexer.ToEphemeralMultiplexer(); - public static EphemeralConnectionMultiplexer ToEphemeralMultiplexer(this IConnectionMultiplexer multiplexer) => - new(multiplexer); + public EphemeralConnectionMultiplexer ToEphemeralMultiplexer() => + new(multiplexer); + } #endregion #region PooledConnectionMultiplexer - public static PooledConnectionMultiplexer AsPooledMultiplexer(this IConnectionMultiplexer multiplexer) => - multiplexer as PooledConnectionMultiplexer ?? multiplexer.ToPooledMultiplexer(); + extension(IConnectionMultiplexer multiplexer) + { + public PooledConnectionMultiplexer AsPooledMultiplexer() => + multiplexer as PooledConnectionMultiplexer ?? multiplexer.ToPooledMultiplexer(); - public static PooledConnectionMultiplexer ToPooledMultiplexer(this IConnectionMultiplexer multiplexer) => - new(multiplexer); - + public PooledConnectionMultiplexer ToPooledMultiplexer() => + new(multiplexer); + } #endregion } \ No newline at end of file diff --git a/src/Ephemerally.Redis/RedisDatabaseEphemeral.cs b/src/Ephemerally.Redis/RedisDatabaseEphemeral.cs index a15db3f..9099b8a 100644 --- a/src/Ephemerally.Redis/RedisDatabaseEphemeral.cs +++ b/src/Ephemerally.Redis/RedisDatabaseEphemeral.cs @@ -2,7 +2,7 @@ namespace Ephemerally.Redis; -public class RedisDatabaseEphemeral(IDatabase value) : Ephemeral(value, x => x.Database.ToString(), EphemeralCreationOptions), +public class RedisDatabaseEphemeral(IDatabase value) : Ephemeral(value, EphemeralCreationOptions), IDisposable { private static readonly EphemeralCreationOptions EphemeralCreationOptions = new() diff --git a/src/Ephemerally/CreationCachingBehavior.cs b/src/Ephemerally/CreationCachingBehavior.cs deleted file mode 100644 index ff4ac52..0000000 --- a/src/Ephemerally/CreationCachingBehavior.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ephemerally; - -public enum CreationCachingBehavior -{ - NoCache = 0, - Cache = 1 -} \ No newline at end of file diff --git a/src/Ephemerally/Ephemeral.cs b/src/Ephemerally/Ephemeral.cs index 928f9ee..77f8e0f 100644 --- a/src/Ephemerally/Ephemeral.cs +++ b/src/Ephemerally/Ephemeral.cs @@ -6,21 +6,14 @@ public abstract class Ephemeral : IEphemeral private bool _disposed; private readonly EphemeralOptions _options; - private readonly EphemeralMetadata _metadata; - private readonly TValue _object; - private string FullName => _metadata.FullName; - public DateTimeOffset Expiration => _metadata.Expiration!.Value; - public IEphemeralMetadata Metadata => _metadata; - - protected Ephemeral(TValue value, Func getFullName, EphemeralOptions options) + protected Ephemeral(TValue value, EphemeralOptions options) { - _object = value; + Value = value; _options = options; - _metadata = EphemeralMetadata.Parse(getFullName(value)); } - public TValue Value => _object ?? throw new InvalidOperationException("The object has not been created yet."); + public TValue Value => field ?? throw new InvalidOperationException("The object has not been created yet."); /// /// In an overridden implementation, this method should delete the TObject. diff --git a/src/Ephemerally/IEphemeralMetadata.cs b/src/Ephemerally/IEphemeralMetadata.cs index a439f5c..fb8cf21 100644 --- a/src/Ephemerally/IEphemeralMetadata.cs +++ b/src/Ephemerally/IEphemeralMetadata.cs @@ -2,6 +2,5 @@ namespace Ephemerally; public interface IEphemeralMetadata { - string FullName { get; } DateTimeOffset? Expiration { get; } } \ No newline at end of file diff --git a/src/Ephemerally/InternalExtensions.cs b/src/Ephemerally/InternalExtensions.cs index d6c0b98..6620a1b 100644 --- a/src/Ephemerally/InternalExtensions.cs +++ b/src/Ephemerally/InternalExtensions.cs @@ -5,21 +5,24 @@ internal static class InternalExtensions internal static T OrDefault(this T options) where T : EphemeralOptions, new() => options ?? new T(); - public static async ValueTask TryDisposeAsync(this T self) where T : class + extension(T self) where T : class { - if (self is not IAsyncDisposable disposable) - return false; + public async ValueTask TryDisposeAsync() + { + if (self is not IAsyncDisposable disposable) + return false; - await disposable.DisposeAsync().ConfigureAwait(false); - return true; - } + await disposable.DisposeAsync().ConfigureAwait(false); + return true; + } - public static bool TryDispose(this T self) where T : class - { - if (self is not IDisposable disposable) - return false; + public bool TryDispose() + { + if (self is not IDisposable disposable) + return false; - disposable.Dispose(); - return true; + disposable.Dispose(); + return true; + } } } \ No newline at end of file diff --git a/src/Ephemerally/EphemeralMetadata.cs b/src/Ephemerally/NamedEphemeralMetadata.cs similarity index 82% rename from src/Ephemerally/EphemeralMetadata.cs rename to src/Ephemerally/NamedEphemeralMetadata.cs index 95615e6..fe60972 100644 --- a/src/Ephemerally/EphemeralMetadata.cs +++ b/src/Ephemerally/NamedEphemeralMetadata.cs @@ -1,6 +1,6 @@ namespace Ephemerally; -public readonly record struct EphemeralMetadata : IEphemeralMetadata +public readonly record struct NamedEphemeralMetadata : IEphemeralMetadata { private const string PrefixValue = "E"; @@ -14,7 +14,7 @@ namespace Ephemerally; /// Non-ephemeral constructor /// /// - private EphemeralMetadata(string fullName) + private NamedEphemeralMetadata(string fullName) { FullName = fullName; } @@ -26,11 +26,11 @@ private EphemeralMetadata(string fullName) /// /// /// - private EphemeralMetadata( + private NamedEphemeralMetadata( DateTimeOffset expiration, string nonce, string friendlyName, - string fullName = default) + string fullName = null) { FullName = fullName ?? GetFullName(expiration.ToUnixTimeMilliseconds(), nonce, friendlyName); Expiration = expiration; @@ -47,14 +47,14 @@ internal static string GetFullName( string name) => $"{PrefixValue}_{expirationTimestamp}_{nonce}_{name}"; - internal static EphemeralMetadata Parse(string fullName) => + internal static NamedEphemeralMetadata Parse(string fullName) => fullName.Split('_') is [PrefixValue, var ts, var nonce, var friendlyName] && long.TryParse(ts, out var timestamp) ? new(DateTimeOffset.FromUnixTimeMilliseconds(timestamp), nonce, friendlyName, fullName) : new(fullName); - internal static EphemeralMetadata New( + internal static NamedEphemeralMetadata New( string name, DateTimeOffset? expiration) { @@ -66,5 +66,5 @@ internal static EphemeralMetadata New( return new(expirationTimestamp, nonce, name); } - public static EphemeralMetadata Empty => new(string.Empty); + public static NamedEphemeralMetadata Empty => new(string.Empty); } \ No newline at end of file diff --git a/src/Ephemerally/PublicExtensions.cs b/src/Ephemerally/PublicExtensions.cs index 82b7ff1..f8b835d 100644 --- a/src/Ephemerally/PublicExtensions.cs +++ b/src/Ephemerally/PublicExtensions.cs @@ -2,17 +2,20 @@ public static class PublicExtensions { - public static IEphemeralMetadata GetNewMetadata(this EphemeralCreationOptions options) => - EphemeralMetadata.New(options.Name, options.GetExpiration(DateTimeOffset.UtcNow)); + public static NamedEphemeralMetadata GetNewNamedMetadata(this EphemeralCreationOptions options) => + NamedEphemeralMetadata.New(options.Name, options.GetExpiration(DateTimeOffset.UtcNow)); - public static bool IsExpired(this IEphemeralMetadata metadata) => - IsExpiredAsOf(metadata, DateTimeOffset.UtcNow); + extension(IEphemeralMetadata metadata) + { + public bool IsExpired() => + IsExpiredAsOf(metadata, DateTimeOffset.UtcNow); - public static bool IsExpiredAsOf(this IEphemeralMetadata metadata, DateTimeOffset now) => - metadata.Expiration.HasValue && metadata.Expiration.Value <= now; + public bool IsExpiredAsOf(DateTimeOffset now) => + metadata.Expiration.HasValue && metadata.Expiration.Value <= now; + } - public static IEphemeralMetadata GetContainerMetadata(this string fullName) => - EphemeralMetadata.Parse(fullName); + public static NamedEphemeralMetadata GetNamedMetadata(this string fullName) => + NamedEphemeralMetadata.Parse(fullName); public static IEphemeral ToEphemeral(this T value, Func cleanupSelfAsync) where T : class => new SingleEphemeral(value, cleanupSelfAsync); diff --git a/src/Ephemerally/SingleEphemeral.cs b/src/Ephemerally/SingleEphemeral.cs index 2378729..97553bd 100644 --- a/src/Ephemerally/SingleEphemeral.cs +++ b/src/Ephemerally/SingleEphemeral.cs @@ -13,7 +13,7 @@ public sealed class SingleEphemeral : Ephemeral where T : class public SingleEphemeral( T value, Func cleanupSelfAsync) - : base(value, x => string.Empty, new EphemeralOptions { CleanupBehavior = CleanupBehavior.SelfOnly }) + : base(value, new EphemeralOptions { CleanupBehavior = CleanupBehavior.SelfOnly }) { _cleanupSelfAsync = cleanupSelfAsync; } diff --git a/tests/Ephemerally.Azure.Cosmos.Tests/Ephemerally.Azure.Cosmos.Tests.csproj b/tests/Ephemerally.Azure.Cosmos.Tests/Ephemerally.Azure.Cosmos.Tests.csproj index 77a8d1c..f68215c 100644 --- a/tests/Ephemerally.Azure.Cosmos.Tests/Ephemerally.Azure.Cosmos.Tests.csproj +++ b/tests/Ephemerally.Azure.Cosmos.Tests/Ephemerally.Azure.Cosmos.Tests.csproj @@ -16,7 +16,6 @@ - diff --git a/tests/Ephemerally.Redis.Tests/Ephemerally.Redis.Tests.csproj b/tests/Ephemerally.Redis.Tests/Ephemerally.Redis.Tests.csproj index 80b8de3..69a50b9 100644 --- a/tests/Ephemerally.Redis.Tests/Ephemerally.Redis.Tests.csproj +++ b/tests/Ephemerally.Redis.Tests/Ephemerally.Redis.Tests.csproj @@ -11,7 +11,6 @@ - diff --git a/tests/Ephemerally.Tests/EphemeralMetadataTests.cs b/tests/Ephemerally.Tests/EphemeralMetadataTests.cs index c71d026..2de808f 100644 --- a/tests/Ephemerally.Tests/EphemeralMetadataTests.cs +++ b/tests/Ephemerally.Tests/EphemeralMetadataTests.cs @@ -4,14 +4,14 @@ public class EphemeralMetadataTests { [TestCase(1684991963, "ABCDEF", "Test", ExpectedResult = "E_1684991963_ABCDEF_Test")] public string GetFullName_Tests(long expirationTimestamp, string nonce, string name) => - EphemeralMetadata.GetFullName(expirationTimestamp, nonce, name); + NamedEphemeralMetadata.GetFullName(expirationTimestamp, nonce, name); [TestCase(1684991962, ExpectedResult = false)] [TestCase(1684991963, ExpectedResult = true)] [TestCase(1684991964, ExpectedResult = true)] public bool IsExpired_as_of_1684991963(long now) { - var metadata = EphemeralMetadata.Parse("E_1684991963_ABCDEF_Test") with + var metadata = NamedEphemeralMetadata.Parse("E_1684991963_ABCDEF_Test") with { Expiration = DateTimeOffset.FromUnixTimeSeconds(1684991963), NamePart = "Test", diff --git a/tests/Ephemerally.Tests/Ephemerally.Tests.csproj b/tests/Ephemerally.Tests/Ephemerally.Tests.csproj index 32a1736..9ea878e 100644 --- a/tests/Ephemerally.Tests/Ephemerally.Tests.csproj +++ b/tests/Ephemerally.Tests/Ephemerally.Tests.csproj @@ -15,7 +15,10 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive +