{"id":47698840,"url":"https://github.com/akikari/fox.configkit","last_synced_at":"2026-04-02T17:00:19.609Z","repository":{"id":338591392,"uuid":"1157123939","full_name":"akikari/Fox.ConfigKit","owner":"akikari","description":"Lightweight .NET configuration validation library with fail-fast startup checks and fluent API","archived":false,"fork":false,"pushed_at":"2026-03-25T09:27:20.000Z","size":156,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-26T12:51:31.055Z","etag":null,"topics":["aspnetcore","configuration","configuration-validation","csharp","dotnet","dotnet-library","fluent-api","microsoftextensionsoptions","nuget-package","option-pattern","result-pattern","secret-detection","startup-validation","validation"],"latest_commit_sha":null,"homepage":"https://github.com/akikari/ConfigKit","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akikari.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-13T13:09:03.000Z","updated_at":"2026-03-25T09:12:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/akikari/Fox.ConfigKit","commit_stats":null,"previous_names":["akikari/fox.configkit"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/akikari/Fox.ConfigKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akikari%2FFox.ConfigKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akikari%2FFox.ConfigKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akikari%2FFox.ConfigKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akikari%2FFox.ConfigKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akikari","download_url":"https://codeload.github.com/akikari/Fox.ConfigKit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akikari%2FFox.ConfigKit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31310998,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspnetcore","configuration","configuration-validation","csharp","dotnet","dotnet-library","fluent-api","microsoftextensionsoptions","nuget-package","option-pattern","result-pattern","secret-detection","startup-validation","validation"],"created_at":"2026-04-02T17:00:16.024Z","updated_at":"2026-04-02T17:00:19.596Z","avatar_url":"https://github.com/akikari.png","language":"C#","readme":"# 🎯 Fox.ConfigKit\r\n\r\n[![.NET](https://img.shields.io/badge/.NET-8%20%7C%209%20%7C%2010-512BD4)](https://dotnet.microsoft.com/)\r\n[![Build and Test](https://img.shields.io/github/actions/workflow/status/akikari/Fox.ResultKit/build-and-test.yml?branch=main\u0026label=build%20and%20test\u0026color=darkgreen)](https://github.com/akikari/Fox.ResultKit/actions/workflows/build-and-test.yml)\r\n[![NuGet](https://img.shields.io/nuget/v/Fox.ConfigKit.svg)](https://www.nuget.org/packages/Fox.ConfigKit/)\r\n[![NuGet Downloads](https://img.shields.io/nuget/dt/Fox.ConfigKit?label=downloads\u0026color=darkgreen)](https://www.nuget.org/packages/Fox.ConfigKit/)\r\n[![License: MIT](https://img.shields.io/badge/license-MIT-orange.svg)](https://opensource.org/licenses/MIT)\r\n[![codecov](https://img.shields.io/codecov/c/github/akikari/Fox.ConfigKit?color=darkgreen\u0026label=codecov)](https://codecov.io/gh/akikari/Fox.ConfigKit)\r\n\r\n\u003e **Lightweight .NET configuration validation library with fail-fast startup validation**\r\n\r\nFox.ConfigKit is a lightweight library for validating `IOptions\u003cT\u003e` configurations at application startup. Catch configuration errors before they cause runtime failures with fluent, expressive validation rules.\r\n\r\n## 📋 Table of Contents\r\n\r\n- [Why Fox.ConfigKit?](#why-foxconfigkit)\r\n- [Features](#features)\r\n- [Installation](#installation)\r\n- [Quick Start](#quick-start)\r\n- [Validation Rules](#validation-rules)\r\n- [Advanced Scenarios](#advanced-scenarios)\r\n- [Sample Application](#sample-application)\r\n- [Design Principles](#design-principles)\r\n- [Requirements](#requirements)\r\n- [Real-World Example](#real-world-example)\r\n- [Contributing](#contributing)\r\n- [License](#license)\r\n- [Author](#author)\r\n- [Project Status](#project-status)\r\n- [Related Projects](#related-projects)\r\n- [Support](#support)\r\n\r\n## 🤔 Why Fox.ConfigKit?\r\n\r\n**Traditional approach:**\r\n```csharp\r\n// ❌ Configuration errors discovered at runtime\r\npublic class MyService\r\n{\r\n    private readonly MyConfig config;\r\n\r\n    public void DoWork()\r\n    {\r\n        // Crashes here if config.ApiUrl is null or invalid!\r\n        var client = new HttpClient { BaseAddress = new Uri(config.ApiUrl) };\r\n    }\r\n}\r\n```\r\n\r\n**Fox.ConfigKit approach:**\r\n```csharp\r\n// ✅ Configuration errors caught at startup\r\nbuilder.Services.AddConfigKit\u003cMyConfig\u003e(\"MyConfig\")\r\n    .NotEmpty(c =\u003e c.ApiUrl, \"API URL is required\")\r\n    .UrlReachable(c =\u003e c.ApiUrl, message: \"API is not reachable\")\r\n    .ValidateOnStartup();\r\n\r\n// Application won't start if configuration is invalid!\r\n```\r\n\r\n## ✨ Features\r\n\r\n- ✅ **Fail-Fast Validation** - Catch configuration errors at application startup\r\n- ✅ **Fluent API** - Intuitive, type-safe configuration validation\r\n- ✅ **Generic Type Support** - Validate any `IComparable\u003cT\u003e` type (int, decimal, DateTime, TimeSpan, etc.)\r\n- ✅ **Conditional Validation** - Apply rules based on environment or other properties\r\n- ✅ **File System Validation** - Verify directories and files exist\r\n- ✅ **Network Validation** - Check URL reachability at startup\r\n- ✅ **Security Validation** - Detect plain-text secrets in configuration\r\n- ✅ **IOptions Integration** - Seamless integration with `Microsoft.Extensions.Options`\r\n- ✅ **No Third-Party Dependencies** - Only uses core Microsoft.Extensions packages\r\n- ✅ **Multi-Targeting** - Supports .NET 8.0, .NET 9.0, and .NET 10.0\r\n\r\n## 📦 Installation\r\n\r\n```bash\r\ndotnet add package Fox.ConfigKit\r\n```\r\n\r\n**NuGet Package Manager:**\r\n```\r\nInstall-Package Fox.ConfigKit\r\n```\r\n\r\n**PackageReference:**\r\n```xml\r\n\u003cPackageReference Include=\"Fox.ConfigKit\" Version=\"1.0.4\" /\u003e\r\n```\r\n\r\n## 🚀 Quick Start\r\n\r\n### 1. Define Your Configuration Class\r\n\r\n```csharp\r\npublic sealed class DatabaseConfig\r\n{\r\n    public string ConnectionString { get; set; } = string.Empty;\r\n    public int MaxPoolSize { get; set; }\r\n    public int CommandTimeoutSeconds { get; set; }\r\n}\r\n```\r\n\r\n### 2. Configure appsettings.json\r\n\r\n```json\r\n{\r\n  \"Database\": {\r\n    \"ConnectionString\": \"Server=localhost;Database=MyDb;User Id=sa;Password=YourPassword123;\",\r\n    \"MaxPoolSize\": 100,\r\n    \"CommandTimeoutSeconds\": 30\r\n  },\r\n  \"Api\": {\r\n    \"BaseUrl\": \"https://api.example.com\",\r\n    \"ApiKey\": \"your-api-key-here\",\r\n    \"TimeoutSeconds\": 30,\r\n    \"MaxRetries\": 3\r\n  },\r\n  \"Logging\": {\r\n    \"LogDirectory\": \"C:\\\\Logs\\\\MyApp\",\r\n    \"MinimumLevel\": \"Information\",\r\n    \"RetentionDays\": 30\r\n  }\r\n}\r\n```\r\n\r\n### 3. Register and Validate in Program.cs\r\n\r\n```csharp\r\nusing Fox.ConfigKit;\r\nusing Fox.ConfigKit.Validation;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\n// Register configuration with validation\r\nbuilder.Services.AddConfigKit\u003cDatabaseConfig\u003e(\"Database\")\r\n    .NotEmpty(c =\u003e c.ConnectionString, \"Connection string is required\")\r\n    .InRange(c =\u003e c.MaxPoolSize, 1, 1000, \"Max pool size must be between 1 and 1000\")\r\n    .InRange(c =\u003e c.CommandTimeoutSeconds, 1, 600, \"Command timeout must be between 1 and 600 seconds\")\r\n    .ValidateOnStartup();\r\n\r\nvar app = builder.Build();\r\napp.Run();\r\n```\r\n\r\n### 4. Inject and Use\r\n\r\n```csharp\r\npublic class MyService\r\n{\r\n    private readonly DatabaseConfig config;\r\n\r\n    public MyService(IOptions\u003cDatabaseConfig\u003e config)\r\n    {\r\n        this.config = config.Value; // Already validated at startup!\r\n    }\r\n}\r\n```\r\n\r\n## 📚 Validation Rules\r\n\r\n### String Validation\r\n\r\n| Method | Description |\r\n|--------|-------------|\r\n| `NotEmpty(selector, message)` | Ensures string is not null or empty |\r\n| `NotNull(selector, message)` | Ensures value is not null |\r\n| `MatchesPattern(selector, regex, message)` | Validates string against regex pattern |\r\n\r\n**Example:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cAppConfig\u003e(\"App\")\r\n    .NotEmpty(c =\u003e c.Name, \"Application name is required\")\r\n    .MatchesPattern(c =\u003e c.Version, @\"^\\d+\\.\\d+\\.\\d+$\", \"Version must be X.Y.Z format\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n### Comparable Value Validation\r\n\r\nAll comparison validation methods support any `IComparable\u003cT\u003e` type including `int`, `decimal`, `DateTime`, `TimeSpan`, and more.\r\n\r\n| Method | Description | Boundary |\r\n|--------|-------------|----------|\r\n| `GreaterThan(selector, min, message)` | Ensures value is greater than minimum | Exclusive (\u003e) |\r\n| `LessThan(selector, max, message)` | Ensures value is less than maximum | Exclusive (\u003c) |\r\n| `Minimum(selector, min, message)` | Ensures value is at least minimum | Inclusive (\u003e=) |\r\n| `Maximum(selector, max, message)` | Ensures value is at most maximum | Inclusive (\u003c=) |\r\n| `InRange(selector, min, max, message)` | Ensures value is within range | Inclusive (\u003e=, \u003c=) |\r\n\r\n**Example with integers:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cApiConfig\u003e(\"Api\")\r\n    .GreaterThan(c =\u003e c.Port, 1024, \"Port must be \u003e 1024\")\r\n    .Maximum(c =\u003e c.Port, 65535, \"Port must be \u003c= 65535\")\r\n    .InRange(c =\u003e c.MaxRetries, 0, 10, \"Max retries must be between 0 and 10\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n**Example with decimals:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cProductConfig\u003e(\"Product\")\r\n    .Minimum(c =\u003e c.Price, 0.01m, \"Price must be at least $0.01\")\r\n    .Maximum(c =\u003e c.Discount, 0.5m, \"Discount cannot exceed 50%\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n**Example with DateTime:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cCampaignConfig\u003e(\"Campaign\")\r\n    .Minimum(c =\u003e c.StartDate, DateTime.Today, \"Campaign must start today or later\")\r\n    .LessThan(c =\u003e c.EndDate, new DateTime(2025, 12, 31), \"Campaign must end before 2025\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n**Example with TimeSpan:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cApiConfig\u003e(\"Api\")\r\n    .GreaterThan(c =\u003e c.Timeout, TimeSpan.Zero, \"Timeout must be positive\")\r\n    .Maximum(c =\u003e c.Timeout, TimeSpan.FromMinutes(5), \"Timeout cannot exceed 5 minutes\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n### Property-to-Property Comparison\r\n\r\nCompare two properties within the same configuration object. Useful for validating date ranges, min/max pairs, and batch processing configurations.\r\n\r\n| Method | Description | Boundary |\r\n|--------|-------------|----------|\r\n| `GreaterThanProperty(selector, compareToSelector, message)` | Ensures property is greater than another property | Exclusive (\u003e) |\r\n| `LessThanProperty(selector, compareToSelector, message)` | Ensures property is less than another property | Exclusive (\u003c) |\r\n| `MinimumProperty(selector, compareToSelector, message)` | Ensures property is at least another property | Inclusive (\u003e=) |\r\n| `MaximumProperty(selector, compareToSelector, message)` | Ensures property is at most another property | Inclusive (\u003c=) |\r\n\r\n**Example with date ranges:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cCampaignConfig\u003e(\"Campaign\")\r\n    .GreaterThanProperty(c =\u003e c.EndDate, c =\u003e c.StartDate, \"End date must be after start date\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n**Example with batch processing:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cMigrationConfig\u003e(\"Migration\")\r\n    .MinimumProperty(c =\u003e c.RecordsPerRun, c =\u003e c.BatchSize, \"RecordsPerRun must be \u003e= BatchSize\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n**Example with price ranges:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cProductConfig\u003e(\"Product\")\r\n    .LessThanProperty(c =\u003e c.MinPrice, c =\u003e c.MaxPrice, \"MinPrice must be less than MaxPrice\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n### File System Validation\r\n\r\n| Method | Description |\r\n|--------|-------------|\r\n| `FileExists(selector, message)` | Validates file exists at path |\r\n| `DirectoryExists(selector, message)` | Validates directory exists at path |\r\n\r\n**Example:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cLoggingConfig\u003e(\"Logging\")\r\n    .NotEmpty(c =\u003e c.LogDirectory, \"Log directory is required\")\r\n    .DirectoryExists(c =\u003e c.LogDirectory, message: \"Log directory does not exist\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n### Network Validation\r\n\r\n| Method | Description |\r\n|--------|-------------|\r\n| `UrlReachable(selector, timeout, message)` | Validates URL is reachable via HTTP HEAD request |\r\n\r\n**Example:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cExternalApiConfig\u003e(\"ExternalApi\")\r\n    .NotEmpty(c =\u003e c.BaseUrl, \"API URL is required\")\r\n    .UrlReachable(c =\u003e c.BaseUrl, timeout: TimeSpan.FromSeconds(10), message: \"API is not reachable\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n### Security Validation\r\n\r\n| Method | Description |\r\n|--------|-------------|\r\n| `NoPlainTextSecrets(selector, message)` | Detects plain-text secrets using pattern matching |\r\n\r\n**Example:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cApiConfig\u003e(\"Api\")\r\n    .NotEmpty(c =\u003e c.ApiKey, \"API key is required\")\r\n    .NoPlainTextSecrets(c =\u003e c.ApiKey, \"API key appears to be a plain-text secret\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n**Detected secret patterns:**\r\n- OpenAI API keys (`sk-...`)\r\n- Google API keys (`AIza...`)\r\n- AWS Access Keys (`AKIA...`)\r\n- Bearer tokens\r\n- Generic Base64 secrets\r\n- Hexadecimal secrets (64 chars)\r\n\r\n### Environment Validation\r\n\r\n| Method | Description |\r\n|--------|-------------|\r\n| `EnvironmentVariableExists(selector, message)` | Validates environment variable exists |\r\n\r\n**Example:**\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cAppConfig\u003e(\"App\")\r\n    .NotEmpty(c =\u003e c.EnvironmentName, \"Environment name is required\")\r\n    .EnvironmentVariableExists(c =\u003e c.RequiredEnvVar, \"Required environment variable not set\")\r\n    .ValidateOnStartup();\r\n```\r\n\r\n## 🔥 Advanced Scenarios\r\n\r\n### Conditional Validation\r\n\r\nApply validation rules conditionally based on configuration values:\r\n\r\n```csharp\r\nbuilder.Services.AddConfigKit\u003cSecurityConfig\u003e(\"Security\")\r\n    .NotEmpty(c =\u003e c.Environment, \"Environment is required\")\r\n    .When(c =\u003e c.Environment == \"Production\", b =\u003e\r\n    {\r\n        // These rules only apply in production\r\n        b.NotEmpty(c =\u003e c.CertificatePath, \"Certificate is required in production\")\r\n         .FileExists(c =\u003e c.CertificatePath, message: \"Certificate file not found\");\r\n    })\r\n    .When(c =\u003e c.RequireHttps, b =\u003e\r\n    {\r\n        // These rules only apply when HTTPS is required\r\n        b.FileExists(c =\u003e c.CertificatePath, message: \"HTTPS requires certificate\");\r\n    })\r\n    .ValidateOnStartup();\r\n```\r\n\r\n### Cross-Property Validation\r\n\r\n```csharp\r\npublic sealed class DatabaseConfig\r\n{\r\n    public string ConnectionString { get; set; } = string.Empty;\r\n    public bool RequireSsl { get; set; }\r\n}\r\n\r\nbuilder.Services.AddConfigKit\u003cDatabaseConfig\u003e(\"Database\")\r\n    .NotEmpty(c =\u003e c.ConnectionString, \"Connection string is required\")\r\n    .When(c =\u003e c.RequireSsl, b =\u003e\r\n    {\r\n        b.MatchesPattern(c =\u003e c.ConnectionString, \"Encrypt=True|Encrypt=true\", \r\n            \"SSL required but connection string does not specify Encrypt=True\");\r\n    })\r\n    .ValidateOnStartup();\r\n```\r\n\r\n### Environment-Specific Configuration Structures\r\n\r\nWhen different environments require structurally different configurations (e.g., local authentication in DEV, OAuth in PROD/TEST), you have two approaches:\r\n\r\n#### Approach 1: Union Configuration with Conditional Validation\r\n\r\nUse a single configuration class with nullable properties for environment-specific fields:\r\n\r\n```csharp\r\npublic sealed class AuthConfig\r\n{\r\n    public string Type { get; set; } = string.Empty; // \"Local\" or \"OAuth\"\r\n\r\n    // DEV environment only\r\n    public string? LocalUsername { get; set; }\r\n    public string? LocalPassword { get; set; }\r\n\r\n    // PROD/TEST environment only\r\n    public string? OAuthClientId { get; set; }\r\n    public string? OAuthClientSecret { get; set; }\r\n    public string? OAuthAuthority { get; set; }\r\n}\r\n\r\nbuilder.Services.AddConfigKit\u003cAuthConfig\u003e(\"Auth\")\r\n    .NotEmpty(c =\u003e c.Type, \"Authentication type is required\")\r\n    .When(c =\u003e c.Type == \"Local\", b =\u003e\r\n    {\r\n        b.NotEmpty(c =\u003e c.LocalUsername, \"Username required for local auth\")\r\n         .NotEmpty(c =\u003e c.LocalPassword, \"Password required for local auth\");\r\n    })\r\n    .When(c =\u003e c.Type == \"OAuth\", b =\u003e\r\n    {\r\n        b.NotEmpty(c =\u003e c.OAuthClientId, \"ClientId required for OAuth\")\r\n         .NotEmpty(c =\u003e c.OAuthClientSecret, \"ClientSecret required for OAuth\")\r\n         .NotEmpty(c =\u003e c.OAuthAuthority, \"Authority URL required for OAuth\")\r\n         .UrlReachable(c =\u003e c.OAuthAuthority!, TimeSpan.FromSeconds(10), \r\n                      \"OAuth authority not reachable\");\r\n    })\r\n    .ValidateOnStartup();\r\n```\r\n\r\n**Pros:**\r\n- ✅ Single configuration class\r\n- ✅ `appsettings.{Environment}.json` automatically overrides values\r\n- ✅ Works with existing `When()` API\r\n\r\n**Cons:**\r\n- ❌ Nullable properties for environment-specific fields\r\n- ❌ Less type-safe (union type instead of separate types)\r\n\r\n#### Approach 2: Environment-Specific Configuration Classes\r\n\r\nUse separate configuration classes for each environment and register conditionally:\r\n\r\n```csharp\r\npublic interface IAuthConfig \r\n{ \r\n    string Type { get; }\r\n}\r\n\r\npublic sealed class LocalAuthConfig : IAuthConfig\r\n{\r\n    public string Type =\u003e \"Local\";\r\n    public string Username { get; set; } = string.Empty;\r\n    public string Password { get; set; } = string.Empty;\r\n}\r\n\r\npublic sealed class OAuthConfig : IAuthConfig\r\n{\r\n    public string Type =\u003e \"OAuth\";\r\n    public string ClientId { get; set; } = string.Empty;\r\n    public string ClientSecret { get; set; } = string.Empty;\r\n    public string Authority { get; set; } = string.Empty;\r\n}\r\n\r\n// Program.cs - conditional registration based on environment\r\nif (builder.Environment.IsDevelopment())\r\n{\r\n    builder.Services.AddConfigKit\u003cLocalAuthConfig\u003e(\"Auth\")\r\n        .NotEmpty(c =\u003e c.Username, \"Username required\")\r\n        .NotEmpty(c =\u003e c.Password, \"Password required\")\r\n        .ValidateOnStartup();\r\n\r\n    builder.Services.AddSingleton\u003cIAuthConfig\u003e(sp =\u003e \r\n        sp.GetRequiredService\u003cIOptions\u003cLocalAuthConfig\u003e\u003e().Value);\r\n}\r\nelse // Production/Test\r\n{\r\n    builder.Services.AddConfigKit\u003cOAuthConfig\u003e(\"Auth\")\r\n        .NotEmpty(c =\u003e c.ClientId, \"ClientId required\")\r\n        .NotEmpty(c =\u003e c.ClientSecret, \"ClientSecret required\")\r\n        .NotEmpty(c =\u003e c.Authority, \"Authority URL required\")\r\n        .UrlReachable(c =\u003e c.Authority, TimeSpan.FromSeconds(10), \r\n                     \"OAuth authority not reachable\")\r\n        .ValidateOnStartup();\r\n\r\n    builder.Services.AddSingleton\u003cIAuthConfig\u003e(sp =\u003e \r\n        sp.GetRequiredService\u003cIOptions\u003cOAuthConfig\u003e\u003e().Value);\r\n}\r\n```\r\n\r\n**Pros:**\r\n- ✅ Type-safe configuration classes without nullable properties\r\n- ✅ Clear separation of environment-specific concerns\r\n- ✅ Compile-time enforcement of configuration structure\r\n\r\n**Cons:**\r\n- ❌ More complex DI registration (if/else in startup)\r\n- ❌ Requires separate `appsettings.{Environment}.json` files with different schemas\r\n- ❌ Two separate validation builders\r\n\r\n**Recommendation:** Use **Approach 1** for simple environment differences (different values). Use **Approach 2** when environments have fundamentally different authentication mechanisms or infrastructure dependencies.\r\n\r\n### Custom Validation Rules\r\n\r\nCreate custom validation rules by inheriting from `ValidationRuleBase`:\r\n\r\n```csharp\r\nusing Fox.ConfigKit.Validation;\r\n\r\ninternal sealed class CustomRule\u003cT\u003e : ValidationRuleBase, IValidationRule\u003cT\u003e where T : class\r\n{\r\n    private readonly Func\u003cT, string?\u003e getValue;\r\n    private readonly string propertyName;\r\n    private readonly string? message;\r\n\r\n    public CustomRule(Expression\u003cFunc\u003cT, string?\u003e\u003e selector, string? message = null)\r\n    {\r\n        this.getValue = selector.Compile();\r\n        this.propertyName = GetPropertyName(selector);\r\n        this.message = message;\r\n    }\r\n\r\n    public ConfigValidationError? Validate(T options, string sectionName)\r\n    {\r\n        var value = getValue(options);\r\n\r\n        if (string.IsNullOrEmpty(value))\r\n        {\r\n            var key = $\"{sectionName}:{propertyName}\";\r\n            var errorMessage = message ?? $\"{propertyName} is invalid\";\r\n            return new ConfigValidationError(key, errorMessage, value, [\"Provide a valid value\"]);\r\n        }\r\n\r\n        return null;\r\n    }\r\n}\r\n\r\n// Extension method\r\npublic static class CustomValidationExtensions\r\n{\r\n    public static ConfigValidationBuilder\u003cT\u003e MyCustomValidation\u003cT\u003e(\r\n        this ConfigValidationBuilder\u003cT\u003e builder,\r\n        Expression\u003cFunc\u003cT, string?\u003e\u003e selector,\r\n        string? message = null) where T : class\r\n    {\r\n        return builder.AddRule(new CustomRule\u003cT\u003e(selector, message));\r\n    }\r\n}\r\n```\r\n\r\n## 🚂 Result Pattern Integration (Optional)\r\n\r\nFor applications using [Fox.ResultKit](https://github.com/akikari/Fox.ResultKit), the **Fox.ConfigKit.ResultKit** integration package enables functional-style configuration validation with Railway Oriented Programming.\r\n\r\n### Installation\r\n\r\n```bash\r\ndotnet add package Fox.ConfigKit.ResultKit\r\n```\r\n\r\n### Functional Validation (Alternative to ValidateOnStartup)\r\n\r\nInstead of exception-based validation at startup, use `Result\u003cT\u003e` for explicit error handling:\r\n\r\n```csharp\r\nusing Fox.ConfigKit.ResultKit;\r\nusing Fox.ResultKit;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\n// Register configuration WITHOUT ValidateOnStartup()\r\nbuilder.Services.AddConfigKit\u003cApplicationConfig\u003e(\"Application\")\r\n    .NotEmpty(c =\u003e c.Name, \"Application name is required\")\r\n    .Minimum(c =\u003e c.MaxConcurrentRequests, 1)\r\n    .Maximum(c =\u003e c.MaxConcurrentRequests, 1000);\r\n    // ← No .ValidateOnStartup() call!\r\n\r\nbuilder.Services.AddConfigKit\u003cDatabaseConfig\u003e(\"Database\")\r\n    .NotEmpty(c =\u003e c.ConnectionString);\r\n\r\nvar app = builder.Build();\r\n\r\n// Explicit validation with Result pattern\r\nvar appConfig = app.Services.GetRequiredService\u003cIOptions\u003cApplicationConfig\u003e\u003e().Value;\r\nvar dbConfig = app.Services.GetRequiredService\u003cIOptions\u003cDatabaseConfig\u003e\u003e().Value;\r\n\r\n// Composable validation pipeline\r\nvar startupResult = ConfigValidator.Validate\u003cApplicationConfig\u003e()\r\n    .NotEmpty(c =\u003e c.Name, \"Application name is required\")\r\n    .Minimum(c =\u003e c.MaxConcurrentRequests, 1)\r\n    .ToResult(appConfig)\r\n    .Bind(_ =\u003e ConfigValidator.Validate\u003cDatabaseConfig\u003e()\r\n        .NotEmpty(c =\u003e c.ConnectionString, \"Connection string required\")\r\n        .ToResult(dbConfig));\r\n\r\n// Functional error handling\r\nstartupResult.Match(\r\n    onSuccess: _ =\u003e \r\n    {\r\n        app.Logger.LogInformation(\"All configurations valid\");\r\n        app.Run();\r\n    },\r\n    onFailure: error =\u003e \r\n    {\r\n        app.Logger.LogCritical(\"Configuration validation failed: {Error}\", error.Message);\r\n        Environment.Exit(1);\r\n    }\r\n);\r\n```\r\n\r\n### Collecting All Validation Errors\r\n\r\nUse `ToErrorsResult()` to collect **all** validation errors instead of stopping at the first one:\r\n\r\n```csharp\r\nvar validationResult = ConfigValidator.Validate\u003cApplicationConfig\u003e()\r\n    .NotEmpty(c =\u003e c.Name, \"Name is required\")\r\n    .InRange(c =\u003e c.MaxConcurrentRequests, 1, 1000)\r\n    .NotEmpty(c =\u003e c.Version, \"Version is required\")\r\n    .ToErrorsResult(appConfig);\r\n\r\nif (validationResult.IsFailure)\r\n{\r\n    app.Logger.LogCritical(\"Configuration has {Count} errors:\", validationResult.Errors.Count);\r\n\r\n    foreach (var error in validationResult.Errors)\r\n    {\r\n        app.Logger.LogError(\"  - {ErrorMessage}\", error.Message);\r\n    }\r\n\r\n    Environment.Exit(1);\r\n}\r\n\r\napp.Run();\r\n```\r\n\r\n### When to Use Result Pattern vs ValidateOnStartup\r\n\r\n| Scenario | Use `ValidateOnStartup()` | Use `ToResult()` / Result Pattern |\r\n|----------|--------------------------|----------------------------------|\r\n| **Simple ASP.NET Core app** | ✅ Recommended (fail-fast) | ❌ Overkill |\r\n| **Console/Worker apps** | ❌ No DI startup | ✅ Recommended |\r\n| **Complex startup logic** | 🟡 Limited control | ✅ Full control over errors |\r\n| **Multiple configs to validate** | 🟡 Sequential exceptions | ✅ Composable chain with `Bind()` |\r\n| **Need all errors at once** | ❌ Stops at first error | ✅ `ToErrorsResult()` collects all |\r\n| **Unit testing validation** | 🟡 Exception-based asserts | ✅ Result-based asserts |\r\n| **Graceful shutdown on error** | ❌ Exception crash | ✅ `Environment.Exit(1)` |\r\n| **Already using Railway pattern** | 🟡 Inconsistent style | ✅ Consistent functional style |\r\n\r\n### Standalone Validation (Without DI)\r\n\r\nFor console apps, scripts, or unit tests where DI is not available:\r\n\r\n```csharp\r\nusing Fox.ConfigKit.ResultKit;\r\n\r\n// Load configuration from file/environment\r\nvar config = LoadConfigurationFromFile(\"appsettings.json\");\r\n\r\n// Validate without dependency injection\r\nvar result = ConfigValidator.Validate\u003cAppConfig\u003e()\r\n    .NotEmpty(c =\u003e c.Name, \"Name is required\")\r\n    .InRange(c =\u003e c.Port, 1, 65535, \"Invalid port\")\r\n    .ToResult(config);\r\n\r\nreturn result.Match(\r\n    onSuccess: validConfig =\u003e RunApplication(validConfig),\r\n    onFailure: error =\u003e \r\n    {\r\n        Console.WriteLine($\"Configuration error: {error}\");\r\n        return 1; // Exit code\r\n    }\r\n);\r\n```\r\n\r\n**Learn more:** See the [Fox.ConfigKit.ResultKit README](https://github.com/akikari/Fox.ConfigKit/tree/main/src/Fox.ConfigKit.ResultKit) for detailed examples and advanced patterns.\r\n\r\n## 📖 Sample Application\r\n\r\nA comprehensive sample WebApi application is available in the repository demonstrating:\r\n\r\n- ✅ Application-wide configuration validation\r\n- ✅ Database connection string validation\r\n- ✅ External API configuration with URL reachability checks\r\n- ✅ File system validation for log directories\r\n- ✅ Environment-based conditional validation\r\n- ✅ Security configuration with certificate validation\r\n\r\n**Run the sample:**\r\n\r\n```bash\r\ncd samples/Fox.ConfigKit.Samples.WebApi\r\ndotnet run\r\n```\r\n\r\n**Explore:**\r\n- Open `https://localhost:5001/swagger`\r\n- View validated configurations via `/api/configuration/*` endpoints\r\n- See [sample README](samples/Fox.ConfigKit.Samples.WebApi/README.md) for details\r\n\r\n## 🎯 Design Principles\r\n\r\n1. **Fail-Fast** - Catch configuration errors at startup, not at runtime\r\n2. **Explicit Validation** - Make validation rules clear and discoverable\r\n3. **Type-Safe** - Leverage C# type system for compile-time safety\r\n4. **Fluent API** - Intuitive, chainable validation rules\r\n5. **No Third-Party Dependencies** - Only core Microsoft.Extensions packages, no external libraries\r\n6. **Developer-Friendly** - Clear error messages, excellent IntelliSense\r\n\r\n## 🔧 Requirements\r\n\r\n- .NET 8.0 or higher\r\n- C# 12 or higher (for modern language features)\r\n- Nullable reference types enabled (recommended)\r\n\r\n## 🎯 Real-World Example\r\n\r\nSee this package in action within a complete production-grade application: **[Fox.TaskFlow](https://github.com/akikari/Fox.TaskFlow)** - A comprehensive demonstration showcasing real-world integration of seven Fox.*Kit packages in a task management system built with Clean Architecture, SOLID principles, and modern .NET 10 practices.\r\n\r\n## 🤝 Contributing\r\n\r\n**Fox.ConfigKit is intentionally lightweight and feature-focused.** The goal is to remain a simple library with minimal dependencies for configuration validation.\r\n\r\n### What We Welcome\r\n\r\n- ✅ **Bug fixes** - Issues with existing functionality\r\n- ✅ **Documentation improvements** - Clarifications, examples, typo fixes\r\n- ✅ **Performance optimizations** - Without breaking API compatibility\r\n- ✅ **New validation rules** - Following existing patterns\r\n\r\n### What We Generally Do Not Accept\r\n\r\n- ❌ New dependencies or third-party packages\r\n- ❌ Large feature additions that increase complexity\r\n- ❌ Breaking API changes\r\n\r\nIf you want to propose a significant change, please open an issue first to discuss whether it aligns with the project's philosophy.\r\n\r\n### Build Policy\r\n\r\nThe project enforces a **strict build policy** to ensure code quality:\r\n\r\n- ❌ **No errors allowed** - Build must be error-free\r\n- ❌ **No warnings allowed** - All compiler warnings must be resolved\r\n- ❌ **No messages allowed** - Informational messages must be suppressed or addressed\r\n\r\nAll pull requests must pass this requirement.\r\n\r\n### Code Quality Standards\r\n\r\nFox.ValidationKit follows strict coding standards:\r\n\r\n- **Comprehensive unit tests required** (xUnit + FluentAssertions)\r\n- **Maximum test coverage required** - Aim for 100% line and branch coverage. Tests may only be omitted if they would introduce artificial complexity (e.g., testing unreachable code paths, framework internals, or compiler-generated code). Use `[ExcludeFromCodeCoverage]` sparingly and only for justified cases.\r\n- **XML documentation for all public APIs** - Clear, concise documentation with examples\r\n- **Follow Microsoft coding conventions** - See `.github/copilot-instructions.md` for project-specific style\r\n- **Zero warnings, zero errors build policy** - Strict enforcement\r\n\r\n### Code Style\r\n\r\n- Follow the existing code style (see `.github/copilot-instructions.md`)\r\n- Use file-scoped namespaces\r\n- Enable nullable reference types\r\n- Add XML documentation for public APIs\r\n- Write unit tests for new features\r\n- Use expression-bodied members for simple properties\r\n- Auto-properties preferred over backing fields\r\n\r\n### How to Contribute\r\n\r\n1. Fork the repository\r\n2. Create a feature branch from `main`\r\n3. Follow the coding standards in `.github/copilot-instructions.md`\r\n4. Ensure all tests pass (`dotnet test`)\r\n5. Submit a pull request\r\n\r\n## 📝 License\r\n\r\nThis project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.\r\n\r\n## 👤 Author\r\n\r\n**Károly Akácz**\r\n\r\n- GitHub: [@akikari](https://github.com/akikari)\r\n- Repository: [Fox.ConfigKit](https://github.com/akikari/Fox.ConfigKit)\r\n\r\n## 📊 Project Status\r\n\r\n[![NuGet Version](https://img.shields.io/nuget/v/Fox.ConfigKit.svg?label=version\u0026color=blue)](https://www.nuget.org/packages/Fox.ConfigKit/)\r\n\r\nSee [CHANGELOG.md](CHANGELOG.md) for version history.\r\n\r\n## 🔗 Related Projects\r\n\r\n- [Fox.ResultKit](https://github.com/akikari/Fox.ResultKit) - Lightweight Result pattern library for Railway Oriented Programming\r\n- [Fox.ConfigKit.ResultKit](https://www.nuget.org/packages/Fox.ConfigKit.ResultKit) - Integration package for using ConfigKit with Result pattern\r\n\r\n## 📞 Support\r\n\r\nFor issues, questions, or feature requests, please open an issue in the [GitHub repository](https://github.com/akikari/Fox.ConfigKit/issues).\r\n\r\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakikari%2Ffox.configkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakikari%2Ffox.configkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakikari%2Ffox.configkit/lists"}