Skip to content

feat: Add ReadFrom parsing support to ConfigurationOptions for AZ Affinity #28

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

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
985e606
feat: Add ReadFrom parsing support to ConfigurationOptions
jbrinkman Aug 12, 2025
baf5cc9
feat(config): implement ReadFrom serialization in ConfigurationOptions
jbrinkman Aug 12, 2025
d00ac0d
feat(config): extend ConfigurationOptions parsing to handle ReadFrom …
jbrinkman Aug 12, 2025
d1357df
test: Add comprehensive tests for ConnectionMultiplexer ReadFrom mapping
jbrinkman Aug 12, 2025
64487d2
test: Add comprehensive unit tests for ConfigurationOptions ReadFrom …
jbrinkman Aug 12, 2025
05d3c4c
test: Add comprehensive unit tests for ConfigurationOptions ReadFrom …
jbrinkman Aug 12, 2025
fad1808
test: Add integration tests for ConnectionMultiplexer ReadFrom mappin…
jbrinkman Aug 12, 2025
c053044
feat(tests): add comprehensive end-to-end integration tests for ReadF…
jbrinkman Aug 13, 2025
53f6780
test: Resolve stylecop lint errors
jbrinkman Aug 13, 2025
4009a95
test: Refactor unit tests for ConfigurationOptions and ConnectionMult…
jbrinkman Aug 14, 2025
c32220b
test: streamline ConfigurationOptions cloning process
jbrinkman Aug 15, 2025
b92a521
test:Refactor tests in ConfigurationOptionsReadFromTests and Connecti…
jbrinkman Aug 15, 2025
626acb5
refactor: Simplify Clone method in ConfigurationOptions for improved …
jbrinkman Aug 15, 2025
41369c9
refactor: Simplify ReadFrom property and clean up related code in Con…
jbrinkman Aug 15, 2025
c1fbdbb
fix: Ensure non-null az parameter in ReadFrom constructor based on st…
jbrinkman Aug 15, 2025
b56f189
refactor: Remove ValidateReadFromConfiguration method to simplify Con…
jbrinkman Aug 15, 2025
46613eb
refactor: Remove outdated tests and simplify ConfigurationOptionsRead…
jbrinkman Aug 15, 2025
bd94403
Merge branch 'main' into jbrinkman/az-affinity-configurationoptions-s…
Yury-Fridlyand Aug 22, 2025
61b8d6c
Add e2e test
Yury-Fridlyand Aug 22, 2025
2ec87f6
Update tests
Yury-Fridlyand Aug 23, 2025
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
97 changes: 85 additions & 12 deletions sources/Valkey.Glide/Abstract/ConfigurationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ internal const string
Ssl = "ssl",
Version = "version",
SetClientLibrary = "setlib",
Protocol = "protocol"
Protocol = "protocol",
ReadFrom = "readFrom",
Az = "az"
;

private static readonly Dictionary<string, string> normalizedOptions = new[]
Expand All @@ -71,6 +73,8 @@ internal const string
Version,
SetClientLibrary,
Protocol,
ReadFrom,
Az
}.ToDictionary(x => x, StringComparer.OrdinalIgnoreCase);

public static string TryNormalize(string value)
Expand All @@ -83,13 +87,11 @@ public static string TryNormalize(string value)
}
}

#region Private fields
private bool? ssl;

private Proxy? proxy;

private RetryStrategy? reconnectRetryPolicy;

private ReadFrom? readFrom;
#endregion

/// <summary>
/// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException.
Expand Down Expand Up @@ -240,11 +242,7 @@ public RetryStrategy? ReconnectRetryPolicy
/// <summary>
/// The read from strategy and Availability zone if applicable.
/// </summary>
public ReadFrom? ReadFrom
{
get => readFrom;
set => readFrom = value;
}
public ReadFrom? ReadFrom { get; set; }

/// <summary>
/// Indicates whether endpoints should be resolved via DNS before connecting.
Expand Down Expand Up @@ -306,9 +304,9 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow
ResponseTimeout = ResponseTimeout,
DefaultDatabase = DefaultDatabase,
reconnectRetryPolicy = reconnectRetryPolicy,
readFrom = readFrom,
EndPoints = EndPoints.Clone(),
Protocol = Protocol,
ReadFrom = ReadFrom
};

/// <summary>
Expand Down Expand Up @@ -356,6 +354,10 @@ public string ToString(bool includePassword)
Append(sb, OptionKeys.ResponseTimeout, ResponseTimeout);
Append(sb, OptionKeys.DefaultDatabase, DefaultDatabase);
Append(sb, OptionKeys.Protocol, FormatProtocol(Protocol));
if (ReadFrom.HasValue)
{
FormatReadFrom(sb, ReadFrom.Value);
}

return sb.ToString();

Expand Down Expand Up @@ -401,7 +403,7 @@ private void Clear()
ClientName = User = Password = null;
ConnectTimeout = ResponseTimeout = null;
ssl = null;
readFrom = null;
ReadFrom = null;
reconnectRetryPolicy = null;
EndPoints.Clear();
}
Expand All @@ -410,6 +412,9 @@ private void Clear()

private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown = true)
{
ReadFromStrategy? tempReadFromStrategy = null;
string? tempAz = null;

if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
Expand Down Expand Up @@ -463,6 +468,12 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown =
case OptionKeys.ResponseTimeout:
ResponseTimeout = OptionKeys.ParseInt32(key, value);
break;
case OptionKeys.ReadFrom:
tempReadFromStrategy = CheckReadFromValue(value);
break;
case OptionKeys.Az:
tempAz = CheckAzValue(value);
break;
default:
if (!ignoreUnknown) throw new ArgumentException($"Keyword '{key}' is not supported.", key);
break;
Expand All @@ -476,9 +487,71 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown =
}
}
}

// Validate ReadFrom configuration after all parameters have been parsed
if (tempReadFromStrategy.HasValue)
{
ReadFrom = SetReadFrom(tempReadFromStrategy, tempAz);
}

return this;
}

private string CheckAzValue(string az)
{
if (string.IsNullOrWhiteSpace(az))
{
throw new ArgumentException("Availability zone cannot be empty or whitespace");
}
return az;
}

private ReadFrom? SetReadFrom(ReadFromStrategy? strategy, string? az)
{
if (strategy.HasValue)
{
// Use ReadFrom constructors based on strategy type - the constructors contain the validation logic
return strategy.Value switch
{
ReadFromStrategy.AzAffinity or ReadFromStrategy.AzAffinityReplicasAndPrimary => new ReadFrom(strategy.Value, az!),
ReadFromStrategy.Primary or ReadFromStrategy.PreferReplica => new ReadFrom(strategy.Value),
_ => throw new ArgumentException($"ReadFrom strategy '{strategy.Value}' is not supported. Valid strategies are: Primary, PreferReplica, AzAffinity, AzAffinityReplicasAndPrimary"),
};
}
return null;
}

private ReadFromStrategy CheckReadFromValue(string readFrom)
{
if (string.IsNullOrWhiteSpace(readFrom))
{
throw new ArgumentException("ReadFrom strategy cannot be empty");
}

try
{
return Enum.Parse<ReadFromStrategy>(readFrom, ignoreCase: true);
}
catch (ArgumentException)
{
throw new ArgumentException($"ReadFrom strategy '{readFrom}' is not supported. Valid strategies are: Primary, PreferReplica, AzAffinity, AzAffinityReplicasAndPrimary");
}
}

/// <summary>
/// Formats a ReadFrom struct to its string representation and appends it to the StringBuilder.
/// </summary>
/// <param name="sb">The StringBuilder to append to.</param>
/// <param name="readFromConfig">The ReadFrom configuration to format.</param>
private static void FormatReadFrom(StringBuilder sb, ReadFrom readFromConfig)
{
Append(sb, OptionKeys.ReadFrom, readFromConfig.Strategy.ToString());
if (!string.IsNullOrWhiteSpace(readFromConfig.Az))
{
Append(sb, OptionKeys.Az, readFromConfig.Az);
}
}

/// <summary>
/// Specify the connection protocol type.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ private ConnectionMultiplexer(ConfigurationOptions configuration, Database db)
_db = db;
}

private static T CreateClientConfigBuilder<T>(ConfigurationOptions configuration)
internal static T CreateClientConfigBuilder<T>(ConfigurationOptions configuration)
where T : ClientConfigurationBuilder<T>, new()
{
T config = new();
Expand Down
4 changes: 4 additions & 0 deletions sources/Valkey.Glide/ConnectionConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ public ReadFrom(ReadFromStrategy strategy, string az)
{
throw new ArgumentException("Availability zone could be set only when using `AzAffinity` or `AzAffinityReplicasAndPrimary` strategy.");
}
if (string.IsNullOrWhiteSpace(az))
{
throw new ArgumentException("Availability zone cannot be empty or whitespace");
}
Strategy = strategy;
Az = az;
}
Expand Down
2 changes: 2 additions & 0 deletions sources/Valkey.Glide/Internals/FFI.methods.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

// check https://stackoverflow.com/a/77455034 if you're getting analyzer error (using is unnecessary)
#if NET8_0_OR_GREATER
using System.Runtime.CompilerServices;
#endif
using System.Runtime.InteropServices;

namespace Valkey.Glide.Internals;
Expand Down
Loading
Loading