Skip to content

Commit

Permalink
Introduce ResilienceProperties (#1062)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk authored Mar 20, 2023
1 parent 66ca902 commit 0b3e741
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ public void BuildStrategy_EnsureCorrectContext()
context.BuilderName.Should().Be("builder-name");
context.StrategyName.Should().Be("strategy-name");
context.StrategyType.Should().Be("strategy-type");
context.BuilderProperties.Should().BeSameAs(builder.Options.Properties);
verified1 = true;

return new TestResilienceStrategy();
Expand All @@ -272,6 +273,7 @@ public void BuildStrategy_EnsureCorrectContext()
context.BuilderName.Should().Be("builder-name");
context.StrategyName.Should().Be("strategy-name-2");
context.StrategyType.Should().Be("strategy-type-2");
context.BuilderProperties.Should().BeSameAs(builder.Options.Properties);
verified2 = true;

return new TestResilienceStrategy();
Expand Down
3 changes: 2 additions & 1 deletion src/Polly.Core.Tests/ResilienceContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void Return_EnsureDefaults()
context.CancellationToken = cts.Token;
context.Initialize<bool>(true);
context.CancellationToken.Should().Be(cts.Token);

context.Properties.Set(new ResiliencePropertyKey<int>("abc"), 10);
ResilienceContext.Return(context);

AssertDefaults(context);
Expand Down Expand Up @@ -77,5 +77,6 @@ private static void AssertDefaults(ResilienceContext context)
context.ResultType.Name.Should().Be("UnknownResult");
context.IsSynchronous.Should().BeFalse();
context.CancellationToken.Should().Be(CancellationToken.None);
context.Properties.Should().BeEmpty();
}
}
92 changes: 92 additions & 0 deletions src/Polly.Core.Tests/ResiliencePropertiesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System;
using FluentAssertions;
using Xunit;

namespace Polly.Core.Tests;

public class ResiliencePropertiesTests
{
[Fact]
public void TryGetValue_Ok()
{
var key = new ResiliencePropertyKey<long>("dummy");
var props = new ResilienceProperties();

props.Set(key, 12345);

props.TryGetValue(key, out var val).Should().Be(true);
val.Should().Be(12345);
}

[Fact]
public void TryGetValue_NotFound_Ok()
{
var key = new ResiliencePropertyKey<long>("dummy");
var props = new ResilienceProperties();

props.TryGetValue(key, out var val).Should().Be(false);
}

[Fact]
public void GetValue_Ok()
{
var key = new ResiliencePropertyKey<long>("dummy");
var props = new ResilienceProperties();

props.Set(key, 12345);

props.GetValue(key, default).Should().Be(12345);
}

[Fact]
public void GetValue_NotFound_EnsureDefault()
{
var key = new ResiliencePropertyKey<long>("dummy");
var props = new ResilienceProperties();

props.GetValue(key, -1).Should().Be(-1);
}

[Fact]
public void TryGetValue_IncorrectType_NotFound()
{
var key1 = new ResiliencePropertyKey<long>("dummy");
var key2 = new ResiliencePropertyKey<string>("dummy");

var props = new ResilienceProperties();

props.Set(key1, 12345);

props.TryGetValue(key2, out var val).Should().Be(false);
}

[Fact]
public void DictionaryOperations_Ok()
{
IDictionary<string, object?> dict = new ResilienceProperties();

dict.TryGetValue("xyz", out var _).Should().BeFalse();
dict.GetEnumerator().Should().NotBeNull();
((IEnumerable)dict).GetEnumerator().Should().NotBeNull();
dict.IsReadOnly.Should().BeFalse();
dict.Count.Should().Be(0);
dict.Add("dummy", 12345L);
dict.Values.Should().Contain(12345L);
dict.Keys.Should().Contain("dummy");
dict.ContainsKey("dummy").Should().BeTrue();
dict.Contains(new KeyValuePair<string, object?>("dummy", 12345L)).Should().BeTrue();
dict.Add("dummy2", "xyz");
dict["dummy2"].Should().Be("xyz");
dict["dummy3"] = "abc";
dict["dummy3"].Should().Be("abc");
dict.Remove("dummy2").Should().BeTrue();
dict.Remove(new KeyValuePair<string, object?>("not-exists", "abc")).Should().BeFalse();
dict.Clear();
dict.Count.Should().Be(0);
dict.Add(new KeyValuePair<string, object?>("dummy", "abc"));
var array = new KeyValuePair<string, object?>[1];
dict.CopyTo(array, 0);
array[0].Key.Should().Be("dummy");
array[0].Value.Should().Be("abc");
}
}
51 changes: 51 additions & 0 deletions src/Polly.Core.Tests/ResiliencePropertyKeyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using FluentAssertions;
using Xunit;

namespace Polly.Core.Tests;

public class ResiliencePropertyKeyTests
{
[Fact]
public void Ctor_Ok()
{
var instance = new ResiliencePropertyKey<int>("dummy");

instance.Key.Should().Be("dummy");
instance.ToString().Should().Be("dummy");
}

[Fact]
public void Ctor_Null_Throws()
{
Assert.Throws<ArgumentNullException>(() => new ResiliencePropertyKey<int>(null!));
}

[Fact]
public void Equality_Ok()
{
var key1 = new ResiliencePropertyKey<string>("dummy");
var key2 = new ResiliencePropertyKey<string>("dummy");

key1.Equals(key2).Should().BeTrue();
key1.Equals(new ResiliencePropertyKey<string>("dummy2")).Should().BeFalse();
key1.Equals(new ResiliencePropertyKey<object>("dummy")).Should().BeFalse();

key1.Equals((object)key2).Should().BeTrue();
key1.Equals((object)new ResiliencePropertyKey<string>("dummy2")).Should().BeFalse();

(key1 == key2).Should().BeTrue();
(key1 != key2).Should().BeFalse();
}

[Fact]
public void GetHashCode_Ok()
{
var key1 = new ResiliencePropertyKey<string>("dummy");
var key2 = new ResiliencePropertyKey<string>("dummy");

key1.GetHashCode().Should().Be(key2.GetHashCode());
key1.GetHashCode().Should().NotBe(new ResiliencePropertyKey<string>("dummy2").GetHashCode());
key1.GetHashCode().Should().NotBe(new ResiliencePropertyKey<object>("dummy").GetHashCode());
}
}
1 change: 1 addition & 0 deletions src/Polly.Core/Builder/ResilienceStrategyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ private ResilienceStrategy CreateResilienceStrategy(Entry entry)
{
var context = new ResilienceStrategyBuilderContext(
builderName: Options.BuilderName,
builderProperties: Options.Properties,
strategyName: entry.Properties.StrategyName,
strategyType: entry.Properties.StrategyType);

Expand Down
18 changes: 11 additions & 7 deletions src/Polly.Core/Builder/ResilienceStrategyBuilderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ namespace Polly.Builder;
/// </summary>
public class ResilienceStrategyBuilderContext
{
/// <summary>
/// Initializes a new instance of the <see cref="ResilienceStrategyBuilderContext"/> class.
/// </summary>
/// <param name="builderName">The name of the builder.</param>
/// <param name="strategyName">The strategy name.</param>
/// <param name="strategyType">The strategy type.</param>
public ResilienceStrategyBuilderContext(string builderName, string strategyName, string strategyType)
internal ResilienceStrategyBuilderContext(
string builderName,
ResilienceProperties builderProperties,
string strategyName,
string strategyType)
{
BuilderName = Guard.NotNull(builderName);
BuilderProperties = Guard.NotNull(builderProperties);
StrategyName = Guard.NotNull(strategyName);
StrategyType = Guard.NotNull(strategyType);
}
Expand All @@ -23,6 +22,11 @@ public ResilienceStrategyBuilderContext(string builderName, string strategyName,
/// </summary>
public string BuilderName { get; }

/// <summary>
/// Gets the custom properties attached to the builder.
/// </summary>
public ResilienceProperties BuilderProperties { get; }

/// <summary>
/// Gets the name of the strategy.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Polly.Core/Builder/ResilienceStrategyBuilderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ public class ResilienceStrategyBuilderOptions
/// <remarks>This property is also included in the telemetry that is produced by the individual resilience strategies.</remarks>
[Required(AllowEmptyStrings = true)]
public string BuilderName { get; set; } = string.Empty;

/// <summary>
/// Gets the custom properties attached to builder options.
/// </summary>
public ResilienceProperties Properties { get; } = new();
}
12 changes: 12 additions & 0 deletions src/Polly.Core/LegacySupport/MaybeNullWhenAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#if NETFRAMEWORK || NETSTANDARD

namespace System.Diagnostics.CodeAnalysis;

[AttributeUsage(AttributeTargets.Parameter)]
internal sealed class MaybeNullWhenAttribute : Attribute
{
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;

public bool ReturnValue { get; }
}
#endif
6 changes: 6 additions & 0 deletions src/Polly.Core/ResilienceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ private ResilienceContext()
/// </summary>
internal bool IsInitialized => ResultType != typeof(UnknownResult);

/// <summary>
/// Gets the custom properties attached to the context.
/// </summary>
public ResilienceProperties Properties { get; } = new();

/// <summary>
/// Gets a <see cref="ResilienceContext"/> instance from the pool.
/// </summary>
Expand Down Expand Up @@ -91,6 +96,7 @@ private void Reset()
ResultType = typeof(UnknownResult);
ContinueOnCapturedContext = false;
CancellationToken = default;
((IDictionary<string, object?>)Properties).Clear();
}

/// <summary>
Expand Down
114 changes: 114 additions & 0 deletions src/Polly.Core/ResilienceProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Diagnostics.CodeAnalysis;

namespace Polly;

#pragma warning disable CA1710 // Identifiers should have correct suffix

/// <summary>
/// Represents a collection of custom resilience properties.
/// </summary>
public sealed class ResilienceProperties : IDictionary<string, object?>
{
private Dictionary<string, object?> Options { get; } = new();

/// <summary>
/// Gets the value of a given property.
/// </summary>
/// <param name="key">Strongly typed key to get the value of the property.</param>
/// <param name="value">Returns the value of the property.</param>
/// <typeparam name="TValue">The type of property value as defined by <paramref name="key"/> parameter.</typeparam>
/// <returns>True, if a property was retrieved.</returns>
public bool TryGetValue<TValue>(ResiliencePropertyKey<TValue> key, [MaybeNullWhen(false)] out TValue value)
{
if (Options.TryGetValue(key.Key, out object? val) && val is TValue typedValue)
{
value = typedValue;
return true;
}

value = default;
return false;
}

/// <summary>
/// Gets the value of a given property with a fallback default value.
/// </summary>
/// <param name="key">Strongly typed key to get the value of the property.</param>
/// <param name="defaultValue">The default value to use if property is not found.</param>
/// <typeparam name="TValue">The type of property value as defined by <paramref name="key"/> parameter.</typeparam>
/// <returns>The property value or the default value.</returns>
public TValue GetValue<TValue>(ResiliencePropertyKey<TValue> key, TValue defaultValue)
{
if (TryGetValue(key, out var value))
{
return value;
}

return defaultValue;
}

/// <summary>
/// Sets the value of a given property.
/// </summary>
/// <param name="key">Strongly typed key to get the value of the property.</param>
/// <param name="value">Returns the value of the property.</param>
/// <typeparam name="TValue">The type of property value as defined by <paramref name="key"/> parameter.</typeparam>
public void Set<TValue>(ResiliencePropertyKey<TValue> key, TValue value)
{
Options[key.Key] = value;
}

/// <inheritdoc/>
object? IDictionary<string, object?>.this[string key]
{
get => Options[key];
set => Options[key] = value;
}

/// <inheritdoc/>
ICollection<string> IDictionary<string, object?>.Keys => Options.Keys;

/// <inheritdoc/>
ICollection<object?> IDictionary<string, object?>.Values => Options.Values;

/// <inheritdoc/>
int ICollection<KeyValuePair<string, object?>>.Count => Options.Count;

/// <inheritdoc/>
bool ICollection<KeyValuePair<string, object?>>.IsReadOnly => ((IDictionary<string, object?>)Options).IsReadOnly;

/// <inheritdoc/>
void IDictionary<string, object?>.Add(string key, object? value) => Options.Add(key, value);

/// <inheritdoc/>
void ICollection<KeyValuePair<string, object?>>.Add(KeyValuePair<string, object?> item) => ((IDictionary<string, object?>)Options).Add(item);

/// <inheritdoc/>
void ICollection<KeyValuePair<string, object?>>.Clear() => Options.Clear();

/// <inheritdoc/>
bool ICollection<KeyValuePair<string, object?>>.Contains(KeyValuePair<string, object?> item) => ((IDictionary<string, object?>)Options).Contains(item);

/// <inheritdoc/>
bool IDictionary<string, object?>.ContainsKey(string key) => Options.ContainsKey(key);

/// <inheritdoc/>
void ICollection<KeyValuePair<string, object?>>.CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) =>
((IDictionary<string, object?>)Options).CopyTo(array, arrayIndex);

/// <inheritdoc/>
IEnumerator<KeyValuePair<string, object?>> IEnumerable<KeyValuePair<string, object?>>.GetEnumerator() => Options.GetEnumerator();

/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Options).GetEnumerator();

/// <inheritdoc/>
bool IDictionary<string, object?>.Remove(string key) => Options.Remove(key);

/// <inheritdoc/>
bool ICollection<KeyValuePair<string, object?>>.Remove(KeyValuePair<string, object?> item) => ((IDictionary<string, object?>)Options).Remove(item);

/// <inheritdoc/>
bool IDictionary<string, object?>.TryGetValue(string key, out object? value) => Options.TryGetValue(key, out value);
}

Loading

0 comments on commit 0b3e741

Please sign in to comment.