-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathConnectionStringParser.cs
More file actions
151 lines (120 loc) · 5.39 KB
/
Copy pathConnectionStringParser.cs
File metadata and controls
151 lines (120 loc) · 5.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
using System.Globalization;
using SquidStd.Database.Abstractions.Types.Data;
namespace SquidStd.Database.Connection;
/// <summary>
/// Parses URI-style connection strings ("scheme://...") into a provider and native connection string.
/// </summary>
public static class ConnectionStringParser
{
/// <summary>
/// Parses the given URI connection string.
/// </summary>
/// <param name="connectionString">The URI connection string.</param>
/// <param name="sqliteBaseDirectory">Base directory used to resolve relative SQLite file paths; null keeps them verbatim.</param>
/// <returns>The parsed provider and native connection string.</returns>
public static ParsedConnection Parse(string connectionString, string? sqliteBaseDirectory = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
var schemeEnd = connectionString.IndexOf("://", StringComparison.Ordinal);
if (schemeEnd <= 0)
{
throw new FormatException($"Connection string '{connectionString}' is not a valid URI.");
}
var scheme = connectionString[..schemeEnd].ToLowerInvariant();
var remainder = connectionString[(schemeEnd + 3)..];
var provider = ResolveProvider(scheme);
if (provider == DatabaseProviderType.Sqlite)
{
var filePath = ResolveSqlitePath(remainder, sqliteBaseDirectory);
return new(provider, $"Data Source={filePath ?? remainder}", filePath);
}
return new(provider, BuildServer(provider, remainder));
}
private static string BuildServer(DatabaseProviderType provider, string remainder)
{
// Split "[user[:pass]@]host[:port]/database[?query]" into authority and path.
var slash = remainder.IndexOf('/', StringComparison.Ordinal);
if (slash < 0)
{
throw new FormatException("Connection string requires a database name.");
}
var authority = remainder[..slash];
var pathAndQuery = remainder[(slash + 1)..];
var query = pathAndQuery.IndexOf('?', StringComparison.Ordinal);
var database = (query < 0 ? pathAndQuery : pathAndQuery[..query]).Trim('/');
if (string.IsNullOrEmpty(database))
{
throw new FormatException("Connection string requires a database name.");
}
var (user, password, hostPort) = SplitAuthority(authority);
var (host, port) = SplitHostPort(hostPort);
if (string.IsNullOrEmpty(host))
{
throw new FormatException("Connection string requires a host.");
}
return provider switch
{
DatabaseProviderType.Postgres =>
$"Host={host};Port={port ?? 5432};Username={user};Password={password};Database={database}",
DatabaseProviderType.MySql =>
$"Server={host};Port={port ?? 3306};Uid={user};Pwd={password};Database={database}",
DatabaseProviderType.SqlServer =>
$"Server={host},{port ?? 1433};User Id={user};Password={password};Database={database};TrustServerCertificate=true",
_ => throw new NotSupportedException($"Unsupported provider {provider}.")
};
}
private static string? ResolveSqlitePath(string remainder, string? baseDirectory)
{
if (string.IsNullOrWhiteSpace(remainder))
{
throw new FormatException("SQLite connection string requires a file path or ':memory:'.");
}
if (string.Equals(remainder, ":memory:", StringComparison.OrdinalIgnoreCase))
{
return null;
}
if (!Path.IsPathRooted(remainder) && !string.IsNullOrWhiteSpace(baseDirectory))
{
return Path.GetFullPath(Path.Combine(baseDirectory, remainder));
}
return remainder;
}
private static DatabaseProviderType ResolveProvider(string scheme)
=> scheme switch
{
"sqlite" => DatabaseProviderType.Sqlite,
"postgres" or "postgresql" => DatabaseProviderType.Postgres,
"sqlserver" or "mssql" => DatabaseProviderType.SqlServer,
"mysql" => DatabaseProviderType.MySql,
_ => throw new NotSupportedException($"Unsupported database scheme '{scheme}'.")
};
private static (string User, string Password, string HostPort) SplitAuthority(string authority)
{
var at = authority.LastIndexOf('@');
if (at < 0)
{
return (string.Empty, string.Empty, authority);
}
var userInfo = authority[..at];
var hostPort = authority[(at + 1)..];
var separator = userInfo.IndexOf(':', StringComparison.Ordinal);
if (separator < 0)
{
return (Uri.UnescapeDataString(userInfo), string.Empty, hostPort);
}
var user = Uri.UnescapeDataString(userInfo[..separator]);
var password = Uri.UnescapeDataString(userInfo[(separator + 1)..]);
return (user, password, hostPort);
}
private static (string Host, int? Port) SplitHostPort(string hostPort)
{
var separator = hostPort.IndexOf(':', StringComparison.Ordinal);
if (separator < 0)
{
return (hostPort, null);
}
var host = hostPort[..separator];
var port = int.Parse(hostPort[(separator + 1)..], CultureInfo.InvariantCulture);
return (host, port);
}
}