< Summary - Envilder .NET SDK

Information
Class: Envilder.Application.Envilder
Assembly: Envilder
File(s): /home/runner/work/envilder/envilder/src/sdks/dotnet/Application/Envilder.cs
Tag: 299_25910610327
Line coverage
83%
Covered lines: 59
Uncovered lines: 12
Coverable lines: 71
Total lines: 281
Line coverage: 83%
Branch coverage
75%
Covered branches: 18
Total branches: 24
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ResolveFile(...)100%11100%
ResolveFileAsync()100%11100%
Load(...)100%11100%
LoadAsync()100%11100%
ResolveFile(...)100%22100%
ResolveFileAsync()0%620%
Load(...)50%2275%
LoadAsync()0%620%
ResolveEnvSource(...)90%101092.86%
FromMapFile(...)100%22100%
ValidateFileExists(...)100%22100%
ReadFileAsync()100%11100%
ValidateFilePath(...)100%22100%

File(s)

/home/runner/work/envilder/envilder/src/sdks/dotnet/Application/Envilder.cs

#LineLine coverage
 1namespace Envilder.Application;
 2
 3using global::Envilder.Infrastructure;
 4using System;
 5using System.Collections.Generic;
 6using System.IO;
 7using System.Threading;
 8using System.Threading.Tasks;
 9
 10/// <summary>
 11/// One-liner facade for resolving and injecting secrets from a map file.
 12/// <para>
 13/// Use <see cref="ResolveFile(string)"/> to resolve secrets, <see cref="Load(string)"/>
 14/// to resolve and inject into <see cref="System.Environment"/>, or
 15/// <see cref="FromMapFile(string)"/> for fluent configuration.
 16/// </para>
 17/// </summary>
 18/// <example>
 19/// <code>
 20/// // Resolve + inject in one call
 21/// Envilder.Load("envilder.json");
 22///
 23/// // Resolve without injecting
 24/// var secrets = Envilder.ResolveFile("envilder.json");
 25///
 26/// // Fluent builder with provider override
 27/// var secrets = Envilder.FromMapFile("envilder.json")
 28///     .WithProvider(SecretProviderType.Azure)
 29///     .WithVaultUrl("https://my-vault.vault.azure.net")
 30///     .Resolve();
 31/// </code>
 32/// </example>
 33public static class Envilder
 34{
 35  /// <summary>
 36  /// Reads the map file at <paramref name="filePath"/>, resolves every secret from
 37  /// the configured provider, and returns the results as a dictionary.
 38  /// </summary>
 39  /// <param name="filePath">Path to the JSON map file on disk.</param>
 40  /// <returns>Resolved secrets keyed by environment variable name.</returns>
 41  /// <exception cref="ArgumentException">When <paramref name="filePath"/> is null or whitespace.</exception>
 42  /// <exception cref="FileNotFoundException">When the file does not exist.</exception>
 43  public static IReadOnlyDictionary<string, string> ResolveFile(string filePath)
 44  {
 145    ValidateFilePath(filePath);
 146    var json = File.ReadAllText(filePath);
 147    var parser = new MapFileParser();
 148    var mapFile = parser.Parse(json);
 149    var provider = SecretProviderFactory.Create(mapFile.Config);
 150    var client = new EnvilderClient(provider);
 151    return new Dictionary<string, string>(client.ResolveSecrets(mapFile));
 52  }
 53
 54  /// <summary>
 55  /// Asynchronously reads the map file at <paramref name="filePath"/>, resolves every
 56  /// secret from the configured provider, and returns the results as a dictionary.
 57  /// </summary>
 58  /// <param name="filePath">Path to the JSON map file on disk.</param>
 59  /// <param name="cancellationToken">Optional cancellation token.</param>
 60  /// <returns>Resolved secrets keyed by environment variable name.</returns>
 61  /// <exception cref="ArgumentException">When <paramref name="filePath"/> is null or whitespace.</exception>
 62  /// <exception cref="FileNotFoundException">When the file does not exist.</exception>
 63  public static async Task<IReadOnlyDictionary<string, string>> ResolveFileAsync(
 64    string filePath,
 65    CancellationToken cancellationToken = default)
 66  {
 167    ValidateFilePath(filePath);
 168    var json = await ReadFileAsync(filePath, cancellationToken).ConfigureAwait(false);
 169    var parser = new MapFileParser();
 170    var mapFile = parser.Parse(json);
 171    var provider = SecretProviderFactory.Create(mapFile.Config);
 172    var client = new EnvilderClient(provider);
 173    var secrets = await client.ResolveSecretsAsync(mapFile, cancellationToken).ConfigureAwait(false);
 174    return new Dictionary<string, string>(secrets);
 175  }
 76
 77  /// <summary>
 78  /// Resolves secrets from the map file and injects them as process-level
 79  /// environment variables via <see cref="System.Environment.SetEnvironmentVariable(string, string)"/>.
 80  /// </summary>
 81  /// <param name="filePath">Path to the JSON map file on disk.</param>
 82  /// <returns>Resolved secrets keyed by environment variable name.</returns>
 83  /// <exception cref="ArgumentException">When <paramref name="filePath"/> is null or whitespace.</exception>
 84  /// <exception cref="FileNotFoundException">When the file does not exist.</exception>
 85  public static IReadOnlyDictionary<string, string> Load(string filePath)
 86  {
 187    var secrets = ResolveFile(filePath);
 188    EnvilderClient.InjectIntoEnvironment(secrets);
 189    return secrets;
 90  }
 91
 92  /// <summary>
 93  /// Asynchronously resolves secrets from the map file and injects them as
 94  /// process-level environment variables.
 95  /// </summary>
 96  /// <param name="filePath">Path to the JSON map file on disk.</param>
 97  /// <param name="cancellationToken">Optional cancellation token.</param>
 98  /// <returns>Resolved secrets keyed by environment variable name.</returns>
 99  /// <exception cref="ArgumentException">When <paramref name="filePath"/> is null or whitespace.</exception>
 100  /// <exception cref="FileNotFoundException">When the file does not exist.</exception>
 101  public static async Task<IReadOnlyDictionary<string, string>> LoadAsync(
 102    string filePath,
 103    CancellationToken cancellationToken = default)
 104  {
 1105    var secrets = await ResolveFileAsync(filePath, cancellationToken).ConfigureAwait(false);
 1106    EnvilderClient.InjectIntoEnvironment(secrets);
 1107    return secrets;
 1108  }
 109
 110  /// <summary>
 111  /// Resolves secrets using environment-based routing. Looks up <paramref name="environment"/>
 112  /// in <paramref name="envMapping"/>; if the value is a file path, resolves from that file.
 113  /// Returns an empty dictionary when the environment maps to <see langword="null"/> or is absent.
 114  /// </summary>
 115  /// <param name="environment">The current environment name (e.g. "production").</param>
 116  /// <param name="envMapping">Maps environment names to map-file paths (or <see langword="null"/> to skip).</param>
 117  /// <returns>Resolved secrets, or an empty dictionary.</returns>
 118  /// <exception cref="ArgumentException">When <paramref name="environment"/> is null or whitespace.</exception>
 119  public static IReadOnlyDictionary<string, string> ResolveFile(
 120    string environment,
 121    IDictionary<string, string?> envMapping)
 122  {
 1123    var filePath = ResolveEnvSource(environment, envMapping);
 1124    if (filePath is null)
 125    {
 1126      return new Dictionary<string, string>();
 127    }
 128
 1129    return ResolveFile(filePath);
 130  }
 131
 132  /// <summary>
 133  /// Asynchronously resolves secrets using environment-based routing.
 134  /// </summary>
 135  /// <param name="environment">The current environment name.</param>
 136  /// <param name="envMapping">Maps environment names to map-file paths (or <see langword="null"/> to skip).</param>
 137  /// <param name="cancellationToken">Optional cancellation token.</param>
 138  /// <returns>Resolved secrets, or an empty dictionary.</returns>
 139  public static async Task<IReadOnlyDictionary<string, string>> ResolveFileAsync(
 140    string environment,
 141    IDictionary<string, string?> envMapping,
 142    CancellationToken cancellationToken = default)
 143  {
 0144    var filePath = ResolveEnvSource(environment, envMapping);
 0145    if (filePath is null)
 146    {
 0147      return new Dictionary<string, string>();
 148    }
 149
 0150    return await ResolveFileAsync(filePath, cancellationToken).ConfigureAwait(false);
 0151  }
 152
 153  /// <summary>
 154  /// Resolves secrets using environment-based routing and injects them as
 155  /// process-level environment variables.
 156  /// </summary>
 157  /// <param name="environment">The current environment name.</param>
 158  /// <param name="envMapping">Maps environment names to map-file paths (or <see langword="null"/> to skip).</param>
 159  /// <returns>Resolved secrets, or an empty dictionary.</returns>
 160  /// <exception cref="ArgumentException">When <paramref name="environment"/> is null or whitespace.</exception>
 161  public static IReadOnlyDictionary<string, string> Load(
 162    string environment,
 163    IDictionary<string, string?> envMapping)
 164  {
 1165    var filePath = ResolveEnvSource(environment, envMapping);
 1166    if (filePath is null)
 167    {
 0168      return new Dictionary<string, string>();
 169    }
 170
 1171    return Load(filePath);
 172  }
 173
 174  /// <summary>
 175  /// Asynchronously resolves secrets using environment-based routing and injects them
 176  /// as process-level environment variables.
 177  /// </summary>
 178  /// <param name="environment">The current environment name.</param>
 179  /// <param name="envMapping">Maps environment names to map-file paths (or <see langword="null"/> to skip).</param>
 180  /// <param name="cancellationToken">Optional cancellation token.</param>
 181  /// <returns>Resolved secrets, or an empty dictionary.</returns>
 182  public static async Task<IReadOnlyDictionary<string, string>> LoadAsync(
 183    string environment,
 184    IDictionary<string, string?> envMapping,
 185    CancellationToken cancellationToken = default)
 186  {
 0187    var filePath = ResolveEnvSource(environment, envMapping);
 0188    if (filePath is null)
 189    {
 0190      return new Dictionary<string, string>();
 191    }
 192
 0193    return await LoadAsync(filePath, cancellationToken).ConfigureAwait(false);
 0194  }
 195
 196  private static string? ResolveEnvSource(
 197    string environment,
 198    IDictionary<string, string?> envMapping)
 199  {
 1200    if (string.IsNullOrWhiteSpace(environment))
 201    {
 1202      throw new ArgumentException("Environment name cannot be null or empty.", nameof(environment));
 203    }
 204
 1205    if (envMapping is null)
 206    {
 0207      throw new ArgumentNullException(nameof(envMapping));
 208    }
 209
 1210    environment = environment.Trim();
 211
 1212    if (!envMapping.TryGetValue(environment, out var filePath))
 213    {
 1214      return null;
 215    }
 216
 1217    if (filePath is null)
 218    {
 1219      return null;
 220    }
 221
 1222    if (string.IsNullOrWhiteSpace(filePath))
 223    {
 1224      throw new ArgumentException(
 1225        $"Map file path for environment '{environment}' cannot be empty or whitespace.",
 1226        nameof(envMapping));
 227    }
 228
 1229    return filePath;
 230  }
 231
 232  /// <summary>
 233  /// Returns a fluent <see cref="EnvilderBuilder"/> for the given map file.
 234  /// Chain <see cref="EnvilderBuilder.WithProvider"/>, <see cref="EnvilderBuilder.WithProfile"/>,
 235  /// or <see cref="EnvilderBuilder.WithVaultUrl"/> before calling <see cref="EnvilderBuilder.Resolve"/>
 236  /// or <see cref="EnvilderBuilder.Inject"/>.
 237  /// </summary>
 238  /// <param name="filePath">Path to the JSON map file on disk.</param>
 239  /// <returns>A builder for configuring overrides and resolving secrets.</returns>
 240  /// <exception cref="ArgumentException">When <paramref name="filePath"/> is null or whitespace.</exception>
 241  public static EnvilderBuilder FromMapFile(string filePath)
 242  {
 1243    if (string.IsNullOrWhiteSpace(filePath))
 244    {
 1245      throw new ArgumentException("File path cannot be null or empty.", nameof(filePath));
 246    }
 247
 1248    return new EnvilderBuilder(filePath);
 249  }
 250
 251  internal static void ValidateFileExists(string filePath)
 252  {
 1253    if (!File.Exists(filePath))
 254    {
 1255      throw new FileNotFoundException($"Map file not found: {filePath}", filePath);
 256    }
 1257  }
 258
 259  // StreamReader.ReadToEndAsync(CancellationToken) is not available on netstandard2.0.
 260  // We check cancellation before and after the read to honour the token as closely as possible.
 261  internal static async Task<string> ReadFileAsync(
 262    string filePath,
 263    CancellationToken cancellationToken)
 264  {
 1265    cancellationToken.ThrowIfCancellationRequested();
 1266    using var reader = new StreamReader(filePath);
 1267    var content = await reader.ReadToEndAsync().ConfigureAwait(false);
 1268    cancellationToken.ThrowIfCancellationRequested();
 1269    return content;
 1270  }
 271
 272  private static void ValidateFilePath(string filePath)
 273  {
 1274    if (string.IsNullOrWhiteSpace(filePath))
 275    {
 1276      throw new ArgumentException("File path cannot be null or empty.", nameof(filePath));
 277    }
 278
 1279    ValidateFileExists(filePath);
 1280  }
 281}