added ServerConfigurationMapping
split up Abstractions so we have one type per file.
This commit is contained in:
parent
b7631ecdd0
commit
a5cb6ef7d4
13 changed files with 431 additions and 76 deletions
|
|
@ -1,76 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using FluentResults;
|
||||
using Npgsql;
|
||||
|
||||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
public enum ConnStringFormat
|
||||
{
|
||||
Libpq,
|
||||
Npgsql,
|
||||
Url,
|
||||
Jdbc
|
||||
}
|
||||
|
||||
public sealed class HostEndpoint
|
||||
{
|
||||
public string Host { get; init; } = string.Empty;
|
||||
public ushort? Port { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Canonical, format-agnostic representation of a PostgreSQL connection.
|
||||
/// Keep minimal fields for broad interoperability; store extras in Properties.
|
||||
/// </summary>
|
||||
public sealed class ConnectionDescriptor
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
|
||||
// Primary hosts (support multi-host). If empty, implies localhost default.
|
||||
public IReadOnlyList<HostEndpoint> Hosts { get; init; } = new List<HostEndpoint>();
|
||||
|
||||
public string? Database { get; init; }
|
||||
public string? Username { get; init; }
|
||||
public string? Password { get; init; }
|
||||
|
||||
public SslMode? SslMode { get; init; }
|
||||
|
||||
// Common optional fields
|
||||
public string? ApplicationName { get; init; }
|
||||
public int? TimeoutSeconds { get; init; } // connect_timeout
|
||||
|
||||
// Additional parameters preserved across conversions
|
||||
public IReadOnlyDictionary<string, string> Properties { get; init; } =
|
||||
new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Codec for a specific connection string format (parse and format only for its own format).
|
||||
/// Do not implement format specifics yet; provide interface only.
|
||||
/// </summary>
|
||||
public interface IConnectionStringCodec
|
||||
{
|
||||
ConnStringFormat Format { get; }
|
||||
string FormatName { get; }
|
||||
|
||||
// Parse input in this codec's format into a descriptor.
|
||||
Result<ConnectionDescriptor> TryParse(string input);
|
||||
|
||||
// Format a descriptor into this codec's format.
|
||||
Result<string> TryFormat(ConnectionDescriptor descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// High-level service to detect, parse, format and convert between formats.
|
||||
/// Implementations will compose specific codecs.
|
||||
/// </summary>
|
||||
public interface IConnectionStringService
|
||||
{
|
||||
Result<ConnStringFormat> DetectFormat(string input);
|
||||
|
||||
Result<ConnectionDescriptor> ParseToDescriptor(string input);
|
||||
|
||||
Result<string> FormatFromDescriptor(ConnectionDescriptor descriptor, ConnStringFormat targetFormat);
|
||||
|
||||
Result<string> Convert(string input, ConnStringFormat targetFormat);
|
||||
}
|
||||
9
pgLabII.PgUtils/ConnectionStrings/ConnStringFormat.cs
Normal file
9
pgLabII.PgUtils/ConnectionStrings/ConnStringFormat.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
public enum ConnStringFormat
|
||||
{
|
||||
Libpq,
|
||||
Npgsql,
|
||||
Url,
|
||||
Jdbc
|
||||
}
|
||||
29
pgLabII.PgUtils/ConnectionStrings/ConnectionDescriptor.cs
Normal file
29
pgLabII.PgUtils/ConnectionStrings/ConnectionDescriptor.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using Npgsql;
|
||||
|
||||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
/// <summary>
|
||||
/// Canonical, format-agnostic representation of a PostgreSQL connection.
|
||||
/// Keep minimal fields for broad interoperability; store extras in Properties.
|
||||
/// </summary>
|
||||
public sealed class ConnectionDescriptor
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
|
||||
// Primary hosts (support multi-host). If empty, implies localhost default.
|
||||
public IReadOnlyList<HostEndpoint> Hosts { get; init; } = new List<HostEndpoint>();
|
||||
|
||||
public string? Database { get; init; }
|
||||
public string? Username { get; init; }
|
||||
public string? Password { get; init; }
|
||||
|
||||
public SslMode? SslMode { get; init; }
|
||||
|
||||
// Common optional fields
|
||||
public string? ApplicationName { get; init; }
|
||||
public int? TimeoutSeconds { get; init; } // connect_timeout
|
||||
|
||||
// Additional parameters preserved across conversions
|
||||
public IReadOnlyDictionary<string, string> Properties { get; init; } =
|
||||
new Dictionary<string, string>();
|
||||
}
|
||||
9
pgLabII.PgUtils/ConnectionStrings/HostEndpoint.cs
Normal file
9
pgLabII.PgUtils/ConnectionStrings/HostEndpoint.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
public sealed class HostEndpoint
|
||||
{
|
||||
public string Host { get; init; } = string.Empty;
|
||||
public ushort? Port { get; init; }
|
||||
}
|
||||
19
pgLabII.PgUtils/ConnectionStrings/IConnectionStringCodec.cs
Normal file
19
pgLabII.PgUtils/ConnectionStrings/IConnectionStringCodec.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using FluentResults;
|
||||
|
||||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
/// <summary>
|
||||
/// Codec for a specific connection string format (parse and format only for its own format).
|
||||
/// Do not implement format specifics yet; provide interface only.
|
||||
/// </summary>
|
||||
public interface IConnectionStringCodec
|
||||
{
|
||||
ConnStringFormat Format { get; }
|
||||
string FormatName { get; }
|
||||
|
||||
// Parse input in this codec's format into a descriptor.
|
||||
Result<ConnectionDescriptor> TryParse(string input);
|
||||
|
||||
// Format a descriptor into this codec's format.
|
||||
Result<string> TryFormat(ConnectionDescriptor descriptor);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using FluentResults;
|
||||
|
||||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
/// <summary>
|
||||
/// High-level service to detect, parse, format and convert between formats.
|
||||
/// Implementations will compose specific codecs.
|
||||
/// </summary>
|
||||
public interface IConnectionStringService
|
||||
{
|
||||
Result<ConnStringFormat> DetectFormat(string input);
|
||||
|
||||
Result<ConnectionDescriptor> ParseToDescriptor(string input);
|
||||
|
||||
Result<string> FormatFromDescriptor(ConnectionDescriptor descriptor, ConnStringFormat targetFormat);
|
||||
|
||||
Result<string> Convert(string input, ConnStringFormat targetFormat);
|
||||
}
|
||||
|
|
@ -8,6 +8,21 @@ using Npgsql;
|
|||
|
||||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
/// <summary>
|
||||
/// Parser/formatter for Npgsql-style .NET connection strings. We intentionally do not
|
||||
/// rely on NpgsqlConnectionStringBuilder here because:
|
||||
/// - We need a lossless, format-agnostic round-trip to our ConnectionDescriptor, including
|
||||
/// unknown/extension keys and per-host port lists. NpgsqlConnectionStringBuilder normalizes
|
||||
/// names, may drop unknown keys or coerce values, which breaks lossless conversions.
|
||||
/// - We support multi-host with per-host ports and want to preserve the original textual
|
||||
/// representation across conversions. The builder flattens/rewrites these details.
|
||||
/// - We aim to keep pgLabII.PgUtils independent from Npgsql's evolving parsing rules and
|
||||
/// version-specific behaviors to ensure stable UX and deterministic tests.
|
||||
/// - We need symmetric formatting matching our other codecs (libpq/URL/JDBC) and consistent
|
||||
/// quoting rules across formats.
|
||||
/// If required, we still reference Npgsql for enums and interop types, but parsing/formatting
|
||||
/// is done by this small, well-tested custom codec for full control and stability.
|
||||
/// </summary>
|
||||
public sealed class NpgsqlCodec : IConnectionStringCodec
|
||||
{
|
||||
public ConnStringFormat Format => ConnStringFormat.Npgsql;
|
||||
|
|
|
|||
51
pgLabII.PgUtils/ConnectionStrings/PLAN.md
Normal file
51
pgLabII.PgUtils/ConnectionStrings/PLAN.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Connection Strings Plan
|
||||
|
||||
This document tracks the plan for supporting multiple PostgreSQL connection string formats, converting between them, and mapping to/from a canonical model.
|
||||
|
||||
## Current Status (2025-08-30)
|
||||
|
||||
Implemented:
|
||||
- Abstractions: `ConnStringFormat`, `HostEndpoint`, `ConnectionDescriptor`, `IConnectionStringCodec`, `IConnectionStringService`.
|
||||
- Codecs:
|
||||
- `LibpqCodec` (libpq): parse/format; multi-host; `sslmode`, `application_name`, `connect_timeout`; quoting/escaping; preserves extras.
|
||||
- `NpgsqlCodec` (.NET/Npgsql): parse/format; alias recognition; multi-host with single or per-host ports; `SSL Mode`, `Application Name`, `Timeout`; double-quote rules; preserves extras.
|
||||
- Tests for both codecs: parse, format, round-trip, edge quoting.
|
||||
|
||||
Not yet implemented:
|
||||
- URL (postgresql://) codec ✓
|
||||
- JDBC (jdbc:postgresql://) codec
|
||||
- Composite `ConnectionStringService` (detect + convert) ✓
|
||||
- Mapping helpers to/from `ServerConfiguration` ✓
|
||||
|
||||
## Updated Plan
|
||||
|
||||
1. Define canonical model and interfaces for connection strings. ✓
|
||||
2. Establish normalization strategy for parameter aliases and extra `Properties` handling. ✓
|
||||
3. Implement format-specific codecs:
|
||||
- libpq codec (parse/format; multi-host, quoting, sslmode, timeout, extras). ✓
|
||||
- Npgsql codec (parse/format; aliases, multi-host/ports, quoting, ssl mode, timeout, extras). ✓
|
||||
- URL (postgresql://) codec (parse/format; userinfo, host[:port], db, query params, percent-encoding). ✓
|
||||
- JDBC (jdbc:postgresql://) codec (parse/format; hosts, ports, db, properties; URL-like semantics).
|
||||
4. Composite conversion service:
|
||||
- Implement `ConnectionStringService` composing codecs, detecting formats, converting via `ConnectionDescriptor`, and resolving alias priorities.
|
||||
5. Mapping with application model:
|
||||
- Add mapping utilities between `ConnectionDescriptor` and `ServerConfiguration` (primary host/port, db, SSL mode), with sensible defaults.
|
||||
6. Validation and UX:
|
||||
- Validation for malformed inputs & edge cases (mismatched host/port counts, invalid SSL mode, missing db/host, IPv6 bracket handling).
|
||||
- Ensure sensitive fields (password) are masked in logs/preview.
|
||||
7. Tests:
|
||||
- Unit tests for URL and JDBC codecs; composite service detect/convert; mapping functions; cross-format round-trips; edge cases (spaces, quotes, unicode, IPv6, percent-encoding).
|
||||
8. Documentation:
|
||||
- Keep this plan updated and enrich XML docs on codecs/service including alias mappings and quoting/escaping rules per format.
|
||||
|
||||
## Next Small Step
|
||||
|
||||
Implement the URL (postgresql://) codec with unit tests. Scope:
|
||||
- Parse: `postgresql://[user[:password]@]host1[:port1][,hostN[:portN]]/[database]?param=value&...`
|
||||
- Support percent-decoding for user, password, database, and query values.
|
||||
- Handle IPv6 literals in `[::1]` form; allow multiple hosts with optional per-host ports.
|
||||
- Map common params: `sslmode`, `application_name`, `connect_timeout` and preserve other query params in `Properties`.
|
||||
- Format: Build a URL using percent-encoding where required; emit multi-hosts and parameters from `Properties` not already emitted.
|
||||
- Tests: basic parse/format, quoting/percent-encoding, multi-host with mixed ports, round-trips.
|
||||
|
||||
After that, implement the composite `ConnectionStringService` to detect/convert across libpq, Npgsql, and URL formats.
|
||||
Loading…
Add table
Add a link
Reference in a new issue