connection string/url parsing and generation in the server configuration dialog

This commit is contained in:
eelke 2025-08-31 10:12:22 +02:00
parent a5cb6ef7d4
commit 1d53ca2fc2
11 changed files with 410 additions and 62 deletions

View file

@ -38,30 +38,42 @@ public sealed class NpgsqlCodec : IConnectionStringCodec
// Hosts and Ports
if (dict.TryGetValue("Host", out var hostVal) || dict.TryGetValue("Server", out hostVal) || dict.TryGetValue("Servers", out hostVal))
{
var hosts = SplitList(hostVal).ToList();
List<ushort?> portsPerHost = new();
var rawHosts = SplitList(hostVal).ToList();
var hosts = new List<string>(rawHosts.Count);
var portsPerHost = new List<ushort?>(rawHosts.Count);
// First, extract inline ports from each host entry (e.g., host:5432 or [::1]:5432)
foreach (var raw in rawHosts)
{
ParseHostPort(raw, out var hostOnly, out var inlinePort);
hosts.Add(hostOnly);
portsPerHost.Add(inlinePort);
}
// Then, merge values from Port key: single port applies to all hosts missing a port;
// list of ports applies 1:1 for hosts that still miss a port. Inline ports take precedence.
if (dict.TryGetValue("Port", out var portVal))
{
var ports = SplitList(portVal).ToList();
if (ports.Count == 1 && ushort.TryParse(ports[0], out var singlePort))
if (ports.Count == 1 && ushort.TryParse(ports[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var singlePort))
{
foreach (var _ in hosts) portsPerHost.Add(singlePort);
for (int i = 0; i < portsPerHost.Count; i++)
if (!portsPerHost[i].HasValue)
portsPerHost[i] = singlePort;
}
else if (ports.Count == hosts.Count)
{
foreach (var p in ports)
for (int i = 0; i < ports.Count; i++)
{
if (ushort.TryParse(p, NumberStyles.Integer, CultureInfo.InvariantCulture, out var up))
portsPerHost.Add(up);
else
portsPerHost.Add(null);
if (!portsPerHost[i].HasValue && ushort.TryParse(ports[i], NumberStyles.Integer, CultureInfo.InvariantCulture, out var up))
portsPerHost[i] = up;
}
}
}
for (int i = 0; i < hosts.Count; i++)
{
ushort? port = i < portsPerHost.Count ? portsPerHost[i] : null;
descriptor.AddHost(hosts[i], port);
descriptor.AddHost(hosts[i], i < portsPerHost.Count ? portsPerHost[i] : null);
}
}
@ -164,6 +176,42 @@ public sealed class NpgsqlCodec : IConnectionStringCodec
return s.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
private static void ParseHostPort(string hostPart, out string host, out ushort? port)
{
host = hostPart;
port = null;
if (string.IsNullOrWhiteSpace(hostPart)) return;
// IPv6 in brackets: [::1]:5432
if (hostPart[0] == '[')
{
int end = hostPart.IndexOf(']');
if (end > 0)
{
host = hostPart.Substring(1, end - 1);
if (end + 1 < hostPart.Length && hostPart[end + 1] == ':')
{
var ps = hostPart.Substring(end + 2);
if (ushort.TryParse(ps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var p))
port = p;
}
}
return;
}
// Non-IPv6: split on last ':' and ensure right side is numeric
int colon = hostPart.LastIndexOf(':');
if (colon > 0 && colon < hostPart.Length - 1)
{
var ps = hostPart.Substring(colon + 1);
if (ushort.TryParse(ps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var p))
{
host = hostPart.Substring(0, colon);
port = p;
}
}
}
private static bool TryGetFirst(Dictionary<string, string> dict, out string value, params string[] keys)
{
foreach (var k in keys)