Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ResilienceProperties #1062

Merged
merged 4 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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