An open API service indexing awesome lists of open source software.

https://github.com/akikari/fox.configkit

Lightweight .NET configuration validation library with fail-fast startup checks and fluent API
https://github.com/akikari/fox.configkit

aspnetcore configuration configuration-validation csharp dotnet dotnet-library fluent-api microsoftextensionsoptions nuget-package option-pattern result-pattern secret-detection startup-validation validation

Last synced: 24 days ago
JSON representation

Lightweight .NET configuration validation library with fail-fast startup checks and fluent API

Awesome Lists containing this project

README

          

# 🎯 Fox.ConfigKit

[![.NET](https://img.shields.io/badge/.NET-8%20%7C%209%20%7C%2010-512BD4)](https://dotnet.microsoft.com/)
[![Build and Test](https://img.shields.io/github/actions/workflow/status/akikari/Fox.ResultKit/build-and-test.yml?branch=main&label=build%20and%20test&color=darkgreen)](https://github.com/akikari/Fox.ResultKit/actions/workflows/build-and-test.yml)
[![NuGet](https://img.shields.io/nuget/v/Fox.ConfigKit.svg)](https://www.nuget.org/packages/Fox.ConfigKit/)
[![NuGet Downloads](https://img.shields.io/nuget/dt/Fox.ConfigKit?label=downloads&color=darkgreen)](https://www.nuget.org/packages/Fox.ConfigKit/)
[![License: MIT](https://img.shields.io/badge/license-MIT-orange.svg)](https://opensource.org/licenses/MIT)
[![codecov](https://img.shields.io/codecov/c/github/akikari/Fox.ConfigKit?color=darkgreen&label=codecov)](https://codecov.io/gh/akikari/Fox.ConfigKit)

> **Lightweight .NET configuration validation library with fail-fast startup validation**

Fox.ConfigKit is a lightweight library for validating `IOptions` configurations at application startup. Catch configuration errors before they cause runtime failures with fluent, expressive validation rules.

## πŸ“‹ Table of Contents

- [Why Fox.ConfigKit?](#why-foxconfigkit)
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Validation Rules](#validation-rules)
- [Advanced Scenarios](#advanced-scenarios)
- [Sample Application](#sample-application)
- [Design Principles](#design-principles)
- [Requirements](#requirements)
- [Real-World Example](#real-world-example)
- [Contributing](#contributing)
- [License](#license)
- [Author](#author)
- [Project Status](#project-status)
- [Related Projects](#related-projects)
- [Support](#support)

## πŸ€” Why Fox.ConfigKit?

**Traditional approach:**
```csharp
// ❌ Configuration errors discovered at runtime
public class MyService
{
private readonly MyConfig config;

public void DoWork()
{
// Crashes here if config.ApiUrl is null or invalid!
var client = new HttpClient { BaseAddress = new Uri(config.ApiUrl) };
}
}
```

**Fox.ConfigKit approach:**
```csharp
// βœ… Configuration errors caught at startup
builder.Services.AddConfigKit("MyConfig")
.NotEmpty(c => c.ApiUrl, "API URL is required")
.UrlReachable(c => c.ApiUrl, message: "API is not reachable")
.ValidateOnStartup();

// Application won't start if configuration is invalid!
```

## ✨ Features

- βœ… **Fail-Fast Validation** - Catch configuration errors at application startup
- βœ… **Fluent API** - Intuitive, type-safe configuration validation
- βœ… **Generic Type Support** - Validate any `IComparable` type (int, decimal, DateTime, TimeSpan, etc.)
- βœ… **Conditional Validation** - Apply rules based on environment or other properties
- βœ… **File System Validation** - Verify directories and files exist
- βœ… **Network Validation** - Check URL reachability at startup
- βœ… **Security Validation** - Detect plain-text secrets in configuration
- βœ… **IOptions Integration** - Seamless integration with `Microsoft.Extensions.Options`
- βœ… **No Third-Party Dependencies** - Only uses core Microsoft.Extensions packages
- βœ… **Multi-Targeting** - Supports .NET 8.0, .NET 9.0, and .NET 10.0

## πŸ“¦ Installation

```bash
dotnet add package Fox.ConfigKit
```

**NuGet Package Manager:**
```
Install-Package Fox.ConfigKit
```

**PackageReference:**
```xml

```

## πŸš€ Quick Start

### 1. Define Your Configuration Class

```csharp
public sealed class DatabaseConfig
{
public string ConnectionString { get; set; } = string.Empty;
public int MaxPoolSize { get; set; }
public int CommandTimeoutSeconds { get; set; }
}
```

### 2. Configure appsettings.json

```json
{
"Database": {
"ConnectionString": "Server=localhost;Database=MyDb;User Id=sa;Password=YourPassword123;",
"MaxPoolSize": 100,
"CommandTimeoutSeconds": 30
},
"Api": {
"BaseUrl": "https://api.example.com",
"ApiKey": "your-api-key-here",
"TimeoutSeconds": 30,
"MaxRetries": 3
},
"Logging": {
"LogDirectory": "C:\\Logs\\MyApp",
"MinimumLevel": "Information",
"RetentionDays": 30
}
}
```

### 3. Register and Validate in Program.cs

```csharp
using Fox.ConfigKit;
using Fox.ConfigKit.Validation;

var builder = WebApplication.CreateBuilder(args);

// Register configuration with validation
builder.Services.AddConfigKit("Database")
.NotEmpty(c => c.ConnectionString, "Connection string is required")
.InRange(c => c.MaxPoolSize, 1, 1000, "Max pool size must be between 1 and 1000")
.InRange(c => c.CommandTimeoutSeconds, 1, 600, "Command timeout must be between 1 and 600 seconds")
.ValidateOnStartup();

var app = builder.Build();
app.Run();
```

### 4. Inject and Use

```csharp
public class MyService
{
private readonly DatabaseConfig config;

public MyService(IOptions config)
{
this.config = config.Value; // Already validated at startup!
}
}
```

## πŸ“š Validation Rules

### String Validation

| Method | Description |
|--------|-------------|
| `NotEmpty(selector, message)` | Ensures string is not null or empty |
| `NotNull(selector, message)` | Ensures value is not null |
| `MatchesPattern(selector, regex, message)` | Validates string against regex pattern |

**Example:**

```csharp
builder.Services.AddConfigKit("App")
.NotEmpty(c => c.Name, "Application name is required")
.MatchesPattern(c => c.Version, @"^\d+\.\d+\.\d+$", "Version must be X.Y.Z format")
.ValidateOnStartup();
```

### Comparable Value Validation

All comparison validation methods support any `IComparable` type including `int`, `decimal`, `DateTime`, `TimeSpan`, and more.

| Method | Description | Boundary |
|--------|-------------|----------|
| `GreaterThan(selector, min, message)` | Ensures value is greater than minimum | Exclusive (>) |
| `LessThan(selector, max, message)` | Ensures value is less than maximum | Exclusive (<) |
| `Minimum(selector, min, message)` | Ensures value is at least minimum | Inclusive (>=) |
| `Maximum(selector, max, message)` | Ensures value is at most maximum | Inclusive (<=) |
| `InRange(selector, min, max, message)` | Ensures value is within range | Inclusive (>=, <=) |

**Example with integers:**

```csharp
builder.Services.AddConfigKit("Api")
.GreaterThan(c => c.Port, 1024, "Port must be > 1024")
.Maximum(c => c.Port, 65535, "Port must be <= 65535")
.InRange(c => c.MaxRetries, 0, 10, "Max retries must be between 0 and 10")
.ValidateOnStartup();
```

**Example with decimals:**

```csharp
builder.Services.AddConfigKit("Product")
.Minimum(c => c.Price, 0.01m, "Price must be at least $0.01")
.Maximum(c => c.Discount, 0.5m, "Discount cannot exceed 50%")
.ValidateOnStartup();
```

**Example with DateTime:**

```csharp
builder.Services.AddConfigKit("Campaign")
.Minimum(c => c.StartDate, DateTime.Today, "Campaign must start today or later")
.LessThan(c => c.EndDate, new DateTime(2025, 12, 31), "Campaign must end before 2025")
.ValidateOnStartup();
```

**Example with TimeSpan:**

```csharp
builder.Services.AddConfigKit("Api")
.GreaterThan(c => c.Timeout, TimeSpan.Zero, "Timeout must be positive")
.Maximum(c => c.Timeout, TimeSpan.FromMinutes(5), "Timeout cannot exceed 5 minutes")
.ValidateOnStartup();
```

### Property-to-Property Comparison

Compare two properties within the same configuration object. Useful for validating date ranges, min/max pairs, and batch processing configurations.

| Method | Description | Boundary |
|--------|-------------|----------|
| `GreaterThanProperty(selector, compareToSelector, message)` | Ensures property is greater than another property | Exclusive (>) |
| `LessThanProperty(selector, compareToSelector, message)` | Ensures property is less than another property | Exclusive (<) |
| `MinimumProperty(selector, compareToSelector, message)` | Ensures property is at least another property | Inclusive (>=) |
| `MaximumProperty(selector, compareToSelector, message)` | Ensures property is at most another property | Inclusive (<=) |

**Example with date ranges:**

```csharp
builder.Services.AddConfigKit("Campaign")
.GreaterThanProperty(c => c.EndDate, c => c.StartDate, "End date must be after start date")
.ValidateOnStartup();
```

**Example with batch processing:**

```csharp
builder.Services.AddConfigKit("Migration")
.MinimumProperty(c => c.RecordsPerRun, c => c.BatchSize, "RecordsPerRun must be >= BatchSize")
.ValidateOnStartup();
```

**Example with price ranges:**

```csharp
builder.Services.AddConfigKit("Product")
.LessThanProperty(c => c.MinPrice, c => c.MaxPrice, "MinPrice must be less than MaxPrice")
.ValidateOnStartup();
```

### File System Validation

| Method | Description |
|--------|-------------|
| `FileExists(selector, message)` | Validates file exists at path |
| `DirectoryExists(selector, message)` | Validates directory exists at path |

**Example:**

```csharp
builder.Services.AddConfigKit("Logging")
.NotEmpty(c => c.LogDirectory, "Log directory is required")
.DirectoryExists(c => c.LogDirectory, message: "Log directory does not exist")
.ValidateOnStartup();
```

### Network Validation

| Method | Description |
|--------|-------------|
| `UrlReachable(selector, timeout, message)` | Validates URL is reachable via HTTP HEAD request |

**Example:**

```csharp
builder.Services.AddConfigKit("ExternalApi")
.NotEmpty(c => c.BaseUrl, "API URL is required")
.UrlReachable(c => c.BaseUrl, timeout: TimeSpan.FromSeconds(10), message: "API is not reachable")
.ValidateOnStartup();
```

### Security Validation

| Method | Description |
|--------|-------------|
| `NoPlainTextSecrets(selector, message)` | Detects plain-text secrets using pattern matching |

**Example:**

```csharp
builder.Services.AddConfigKit("Api")
.NotEmpty(c => c.ApiKey, "API key is required")
.NoPlainTextSecrets(c => c.ApiKey, "API key appears to be a plain-text secret")
.ValidateOnStartup();
```

**Detected secret patterns:**
- OpenAI API keys (`sk-...`)
- Google API keys (`AIza...`)
- AWS Access Keys (`AKIA...`)
- Bearer tokens
- Generic Base64 secrets
- Hexadecimal secrets (64 chars)

### Environment Validation

| Method | Description |
|--------|-------------|
| `EnvironmentVariableExists(selector, message)` | Validates environment variable exists |

**Example:**

```csharp
builder.Services.AddConfigKit("App")
.NotEmpty(c => c.EnvironmentName, "Environment name is required")
.EnvironmentVariableExists(c => c.RequiredEnvVar, "Required environment variable not set")
.ValidateOnStartup();
```

## πŸ”₯ Advanced Scenarios

### Conditional Validation

Apply validation rules conditionally based on configuration values:

```csharp
builder.Services.AddConfigKit("Security")
.NotEmpty(c => c.Environment, "Environment is required")
.When(c => c.Environment == "Production", b =>
{
// These rules only apply in production
b.NotEmpty(c => c.CertificatePath, "Certificate is required in production")
.FileExists(c => c.CertificatePath, message: "Certificate file not found");
})
.When(c => c.RequireHttps, b =>
{
// These rules only apply when HTTPS is required
b.FileExists(c => c.CertificatePath, message: "HTTPS requires certificate");
})
.ValidateOnStartup();
```

### Cross-Property Validation

```csharp
public sealed class DatabaseConfig
{
public string ConnectionString { get; set; } = string.Empty;
public bool RequireSsl { get; set; }
}

builder.Services.AddConfigKit("Database")
.NotEmpty(c => c.ConnectionString, "Connection string is required")
.When(c => c.RequireSsl, b =>
{
b.MatchesPattern(c => c.ConnectionString, "Encrypt=True|Encrypt=true",
"SSL required but connection string does not specify Encrypt=True");
})
.ValidateOnStartup();
```

### Environment-Specific Configuration Structures

When different environments require structurally different configurations (e.g., local authentication in DEV, OAuth in PROD/TEST), you have two approaches:

#### Approach 1: Union Configuration with Conditional Validation

Use a single configuration class with nullable properties for environment-specific fields:

```csharp
public sealed class AuthConfig
{
public string Type { get; set; } = string.Empty; // "Local" or "OAuth"

// DEV environment only
public string? LocalUsername { get; set; }
public string? LocalPassword { get; set; }

// PROD/TEST environment only
public string? OAuthClientId { get; set; }
public string? OAuthClientSecret { get; set; }
public string? OAuthAuthority { get; set; }
}

builder.Services.AddConfigKit("Auth")
.NotEmpty(c => c.Type, "Authentication type is required")
.When(c => c.Type == "Local", b =>
{
b.NotEmpty(c => c.LocalUsername, "Username required for local auth")
.NotEmpty(c => c.LocalPassword, "Password required for local auth");
})
.When(c => c.Type == "OAuth", b =>
{
b.NotEmpty(c => c.OAuthClientId, "ClientId required for OAuth")
.NotEmpty(c => c.OAuthClientSecret, "ClientSecret required for OAuth")
.NotEmpty(c => c.OAuthAuthority, "Authority URL required for OAuth")
.UrlReachable(c => c.OAuthAuthority!, TimeSpan.FromSeconds(10),
"OAuth authority not reachable");
})
.ValidateOnStartup();
```

**Pros:**
- βœ… Single configuration class
- βœ… `appsettings.{Environment}.json` automatically overrides values
- βœ… Works with existing `When()` API

**Cons:**
- ❌ Nullable properties for environment-specific fields
- ❌ Less type-safe (union type instead of separate types)

#### Approach 2: Environment-Specific Configuration Classes

Use separate configuration classes for each environment and register conditionally:

```csharp
public interface IAuthConfig
{
string Type { get; }
}

public sealed class LocalAuthConfig : IAuthConfig
{
public string Type => "Local";
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}

public sealed class OAuthConfig : IAuthConfig
{
public string Type => "OAuth";
public string ClientId { get; set; } = string.Empty;
public string ClientSecret { get; set; } = string.Empty;
public string Authority { get; set; } = string.Empty;
}

// Program.cs - conditional registration based on environment
if (builder.Environment.IsDevelopment())
{
builder.Services.AddConfigKit("Auth")
.NotEmpty(c => c.Username, "Username required")
.NotEmpty(c => c.Password, "Password required")
.ValidateOnStartup();

builder.Services.AddSingleton(sp =>
sp.GetRequiredService>().Value);
}
else // Production/Test
{
builder.Services.AddConfigKit("Auth")
.NotEmpty(c => c.ClientId, "ClientId required")
.NotEmpty(c => c.ClientSecret, "ClientSecret required")
.NotEmpty(c => c.Authority, "Authority URL required")
.UrlReachable(c => c.Authority, TimeSpan.FromSeconds(10),
"OAuth authority not reachable")
.ValidateOnStartup();

builder.Services.AddSingleton(sp =>
sp.GetRequiredService>().Value);
}
```

**Pros:**
- βœ… Type-safe configuration classes without nullable properties
- βœ… Clear separation of environment-specific concerns
- βœ… Compile-time enforcement of configuration structure

**Cons:**
- ❌ More complex DI registration (if/else in startup)
- ❌ Requires separate `appsettings.{Environment}.json` files with different schemas
- ❌ Two separate validation builders

**Recommendation:** Use **Approach 1** for simple environment differences (different values). Use **Approach 2** when environments have fundamentally different authentication mechanisms or infrastructure dependencies.

### Custom Validation Rules

Create custom validation rules by inheriting from `ValidationRuleBase`:

```csharp
using Fox.ConfigKit.Validation;

internal sealed class CustomRule : ValidationRuleBase, IValidationRule where T : class
{
private readonly Func getValue;
private readonly string propertyName;
private readonly string? message;

public CustomRule(Expression> selector, string? message = null)
{
this.getValue = selector.Compile();
this.propertyName = GetPropertyName(selector);
this.message = message;
}

public ConfigValidationError? Validate(T options, string sectionName)
{
var value = getValue(options);

if (string.IsNullOrEmpty(value))
{
var key = $"{sectionName}:{propertyName}";
var errorMessage = message ?? $"{propertyName} is invalid";
return new ConfigValidationError(key, errorMessage, value, ["Provide a valid value"]);
}

return null;
}
}

// Extension method
public static class CustomValidationExtensions
{
public static ConfigValidationBuilder MyCustomValidation(
this ConfigValidationBuilder builder,
Expression> selector,
string? message = null) where T : class
{
return builder.AddRule(new CustomRule(selector, message));
}
}
```

## πŸš‚ Result Pattern Integration (Optional)

For 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.

### Installation

```bash
dotnet add package Fox.ConfigKit.ResultKit
```

### Functional Validation (Alternative to ValidateOnStartup)

Instead of exception-based validation at startup, use `Result` for explicit error handling:

```csharp
using Fox.ConfigKit.ResultKit;
using Fox.ResultKit;

var builder = WebApplication.CreateBuilder(args);

// Register configuration WITHOUT ValidateOnStartup()
builder.Services.AddConfigKit("Application")
.NotEmpty(c => c.Name, "Application name is required")
.Minimum(c => c.MaxConcurrentRequests, 1)
.Maximum(c => c.MaxConcurrentRequests, 1000);
// ← No .ValidateOnStartup() call!

builder.Services.AddConfigKit("Database")
.NotEmpty(c => c.ConnectionString);

var app = builder.Build();

// Explicit validation with Result pattern
var appConfig = app.Services.GetRequiredService>().Value;
var dbConfig = app.Services.GetRequiredService>().Value;

// Composable validation pipeline
var startupResult = ConfigValidator.Validate()
.NotEmpty(c => c.Name, "Application name is required")
.Minimum(c => c.MaxConcurrentRequests, 1)
.ToResult(appConfig)
.Bind(_ => ConfigValidator.Validate()
.NotEmpty(c => c.ConnectionString, "Connection string required")
.ToResult(dbConfig));

// Functional error handling
startupResult.Match(
onSuccess: _ =>
{
app.Logger.LogInformation("All configurations valid");
app.Run();
},
onFailure: error =>
{
app.Logger.LogCritical("Configuration validation failed: {Error}", error.Message);
Environment.Exit(1);
}
);
```

### Collecting All Validation Errors

Use `ToErrorsResult()` to collect **all** validation errors instead of stopping at the first one:

```csharp
var validationResult = ConfigValidator.Validate()
.NotEmpty(c => c.Name, "Name is required")
.InRange(c => c.MaxConcurrentRequests, 1, 1000)
.NotEmpty(c => c.Version, "Version is required")
.ToErrorsResult(appConfig);

if (validationResult.IsFailure)
{
app.Logger.LogCritical("Configuration has {Count} errors:", validationResult.Errors.Count);

foreach (var error in validationResult.Errors)
{
app.Logger.LogError(" - {ErrorMessage}", error.Message);
}

Environment.Exit(1);
}

app.Run();
```

### When to Use Result Pattern vs ValidateOnStartup

| Scenario | Use `ValidateOnStartup()` | Use `ToResult()` / Result Pattern |
|----------|--------------------------|----------------------------------|
| **Simple ASP.NET Core app** | βœ… Recommended (fail-fast) | ❌ Overkill |
| **Console/Worker apps** | ❌ No DI startup | βœ… Recommended |
| **Complex startup logic** | 🟑 Limited control | βœ… Full control over errors |
| **Multiple configs to validate** | 🟑 Sequential exceptions | βœ… Composable chain with `Bind()` |
| **Need all errors at once** | ❌ Stops at first error | βœ… `ToErrorsResult()` collects all |
| **Unit testing validation** | 🟑 Exception-based asserts | βœ… Result-based asserts |
| **Graceful shutdown on error** | ❌ Exception crash | βœ… `Environment.Exit(1)` |
| **Already using Railway pattern** | 🟑 Inconsistent style | βœ… Consistent functional style |

### Standalone Validation (Without DI)

For console apps, scripts, or unit tests where DI is not available:

```csharp
using Fox.ConfigKit.ResultKit;

// Load configuration from file/environment
var config = LoadConfigurationFromFile("appsettings.json");

// Validate without dependency injection
var result = ConfigValidator.Validate()
.NotEmpty(c => c.Name, "Name is required")
.InRange(c => c.Port, 1, 65535, "Invalid port")
.ToResult(config);

return result.Match(
onSuccess: validConfig => RunApplication(validConfig),
onFailure: error =>
{
Console.WriteLine($"Configuration error: {error}");
return 1; // Exit code
}
);
```

**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.

## πŸ“– Sample Application

A comprehensive sample WebApi application is available in the repository demonstrating:

- βœ… Application-wide configuration validation
- βœ… Database connection string validation
- βœ… External API configuration with URL reachability checks
- βœ… File system validation for log directories
- βœ… Environment-based conditional validation
- βœ… Security configuration with certificate validation

**Run the sample:**

```bash
cd samples/Fox.ConfigKit.Samples.WebApi
dotnet run
```

**Explore:**
- Open `https://localhost:5001/swagger`
- View validated configurations via `/api/configuration/*` endpoints
- See [sample README](samples/Fox.ConfigKit.Samples.WebApi/README.md) for details

## 🎯 Design Principles

1. **Fail-Fast** - Catch configuration errors at startup, not at runtime
2. **Explicit Validation** - Make validation rules clear and discoverable
3. **Type-Safe** - Leverage C# type system for compile-time safety
4. **Fluent API** - Intuitive, chainable validation rules
5. **No Third-Party Dependencies** - Only core Microsoft.Extensions packages, no external libraries
6. **Developer-Friendly** - Clear error messages, excellent IntelliSense

## πŸ”§ Requirements

- .NET 8.0 or higher
- C# 12 or higher (for modern language features)
- Nullable reference types enabled (recommended)

## 🎯 Real-World Example

See 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.

## 🀝 Contributing

**Fox.ConfigKit is intentionally lightweight and feature-focused.** The goal is to remain a simple library with minimal dependencies for configuration validation.

### What We Welcome

- βœ… **Bug fixes** - Issues with existing functionality
- βœ… **Documentation improvements** - Clarifications, examples, typo fixes
- βœ… **Performance optimizations** - Without breaking API compatibility
- βœ… **New validation rules** - Following existing patterns

### What We Generally Do Not Accept

- ❌ New dependencies or third-party packages
- ❌ Large feature additions that increase complexity
- ❌ Breaking API changes

If you want to propose a significant change, please open an issue first to discuss whether it aligns with the project's philosophy.

### Build Policy

The project enforces a **strict build policy** to ensure code quality:

- ❌ **No errors allowed** - Build must be error-free
- ❌ **No warnings allowed** - All compiler warnings must be resolved
- ❌ **No messages allowed** - Informational messages must be suppressed or addressed

All pull requests must pass this requirement.

### Code Quality Standards

Fox.ValidationKit follows strict coding standards:

- **Comprehensive unit tests required** (xUnit + FluentAssertions)
- **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.
- **XML documentation for all public APIs** - Clear, concise documentation with examples
- **Follow Microsoft coding conventions** - See `.github/copilot-instructions.md` for project-specific style
- **Zero warnings, zero errors build policy** - Strict enforcement

### Code Style

- Follow the existing code style (see `.github/copilot-instructions.md`)
- Use file-scoped namespaces
- Enable nullable reference types
- Add XML documentation for public APIs
- Write unit tests for new features
- Use expression-bodied members for simple properties
- Auto-properties preferred over backing fields

### How to Contribute

1. Fork the repository
2. Create a feature branch from `main`
3. Follow the coding standards in `.github/copilot-instructions.md`
4. Ensure all tests pass (`dotnet test`)
5. Submit a pull request

## πŸ“ License

This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.

## πŸ‘€ Author

**KΓ‘roly AkΓ‘cz**

- GitHub: [@akikari](https://github.com/akikari)
- Repository: [Fox.ConfigKit](https://github.com/akikari/Fox.ConfigKit)

## πŸ“Š Project Status

[![NuGet Version](https://img.shields.io/nuget/v/Fox.ConfigKit.svg?label=version&color=blue)](https://www.nuget.org/packages/Fox.ConfigKit/)

See [CHANGELOG.md](CHANGELOG.md) for version history.

## πŸ”— Related Projects

- [Fox.ResultKit](https://github.com/akikari/Fox.ResultKit) - Lightweight Result pattern library for Railway Oriented Programming
- [Fox.ConfigKit.ResultKit](https://www.nuget.org/packages/Fox.ConfigKit.ResultKit) - Integration package for using ConfigKit with Result pattern

## πŸ“ž Support

For issues, questions, or feature requests, please open an issue in the [GitHub repository](https://github.com/akikari/Fox.ConfigKit/issues).