Skip to content

Commit ef54380

Browse files
feat: Add ReadFrom parsing support to ConfigurationOptions for AZ Affinity (#28)
Signed-off-by: jbrinkman <[email protected]> Signed-off-by: Yury-Fridlyand <[email protected]> Co-authored-by: Yury-Fridlyand <[email protected]>
1 parent 69d25ac commit ef54380

File tree

10 files changed

+1998
-14
lines changed

10 files changed

+1998
-14
lines changed

sources/Valkey.Glide/Abstract/ConfigurationOptions.cs

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ internal const string
5252
Ssl = "ssl",
5353
Version = "version",
5454
SetClientLibrary = "setlib",
55-
Protocol = "protocol"
55+
Protocol = "protocol",
56+
ReadFrom = "readFrom",
57+
Az = "az"
5658
;
5759

5860
private static readonly Dictionary<string, string> normalizedOptions = new[]
@@ -71,6 +73,8 @@ internal const string
7173
Version,
7274
SetClientLibrary,
7375
Protocol,
76+
ReadFrom,
77+
Az
7478
}.ToDictionary(x => x, StringComparer.OrdinalIgnoreCase);
7579

7680
public static string TryNormalize(string value)
@@ -83,13 +87,11 @@ public static string TryNormalize(string value)
8387
}
8488
}
8589

90+
#region Private fields
8691
private bool? ssl;
87-
8892
private Proxy? proxy;
89-
9093
private RetryStrategy? reconnectRetryPolicy;
91-
92-
private ReadFrom? readFrom;
94+
#endregion
9395

9496
/// <summary>
9597
/// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException.
@@ -240,11 +242,7 @@ public RetryStrategy? ReconnectRetryPolicy
240242
/// <summary>
241243
/// The read from strategy and Availability zone if applicable.
242244
/// </summary>
243-
public ReadFrom? ReadFrom
244-
{
245-
get => readFrom;
246-
set => readFrom = value;
247-
}
245+
public ReadFrom? ReadFrom { get; set; }
248246

249247
/// <summary>
250248
/// Indicates whether endpoints should be resolved via DNS before connecting.
@@ -306,9 +304,9 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow
306304
ResponseTimeout = ResponseTimeout,
307305
DefaultDatabase = DefaultDatabase,
308306
reconnectRetryPolicy = reconnectRetryPolicy,
309-
readFrom = readFrom,
310307
EndPoints = EndPoints.Clone(),
311308
Protocol = Protocol,
309+
ReadFrom = ReadFrom
312310
};
313311

314312
/// <summary>
@@ -356,6 +354,10 @@ public string ToString(bool includePassword)
356354
Append(sb, OptionKeys.ResponseTimeout, ResponseTimeout);
357355
Append(sb, OptionKeys.DefaultDatabase, DefaultDatabase);
358356
Append(sb, OptionKeys.Protocol, FormatProtocol(Protocol));
357+
if (ReadFrom.HasValue)
358+
{
359+
FormatReadFrom(sb, ReadFrom.Value);
360+
}
359361

360362
return sb.ToString();
361363

@@ -401,7 +403,7 @@ private void Clear()
401403
ClientName = User = Password = null;
402404
ConnectTimeout = ResponseTimeout = null;
403405
ssl = null;
404-
readFrom = null;
406+
ReadFrom = null;
405407
reconnectRetryPolicy = null;
406408
EndPoints.Clear();
407409
}
@@ -410,6 +412,9 @@ private void Clear()
410412

411413
private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown = true)
412414
{
415+
ReadFromStrategy? tempReadFromStrategy = null;
416+
string? tempAz = null;
417+
413418
if (configuration == null)
414419
{
415420
throw new ArgumentNullException(nameof(configuration));
@@ -463,6 +468,12 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown =
463468
case OptionKeys.ResponseTimeout:
464469
ResponseTimeout = OptionKeys.ParseInt32(key, value);
465470
break;
471+
case OptionKeys.ReadFrom:
472+
tempReadFromStrategy = CheckReadFromValue(value);
473+
break;
474+
case OptionKeys.Az:
475+
tempAz = CheckAzValue(value);
476+
break;
466477
default:
467478
if (!ignoreUnknown) throw new ArgumentException($"Keyword '{key}' is not supported.", key);
468479
break;
@@ -476,9 +487,71 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown =
476487
}
477488
}
478489
}
490+
491+
// Validate ReadFrom configuration after all parameters have been parsed
492+
if (tempReadFromStrategy.HasValue)
493+
{
494+
ReadFrom = SetReadFrom(tempReadFromStrategy, tempAz);
495+
}
496+
479497
return this;
480498
}
481499

500+
private string CheckAzValue(string az)
501+
{
502+
if (string.IsNullOrWhiteSpace(az))
503+
{
504+
throw new ArgumentException("Availability zone cannot be empty or whitespace");
505+
}
506+
return az;
507+
}
508+
509+
private ReadFrom? SetReadFrom(ReadFromStrategy? strategy, string? az)
510+
{
511+
if (strategy.HasValue)
512+
{
513+
// Use ReadFrom constructors based on strategy type - the constructors contain the validation logic
514+
return strategy.Value switch
515+
{
516+
ReadFromStrategy.AzAffinity or ReadFromStrategy.AzAffinityReplicasAndPrimary => new ReadFrom(strategy.Value, az!),
517+
ReadFromStrategy.Primary or ReadFromStrategy.PreferReplica => new ReadFrom(strategy.Value),
518+
_ => throw new ArgumentException($"ReadFrom strategy '{strategy.Value}' is not supported. Valid strategies are: Primary, PreferReplica, AzAffinity, AzAffinityReplicasAndPrimary"),
519+
};
520+
}
521+
return null;
522+
}
523+
524+
private ReadFromStrategy CheckReadFromValue(string readFrom)
525+
{
526+
if (string.IsNullOrWhiteSpace(readFrom))
527+
{
528+
throw new ArgumentException("ReadFrom strategy cannot be empty");
529+
}
530+
531+
try
532+
{
533+
return Enum.Parse<ReadFromStrategy>(readFrom, ignoreCase: true);
534+
}
535+
catch (ArgumentException)
536+
{
537+
throw new ArgumentException($"ReadFrom strategy '{readFrom}' is not supported. Valid strategies are: Primary, PreferReplica, AzAffinity, AzAffinityReplicasAndPrimary");
538+
}
539+
}
540+
541+
/// <summary>
542+
/// Formats a ReadFrom struct to its string representation and appends it to the StringBuilder.
543+
/// </summary>
544+
/// <param name="sb">The StringBuilder to append to.</param>
545+
/// <param name="readFromConfig">The ReadFrom configuration to format.</param>
546+
private static void FormatReadFrom(StringBuilder sb, ReadFrom readFromConfig)
547+
{
548+
Append(sb, OptionKeys.ReadFrom, readFromConfig.Strategy.ToString());
549+
if (!string.IsNullOrWhiteSpace(readFromConfig.Az))
550+
{
551+
Append(sb, OptionKeys.Az, readFromConfig.Az);
552+
}
553+
}
554+
482555
/// <summary>
483556
/// Specify the connection protocol type.
484557
/// </summary>

sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ private ConnectionMultiplexer(ConfigurationOptions configuration, Database db)
169169
_db = db;
170170
}
171171

172-
private static T CreateClientConfigBuilder<T>(ConfigurationOptions configuration)
172+
internal static T CreateClientConfigBuilder<T>(ConfigurationOptions configuration)
173173
where T : ClientConfigurationBuilder<T>, new()
174174
{
175175
T config = new();

sources/Valkey.Glide/ConnectionConfiguration.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ public ReadFrom(ReadFromStrategy strategy, string az)
103103
{
104104
throw new ArgumentException("Availability zone could be set only when using `AzAffinity` or `AzAffinityReplicasAndPrimary` strategy.");
105105
}
106+
if (string.IsNullOrWhiteSpace(az))
107+
{
108+
throw new ArgumentException("Availability zone cannot be empty or whitespace");
109+
}
106110
Strategy = strategy;
107111
Az = az;
108112
}

sources/Valkey.Glide/Internals/FFI.methods.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
22

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

79
namespace Valkey.Glide.Internals;

0 commit comments

Comments
 (0)