Commit 6718f8a9 authored by Marc Gravell's avatar Marc Gravell

Issue 25: improved handling of configuration strings

parent 59e54f53
......@@ -42,7 +42,7 @@ public void AsyncTasksReportFailureIfServerUnavailable()
Assert.IsTrue(c.IsFaulted, "faulted");
var ex = c.Exception.InnerExceptions.Single();
Assert.IsInstanceOf<RedisConnectionException>(ex);
Assert.AreEqual("No connection is available to service this operation: SADD", ex.Message);
Assert.AreEqual("No connection is available to service this operation: SADD AsyncTasksReportFailureIfServerUnavailable", ex.Message);
}
}
#endif
......
......@@ -293,7 +293,7 @@ public void GetWithExpiry(bool exists, bool hasExpiry)
}
}
[Test]
[ExpectedException(typeof(RedisServerException), ExpectedMessage = "ERR Operation against a key holding the wrong kind of value")]
[ExpectedException(typeof(RedisServerException), ExpectedMessage = "WRONGTYPE Operation against a key holding the wrong kind of value")]
public void GetWithExpiryWrongTypeAsync()
{
using (var conn = Create())
......@@ -315,7 +315,7 @@ public void GetWithExpiryWrongTypeAsync()
}
[Test]
[ExpectedException(typeof(RedisServerException), ExpectedMessage = "ERR Operation against a key holding the wrong kind of value")]
[ExpectedException(typeof(RedisServerException), ExpectedMessage = "WRONGTYPE Operation against a key holding the wrong kind of value")]
public void GetWithExpiryWrongTypeSync()
{
using (var conn = Create())
......
......@@ -11,6 +11,7 @@ public void Basic()
{
var config = ConfigurationOptions.Parse(".,$PING=p");
Assert.AreEqual(1, config.EndPoints.Count);
config.SetDefaultPorts();
Assert.Contains(new DnsEndPoint(".",6379), config.EndPoints);
var map = config.CommandMap;
Assert.AreEqual("$PING=p", map.ToString());
......
using NUnit.Framework;
using System;
namespace StackExchange.Redis.Tests.Issues
{
[TestFixture]
public class Issue25 : TestBase
{
[Test]
public void CaseInsensitive()
{
var options = ConfigurationOptions.Parse("ssl=true");
Assert.IsTrue(options.Ssl);
Assert.AreEqual("ssl=True", options.ToString());
options = ConfigurationOptions.Parse("SSL=TRUE");
Assert.IsTrue(options.Ssl);
Assert.AreEqual("ssl=True", options.ToString());
}
[Test]
public void UnkonwnKeywordHandling_Ignore()
{
var options = ConfigurationOptions.Parse("ssl2=true", true);
}
[Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Keyword 'ssl2' is not supported")]
public void UnkonwnKeywordHandling_ExplicitFail()
{
var options = ConfigurationOptions.Parse("ssl2=true", false);
}
[Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Keyword 'ssl2' is not supported")]
public void UnkonwnKeywordHandling_ImplicitFail()
{
var options = ConfigurationOptions.Parse("ssl2=true");
}
}
}
......@@ -72,6 +72,7 @@
<Compile Include="Config.cs" />
<Compile Include="FloatingPoint.cs" />
<Compile Include="Issues\BGSAVEResponse.cs" />
<Compile Include="Issues\Issue25.cs" />
<Compile Include="Issues\Issue6.cs" />
<Compile Include="Issues\SO22786599.cs" />
<Compile Include="Keys.cs" />
......
......@@ -6,7 +6,7 @@
using System.Net.Security;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
namespace StackExchange.Redis
{
......@@ -32,12 +32,68 @@ public sealed class ConfigurationOptions : ICloneable
{
internal const string DefaultTieBreaker = "__Booksleeve_TieBreak", DefaultConfigurationChannel = "__Booksleeve_MasterChanged";
private const string AllowAdminPrefix = "allowAdmin=", SyncTimeoutPrefix = "syncTimeout=",
ServiceNamePrefix = "serviceName=", ClientNamePrefix = "name=", KeepAlivePrefix = "keepAlive=",
VersionPrefix = "version=", ConnectTimeoutPrefix = "connectTimeout=", PasswordPrefix = "password=",
TieBreakerPrefix = "tiebreaker=", WriteBufferPrefix = "writeBuffer=", sslPrefix = "ssl=", SslHostPrefix = "sslHost=",
ConfigChannelPrefix = "configChannel=", AbortOnConnectFailPrefix = "abortConnect=", ResolveDnsPrefix = "resolveDns=",
ChannelPrefixPrefix = "channelPrefix=", ProxyPrefix = "proxy=";
private static class OptionKeys
{
public static int ParseInt32(string key, string value, int minValue = int.MinValue, int maxValue = int.MaxValue)
{
int tmp;
if (!Format.TryParseInt32(value, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires an integer value");
if (tmp < minValue) throw new ArgumentOutOfRangeException("Keyword '" + key + "' has a minimum value of " + minValue);
if (tmp > maxValue) throw new ArgumentOutOfRangeException("Keyword '" + key + "' has a maximum value of " + maxValue);
return tmp;
}
internal static bool ParseBoolean(string key, string value)
{
bool tmp;
if (!Format.TryParseBoolean(value, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a boolean value");
return tmp;
}
internal static Version ParseVersion(string key, string value)
{
Version tmp;
if (!System.Version.TryParse(value, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a version value");
return tmp;
}
internal static Proxy ParseProxy(string key, string value)
{
Proxy tmp;
if (!Enum.TryParse(value, true, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a proxy value");
return tmp;
}
internal static void Unknown(string key)
{
throw new ArgumentException("Keyword '" + key + "' is not supported");
}
internal const string AllowAdmin = "allowAdmin", SyncTimeout = "syncTimeout",
ServiceName = "serviceName", ClientName = "name", KeepAlive = "keepAlive",
Version = "version", ConnectTimeout = "connectTimeout", Password = "password",
TieBreaker = "tiebreaker", WriteBuffer = "writeBuffer", Ssl = "ssl", SslHost = "sslHost",
ConfigChannel = "configChannel", AbortOnConnectFail = "abortConnect", ResolveDns = "resolveDns",
ChannelPrefix = "channelPrefix", Proxy = "proxy";
private static readonly Dictionary<string, string> normalizedOptions = new[]
{
AllowAdmin, SyncTimeout,
ServiceName, ClientName, KeepAlive,
Version, ConnectTimeout, Password,
TieBreaker, WriteBuffer, Ssl, SslHost,
ConfigChannel, AbortOnConnectFail, ResolveDns,
ChannelPrefix, Proxy
}.ToDictionary(x => x, StringComparer.InvariantCultureIgnoreCase);
public static string TryNormalize(string value)
{
string tmp;
if(value != null && normalizedOptions.TryGetValue(value, out tmp))
{
return tmp ?? "";
}
return value ?? "";
}
}
private readonly EndPointCollection endpoints = new EndPointCollection();
......@@ -199,7 +255,16 @@ public CommandMap CommandMap
public static ConfigurationOptions Parse(string configuration)
{
var options = new ConfigurationOptions();
options.DoParse(configuration);
options.DoParse(configuration, false);
return options;
}
/// <summary>
/// Parse the configuration from a comma-delimited configuration string
/// </summary>
public static ConfigurationOptions Parse(string configuration, bool ignoreUnknown)
{
var options = new ConfigurationOptions();
options.DoParse(configuration, ignoreUnknown);
return options;
}
......@@ -256,23 +321,23 @@ public override string ToString()
{
Append(sb, Format.ToString(endpoint));
}
Append(sb, ClientNamePrefix, clientName);
Append(sb, ServiceNamePrefix, serviceName);
Append(sb, KeepAlivePrefix, keepAlive);
Append(sb, SyncTimeoutPrefix, syncTimeout);
Append(sb, AllowAdminPrefix, allowAdmin);
Append(sb, VersionPrefix, defaultVersion);
Append(sb, ConnectTimeoutPrefix, connectTimeout);
Append(sb, PasswordPrefix, password);
Append(sb, TieBreakerPrefix, tieBreaker);
Append(sb, WriteBufferPrefix, writeBuffer);
Append(sb, sslPrefix, ssl);
Append(sb, SslHostPrefix, sslHost);
Append(sb, ConfigChannelPrefix, configChannel);
Append(sb, AbortOnConnectFailPrefix, abortOnConnectFail);
Append(sb, ResolveDnsPrefix, resolveDns);
Append(sb, ChannelPrefixPrefix, (string)ChannelPrefix);
Append(sb, ProxyPrefix, proxy);
Append(sb, OptionKeys.ClientName, clientName);
Append(sb, OptionKeys.ServiceName, serviceName);
Append(sb, OptionKeys.KeepAlive, keepAlive);
Append(sb, OptionKeys.SyncTimeout, syncTimeout);
Append(sb, OptionKeys.AllowAdmin, allowAdmin);
Append(sb, OptionKeys.Version, defaultVersion);
Append(sb, OptionKeys.ConnectTimeout, connectTimeout);
Append(sb, OptionKeys.Password, password);
Append(sb, OptionKeys.TieBreaker, tieBreaker);
Append(sb, OptionKeys.WriteBuffer, writeBuffer);
Append(sb, OptionKeys.Ssl, ssl);
Append(sb, OptionKeys.SslHost, sslHost);
Append(sb, OptionKeys.ConfigChannel, configChannel);
Append(sb, OptionKeys.AbortOnConnectFail, abortOnConnectFail);
Append(sb, OptionKeys.ResolveDns, resolveDns);
Append(sb, OptionKeys.ChannelPrefix, (string)ChannelPrefix);
Append(sb, OptionKeys.Proxy, proxy);
if(commandMap != null) commandMap.AppendDeltas(sb);
return sb.ToString();
}
......@@ -347,7 +412,11 @@ static void Append(StringBuilder sb, string prefix, object value)
if (!string.IsNullOrWhiteSpace(s))
{
if (sb.Length != 0) sb.Append(',');
sb.Append(prefix).Append(s);
if(!string.IsNullOrEmpty(prefix))
{
sb.Append(prefix).Append('=');
}
sb.Append(s);
}
}
......@@ -372,7 +441,7 @@ void Clear()
object ICloneable.Clone() { return Clone(); }
private void DoParse(string configuration)
private void DoParse(string configuration, bool ignoreUnknown)
{
Clear();
if (!string.IsNullOrWhiteSpace(configuration))
......@@ -390,97 +459,78 @@ private void DoParse(string configuration)
int idx = option.IndexOf('=');
if (idx > 0)
{
var key = option.Substring(0, idx).Trim();
var value = option.Substring(idx + 1).Trim();
if (IsOption(option, SyncTimeoutPrefix))
{
int tmp;
if (Format.TryParseInt32(value.Trim(), out tmp) && tmp > 0) SyncTimeout = tmp;
}
else if (IsOption(option, AllowAdminPrefix))
{
bool tmp;
if (Format.TryParseBoolean(value.Trim(), out tmp)) AllowAdmin = tmp;
}
else if (IsOption(option, AbortOnConnectFailPrefix))
{
bool tmp;
if (Format.TryParseBoolean(value.Trim(), out tmp)) AbortOnConnectFail = tmp;
}
else if (IsOption(option, ResolveDnsPrefix))
{
bool tmp;
if (Format.TryParseBoolean(value.Trim(), out tmp)) ResolveDns = tmp;
}
else if (IsOption(option, ServiceNamePrefix))
{
ServiceName = value.Trim();
}
else if (IsOption(option, ClientNamePrefix))
{
ClientName = value.Trim();
}
else if (IsOption(option, ChannelPrefixPrefix))
{
ChannelPrefix = value.Trim();
}
else if (IsOption(option, ConfigChannelPrefix))
{
ConfigurationChannel = value.Trim();
}
else if (IsOption(option, KeepAlivePrefix))
{
int tmp;
if (Format.TryParseInt32(value.Trim(), out tmp)) KeepAlive = tmp;
}
else if (IsOption(option, ConnectTimeoutPrefix))
{
int tmp;
if (Format.TryParseInt32(value.Trim(), out tmp)) ConnectTimeout = tmp;
}
else if (IsOption(option, VersionPrefix))
{
Version tmp;
if (Version.TryParse(value.Trim(), out tmp)) DefaultVersion = tmp;
}
else if (IsOption(option, PasswordPrefix))
{
Password = value.Trim();
}
else if (IsOption(option, TieBreakerPrefix))
{
TieBreaker = value.Trim();
}
else if (IsOption(option, sslPrefix))
{
bool tmp;
if (Format.TryParseBoolean(value.Trim(), out tmp)) Ssl = tmp;
}
else if (IsOption(option, SslHostPrefix))
{
SslHost = value.Trim();
}
else if (IsOption(option, WriteBufferPrefix))
{
int tmp;
if (Format.TryParseInt32(value.Trim(), out tmp)) WriteBuffer = tmp;
} else if(IsOption(option, ProxyPrefix))
{
Proxy tmp;
if (Enum.TryParse(option, true, out tmp)) Proxy = tmp;
}
else if(option[0]=='$')
{
RedisCommand cmd;
option = option.Substring(1, idx-1);
if (Enum.TryParse(option, true, out cmd))
{
if (map == null) map = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
map[option] = value;
}
}
else
switch (OptionKeys.TryNormalize(key))
{
ConnectionMultiplexer.TraceWithoutContext("Unknown configuration option:" + option);
case OptionKeys.SyncTimeout:
SyncTimeout = OptionKeys.ParseInt32(key, value, minValue: 1);
break;
case OptionKeys.AllowAdmin:
AllowAdmin = OptionKeys.ParseBoolean(key, value);
break;
case OptionKeys.AbortOnConnectFail:
AbortOnConnectFail = OptionKeys.ParseBoolean(key, value);
break;
case OptionKeys.ResolveDns:
ResolveDns = OptionKeys.ParseBoolean(key, value);
break;
case OptionKeys.ServiceName:
ServiceName = value;
break;
case OptionKeys.ClientName:
ClientName = value;
break;
case OptionKeys.ChannelPrefix:
ChannelPrefix = value;
break;
case OptionKeys.ConfigChannel:
ConfigurationChannel = value;
break;
case OptionKeys.KeepAlive:
KeepAlive = OptionKeys.ParseInt32(key, value);
break;
case OptionKeys.ConnectTimeout:
ConnectTimeout = OptionKeys.ParseInt32(key, value);
break;
case OptionKeys.Version:
DefaultVersion = OptionKeys.ParseVersion(key, value);
break;
case OptionKeys.Password:
Password = value;
break;
case OptionKeys.TieBreaker:
TieBreaker = value;
break;
case OptionKeys.Ssl:
Ssl = OptionKeys.ParseBoolean(key, value);
break;
case OptionKeys.SslHost:
SslHost = value;
break;
case OptionKeys.WriteBuffer:
WriteBuffer = OptionKeys.ParseInt32(key, value);
break;
case OptionKeys.Proxy:
Proxy = OptionKeys.ParseProxy(key, value);
break;
default:
if (!string.IsNullOrEmpty(key) && key[0] == '$')
{
RedisCommand cmd;
var cmdName = option.Substring(1, idx - 1);
if (Enum.TryParse(cmdName, true, out cmd))
{
if (map == null) map = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
map[cmdName] = value;
}
}
else
{
if(!ignoreUnknown) OptionKeys.Unknown(key);
}
break;
}
}
else
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment