-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #92 from MrDave1999/feature/issue_73
Added support for binding a configuration class with the keys of the .env file
- Loading branch information
Showing
8 changed files
with
290 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace DotEnv.Core | ||
{ | ||
/// <summary> | ||
/// The exception that is thrown when the binder encounters one or more errors. | ||
/// </summary> | ||
public class BinderException : Exception | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="BinderException" /> class. | ||
/// </summary> | ||
public BinderException() | ||
{ | ||
|
||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="BinderException" /> class with the a specified error message. | ||
/// </summary> | ||
/// <param name="message">The message that describes the error.</param> | ||
public BinderException(string message) : base(message) | ||
{ | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
using static DotEnv.Core.ExceptionMessages; | ||
|
||
namespace DotEnv.Core | ||
{ | ||
/// <inheritdoc cref="IEnvBinder" /> | ||
public class EnvBinder : IEnvBinder | ||
{ | ||
/// <summary> | ||
/// Allows access to the configuration options for the binder. | ||
/// </summary> | ||
private readonly EnvBinderOptions _configuration = new(); | ||
|
||
/// <summary> | ||
/// Allows access to the errors container of the binder. | ||
/// </summary> | ||
private readonly EnvValidationResult _validationResult = new(); | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="EnvBinder" /> class. | ||
/// </summary> | ||
public EnvBinder() | ||
{ | ||
|
||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="EnvBinder" /> class with environment variables provider. | ||
/// </summary> | ||
/// <param name="provider">The environment variables provider.</param> | ||
public EnvBinder(IEnvironmentVariablesProvider provider) | ||
{ | ||
_configuration.EnvVars = provider; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public TSettings Bind<TSettings>() where TSettings : new() | ||
=> Bind<TSettings>(out _); | ||
|
||
/// <inheritdoc /> | ||
public TSettings Bind<TSettings>(out EnvValidationResult result) where TSettings : new() | ||
{ | ||
var settings = new TSettings(); | ||
var type = typeof(TSettings); | ||
result = _validationResult; | ||
foreach (PropertyInfo property in type.GetProperties()) | ||
{ | ||
var envKeyAttribute = (EnvKeyAttribute)Attribute.GetCustomAttribute(property, typeof(EnvKeyAttribute)); | ||
var variableName = envKeyAttribute is not null ? envKeyAttribute.Name : property.Name; | ||
var retrievedValue = _configuration.EnvVars[variableName]; | ||
|
||
if (retrievedValue is null) | ||
{ | ||
var errorMsg = envKeyAttribute is not null ? string.Format(KeyAssignedToPropertyIsNotSet, type.Name, property.Name, envKeyAttribute.Name) | ||
: string.Format(PropertyDoesNotMatchConfigKeyMessage, property.Name); | ||
_validationResult.Add(errorMsg); | ||
continue; | ||
} | ||
|
||
try | ||
{ | ||
property.SetValue(settings, Convert.ChangeType(retrievedValue, property.PropertyType)); | ||
} | ||
catch (FormatException) | ||
{ | ||
_validationResult.Add(errorMsg: string.Format(FailedConvertConfigurationValueMessage, variableName, property.PropertyType.Name, retrievedValue, property.PropertyType.Name)); | ||
} | ||
} | ||
|
||
if(_validationResult.HasError()) | ||
throw new BinderException(message: _validationResult.ErrorMessages); | ||
|
||
return settings; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace DotEnv.Core | ||
{ | ||
/// <summary> | ||
/// Represents the options for configuring various behaviors of the binder. | ||
/// </summary> | ||
public class EnvBinderOptions | ||
{ | ||
/// <summary> | ||
/// Gets or sets the environment variables provider. | ||
/// </summary> | ||
public IEnvironmentVariablesProvider EnvVars { get; set; } = new DefaultEnvironmentProvider(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace DotEnv.Core | ||
{ | ||
/// <summary> | ||
/// Represents the key of a .env file that is assigned to a property. | ||
/// </summary> | ||
public class EnvKeyAttribute : Attribute | ||
{ | ||
/// <summary> | ||
/// Gets the name of the key the property is mapped to. | ||
/// </summary> | ||
public string Name { get; } | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="EnvKeyAttribute" /> class. | ||
/// </summary> | ||
public EnvKeyAttribute() | ||
{ | ||
|
||
} | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="EnvKeyAttribute" /> class with the name of the key. | ||
/// </summary> | ||
/// <param name="name">The name of the key the property is mapped to.</param> | ||
public EnvKeyAttribute(string name) | ||
{ | ||
Name = name; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace DotEnv.Core | ||
{ | ||
/// <summary> | ||
/// Allows binding strongly typed objects to configuration values. | ||
/// </summary> | ||
public interface IEnvBinder | ||
{ | ||
/// <param name="result">The result contains the errors found by the binder.</param> | ||
/// <inheritdoc cref="Bind()" /> | ||
TSettings Bind<TSettings>(out EnvValidationResult result) where TSettings : new(); | ||
|
||
/// <summary> | ||
/// Binds the instance of the environment variables provider to a new instance of type TSettings. | ||
/// </summary> | ||
/// <typeparam name="TSettings">The type of the new instance to bind.</typeparam> | ||
/// <exception cref="BinderException"> | ||
/// If the binder encounters one or more errors. | ||
/// </exception> | ||
/// <returns>The new instance of TSettings.</returns> | ||
TSettings Bind<TSettings>() where TSettings : new(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
namespace DotEnv.Core.Tests.Binder; | ||
|
||
public class AppSettings | ||
{ | ||
[EnvKey("BIND_JWT_SECRET")] | ||
public string JwtSecret { get; set; } | ||
|
||
[EnvKey("BIND_TOKEN_ID")] | ||
public string TokenId { get; set; } | ||
|
||
[EnvKey("BIND_RACE_TIME")] | ||
public int RaceTime { get; set; } | ||
|
||
public string BindSecretKey { get; set; } | ||
public string BindJwtSecret { get; set; } | ||
} | ||
|
||
public class SettingsExample1 | ||
{ | ||
public string SecretKey { get; set; } | ||
} | ||
|
||
public class SettingsExample2 | ||
{ | ||
[EnvKey("SECRET_KEY")] | ||
public string SecretKey { get; set; } | ||
} | ||
|
||
public class SettingsExample3 | ||
{ | ||
[EnvKey("BIND_WEATHER_ID")] | ||
public int WeatherId { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
namespace DotEnv.Core.Tests.Binder; | ||
|
||
[TestClass] | ||
public class EnvBinderTests | ||
{ | ||
[TestMethod] | ||
public void Bind_WhenPropertiesAreLinkedToTheDefaultProviderInstance_ShouldReturnsSettingsInstance() | ||
{ | ||
SetEnvironmentVariable("BIND_JWT_SECRET", "12example"); | ||
SetEnvironmentVariable("BIND_TOKEN_ID", "e32d"); | ||
SetEnvironmentVariable("BIND_RACE_TIME", "23"); | ||
SetEnvironmentVariable("BindSecretKey", "12example"); | ||
SetEnvironmentVariable("BindJwtSecret", "secret123"); | ||
|
||
var settings = new EnvBinder().Bind<AppSettings>(); | ||
|
||
Assert.AreEqual(expected: "12example", actual: settings.JwtSecret); | ||
Assert.AreEqual(expected: "e32d", actual: settings.TokenId); | ||
Assert.AreEqual(expected: 23, actual: settings.RaceTime); | ||
Assert.AreEqual(expected: "12example", actual: settings.BindSecretKey); | ||
Assert.AreEqual(expected: "secret123", actual: settings.BindJwtSecret); | ||
} | ||
|
||
[TestMethod] | ||
public void Bind_WhenPropertiesAreLinkedToTheCustomProviderInstance_ShouldReturnsSettingsInstance() | ||
{ | ||
var customProvider = new CustomEnvironmentVariablesProvider(); | ||
customProvider["BIND_JWT_SECRET"] = "13example"; | ||
customProvider["BIND_TOKEN_ID"] = "e31d"; | ||
customProvider["BIND_RACE_TIME"] = "24"; | ||
customProvider["BindSecretKey"] = "13example"; | ||
customProvider["BindJwtSecret"] = "secret124"; | ||
|
||
var settings = new EnvBinder(customProvider).Bind<AppSettings>(); | ||
|
||
Assert.AreEqual(expected: "13example", actual: settings.JwtSecret); | ||
Assert.AreEqual(expected: "e31d", actual: settings.TokenId); | ||
Assert.AreEqual(expected: 24, actual: settings.RaceTime); | ||
Assert.AreEqual(expected: "13example", actual: settings.BindSecretKey); | ||
Assert.AreEqual(expected: "secret124", actual: settings.BindJwtSecret); | ||
} | ||
|
||
[TestMethod] | ||
public void Bind_WhenPropertyDoesNotMatchConfigurationKey_ShouldThrowBinderException() | ||
{ | ||
var binder = new EnvBinder(); | ||
|
||
void action() => binder.Bind<SettingsExample1>(); | ||
|
||
var ex = Assert.ThrowsException<BinderException>(action); | ||
StringAssert.Contains(ex.Message, string.Format(PropertyDoesNotMatchConfigKeyMessage, "SecretKey")); | ||
} | ||
|
||
[TestMethod] | ||
public void Bind_WhenKeyAssignedToThePropertyIsNotSet_ShouldThrowBinderException() | ||
{ | ||
var binder = new EnvBinder(); | ||
|
||
void action() => binder.Bind<SettingsExample2>(); | ||
|
||
var ex = Assert.ThrowsException<BinderException>(action); | ||
StringAssert.Contains(ex.Message, string.Format(KeyAssignedToPropertyIsNotSet, "SettingsExample2", "SecretKey", "SECRET_KEY")); | ||
} | ||
|
||
[TestMethod] | ||
public void Bind_WhenConfigurationValueCannotBeConvertedToAnotherDataType_ShouldThrowBinderException() | ||
{ | ||
var binder = new EnvBinder(); | ||
SetEnvironmentVariable("BIND_WEATHER_ID", "This is not an int"); | ||
|
||
void action() => binder.Bind<SettingsExample3>(); | ||
|
||
var ex = Assert.ThrowsException<BinderException>(action); | ||
StringAssert.Contains(ex.Message, string.Format(FailedConvertConfigurationValueMessage, "BIND_WEATHER_ID", "Int32", "This is not an int", "Int32")); | ||
} | ||
} |