Skip to content

Commit

Permalink
feat: specific class for configured connection-url factory (#684)
Browse files Browse the repository at this point in the history
* feat: read from appsettings.son
* feat: add class ConfiguredConnectionUrlFactory
  • Loading branch information
Seddryck authored Nov 26, 2023
1 parent a0f74f8 commit a3b70a9
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 16 deletions.
3 changes: 2 additions & 1 deletion DubUrl.Core/ConnectionUrlFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public ConnectionUrlFactory(SchemeMapperBuilder builder)
internal ConnectionUrlFactory(IParser parser, SchemeMapperBuilder builder)
=> (Parser, SchemeMapperBuilder) = (parser, builder);

public virtual ConnectionUrl Instantiate(string url) => new (url, Parser, SchemeMapperBuilder);
public virtual ConnectionUrl Instantiate(string url)
=> new (url, Parser, SchemeMapperBuilder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using NUnit.Framework;
using DubUrl.Extensions.Configuration;
using DubUrl.Mapping;

namespace DubUrl.Extensions.Testing.Configuration
{
public class ConfiguredConnectionUrlFactoryTest
{
[Test()]
public void FromConfiguration_ExistingConnectionString_ValueReturned()
{
var connectionUrl = "mssql://localhost/Customers";
var connectionName = "Customers";
var connectionStrings = new Dictionary<string, string?>
{
[$"ConnectionStrings:{connectionName}"] = connectionUrl
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(connectionStrings)
.Build();

var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder(), config);
Assert.That(factory.InstantiateFromConnectionStrings(connectionName).Url, Is.EqualTo(connectionUrl));
}

[Test()]
public void FromConfiguration_ExistingKeys_ValueReturned()
{
var connectionUrl = "mssql://localhost/Customers";
var key = "Databases:Customers:ConnectionUrl";
var databases = new Dictionary<string, string?>
{
[key] = connectionUrl
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(databases)
.Build();

var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder(), config);
Assert.That(factory.InstantiateFromConfiguration(key.Split(':')).Url, Is.EqualTo(connectionUrl));
}

[Test()]
[TestCase("Foo")]
[TestCase("Databases:Foo")]
[TestCase("Databases:Customers:Foo")]
public void FromConfiguration_NotExistingKeys_Throws(string keys)
{
var connectionUrl = "mssql://localhost/Customers";
var key = "Databases:Customers:ConnectionUrl";
var databases = new Dictionary<string, string?>
{
[key] = connectionUrl
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(databases)
.Build();

var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder(), config);
Assert.Throws<KeyNotFoundException>(() => factory.InstantiateFromConfiguration(keys.Split(':')));
}

[Test()]
public void BindFromConfiguration_ExistingKeyWithDetails_ReturnsValue()
{
var connectionUrl = "mssql://foo:bar@localhost:1234/Customers?foo=1&bar=2";
var key = "Databases:Customers:ConnectionUrl";
var databases = new Dictionary<string, string?>
{
[$"{key}:scheme"] = "mssql",
[$"{key}:host"] = "localhost",
[$"{key}:port"] = "1234",
[$"{key}:username"] = "foo",
[$"{key}:password"] = "bar",
[$"{key}:segments:0"] = "Customers",
[$"{key}:keys:0"] = "foo",
[$"{key}:values:0"] = "1",
[$"{key}:keys:1"] = "bar",
[$"{key}:values:1"] = "2",
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(databases)
.Build();

var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder(), config);
Assert.That(factory.InstantiateWithBind(key.Split(':')).Url, Is.EqualTo(connectionUrl));
}
}
}
56 changes: 56 additions & 0 deletions DubUrl.Extensions/Configuration/ConfiguredConnectionUrlFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DubUrl.Mapping;
using Microsoft.Extensions.Configuration;

namespace DubUrl.Extensions.Configuration
{
public class ConfiguredConnectionUrlFactory : ConnectionUrlFactory
{
private IConfigurationRoot Configuration { get; }

public ConfiguredConnectionUrlFactory(SchemeMapperBuilder builder)
: base(builder)
{
Configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
}

public ConfiguredConnectionUrlFactory(SchemeMapperBuilder builder, IConfigurationRoot config)
: base(builder)
=> Configuration = config;

public ConnectionUrl InstantiateFromConnectionStrings(string name)
=> Instantiate(Configuration.GetConnectionString(name)
?? throw new ArgumentOutOfRangeException(nameof(name), $"Cannot find a connection string named '{name}' in the section 'ConnectionStrings'"));

public ConnectionUrl InstantiateFromConfiguration(string[] keys)
=> Instantiate(GetSection(Configuration, keys).Value
?? throw new NullReferenceException($"Value of the key '{string.Join('.', keys)}' is null."));

public ConnectionUrl InstantiateWithBind(string[] keys)
=> Instantiate(GetSection(Configuration, keys).Get<ConnectionUrlSettings>().ToString());

private static IConfigurationSection GetSection(IConfigurationRoot config, string[] keys)
{
IConfigurationSection? section = null;
if (!keys.Any())
throw new ArgumentOutOfRangeException(nameof(keys), $"The provided keys cannot be an empty array.");
for (int i = 0; i < keys.Length; i++)
{
section = section is not null ? section.GetSection(keys[i]) : config.GetSection(keys[i]);
if (!section.Exists())
if (i == 0)
throw new KeyNotFoundException($"Cannot find the configuration key '{keys[i]}' at the root of configuration.");
else
throw new KeyNotFoundException($"Cannot find the configuration key '{keys[i]}' under the section '{string.Join('.', keys.Take(i))}'.");
}
return section!;
}
}
}
10 changes: 8 additions & 2 deletions DubUrl.Extensions/Configuration/ConnectionUrlSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ public record struct ConnectionUrlSettings
string? Username,
string? Password,
string[]? Segments,
string? Segment,
string[]? Keys,
string[]? Values
)
{
public override string ToString()
public override readonly string ToString()
{
IDictionary<string, string> parameters;
if (Keys is not null || Values is not null)
Expand Down Expand Up @@ -50,12 +51,17 @@ public override string ToString()
builder.UserName = encoder.Encode(Username);
if (!string.IsNullOrEmpty(Password))
builder.Password = encoder.Encode(Password);
if (Segments is not null && Segments.Any() && !string.IsNullOrEmpty(Segment))
throw new InvalidOperationException("Cannot define both an array of segments and a segment.");
if (Segments is not null && Segments.Any())
builder.Path = string.Join('/', Segments.Select(encoder.Encode));
else if (!string.IsNullOrEmpty(Segment))
builder.Path = string.Join('/', Segment.Split('/').Select(encoder.Encode));
if (Segments is not null && Segments.Any())
builder.Path = string.Join('/', Segments.Select(encoder.Encode));
if (parameters.Any())
builder.Query = string.Join("&", parameters.Select(x => $"{encoder.Encode(x.Key)}={encoder.Encode(x.Value)}"));
return builder.ToString();
}

}
}
43 changes: 35 additions & 8 deletions DubUrl.QA/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,61 @@
using DubUrl.Registering;
using NUnit.Framework;
using DubUrl.Extensions.Configuration;
using Microsoft.Extensions.Configuration;

namespace DubUrl.QA
{
public class Configuration
{
private IConfigurationRoot IniConfig { get; set; }

[OneTimeSetUp]
public virtual void SetupFixture()
=> new ProviderFactoriesRegistrator().Register();

{
new ProviderFactoriesRegistrator().Register();
IniConfig = new ConfigurationBuilder().AddIniFile("appsettings.ini").Build();
}

[Test]
public void ReadFromAppSettingsJson_ConnectionStrings()
{
var factory = new ConnectionUrlFactory(new SchemeMapperBuilder());
Assert.That(factory.FromConfiguration("Customers").Url, Is.EqualTo("mssql://localhost/Customers"));
var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder());
Assert.That(factory.InstantiateFromConnectionStrings("Customers").Url, Is.EqualTo("mssql://localhost/Customers"));
}

[Test]
public void ReadFromAppSettingsJson_Key()
{
var factory = new ConnectionUrlFactory(new SchemeMapperBuilder());
Assert.That(factory.FromConfiguration(new[] { "Databases", "Customers", "ConnectionUrl" }).Url, Is.EqualTo("pgsql://127.0.0.1/Customers"));
var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder());
Assert.That(factory.InstantiateFromConfiguration(new[] { "Databases", "Customers", "ConnectionUrl" }).Url, Is.EqualTo("pgsql://127.0.0.1/Customers"));
}

[Test]
public void BindFromAppSettingsJson_Key()
{
var factory = new ConnectionUrlFactory(new SchemeMapperBuilder());
Assert.That(factory.BindFromConfiguration(new[] { "Databases", "Customers", "Details" }).Url, Is.EqualTo("mysql://remote.database.org:1234/myInstance/Customers"));
var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder());
Assert.That(factory.InstantiateWithBind(new[] { "Databases", "Customers", "Details" }).Url, Is.EqualTo("mysql://remote.database.org:1234/myInstance/Customers"));
}

[Test]
public void ReadFromIni_ConnectionStrings()
{
var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder(), IniConfig);
Assert.That(factory.InstantiateFromConnectionStrings("Customers").Url, Is.EqualTo("duckdb://localhost/Customers"));
}

[Test]
public void ReadFromIni_Key()
{
var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder(), IniConfig);
Assert.That(factory.InstantiateFromConfiguration(new[] { "Databases", "Customers" }).Url, Is.EqualTo("sqlite://localhost/Customers"));
}

[Test]
public void BindFromIni_Key()
{
var factory = new ConfiguredConnectionUrlFactory(new SchemeMapperBuilder(), IniConfig);
Assert.That(factory.InstantiateWithBind(new[] { "Details" }).Url, Is.EqualTo("firebird://remote.database.org:1234/myInstance/Customers"));
}
}
}
24 changes: 19 additions & 5 deletions DubUrl.QA/DubUrl.QA.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,35 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="DuckDB.NET.Bindings" Version="0.9.2" />
<PackageReference Include="Dapper" Version="2.1.24" />
<PackageReference Include="DuckDB.NET.Data" Version="0.9.2" />
<PackageReference Include="FirebirdSql.Data.FirebirdClient" Version="10.0.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.2" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MySqlConnector" Version="2.3.1" />
<PackageReference Include="Npgsql" Version="8.0.0" />
<PackageReference Include="NReco.PrestoAdo" Version="1.1.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.7" />
<PackageReference Include="System.Data.Odbc" Version="8.0.0" />
<PackageReference Include="System.Data.OleDb" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.7" />
<PackageReference Include="System.Data.Odbc" Version="8.0.0" />
<PackageReference Include="System.Data.OleDb" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
Expand All @@ -96,6 +107,9 @@
</ItemGroup>

<ItemGroup>
<None Update="appsettings.ini">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
12 changes: 12 additions & 0 deletions DubUrl.QA/appsettings.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[ConnectionStrings]
Customers=duckdb://localhost/Customers

[Databases]
Customers=sqlite://localhost/Customers

[Details]
scheme=firebird
host=remote.database.org
port=1234
segment=myInstance/Customers

0 comments on commit a3b70a9

Please sign in to comment.