{"id":18497784,"url":"https://github.com/timewarpengineering/timewarp-options-validation","last_synced_at":"2026-02-09T07:35:05.705Z","repository":{"id":108670227,"uuid":"563648518","full_name":"TimeWarpEngineering/timewarp-options-validation","owner":"TimeWarpEngineering","description":null,"archived":false,"fork":false,"pushed_at":"2025-12-06T10:11:12.000Z","size":137,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-10T01:57:44.091Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TimeWarpEngineering.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-11-09T03:09:36.000Z","updated_at":"2025-12-06T10:10:13.000Z","dependencies_parsed_at":"2024-11-06T13:45:25.129Z","dependency_job_id":null,"html_url":"https://github.com/TimeWarpEngineering/timewarp-options-validation","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/TimeWarpEngineering/timewarp-options-validation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimeWarpEngineering%2Ftimewarp-options-validation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimeWarpEngineering%2Ftimewarp-options-validation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimeWarpEngineering%2Ftimewarp-options-validation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimeWarpEngineering%2Ftimewarp-options-validation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TimeWarpEngineering","download_url":"https://codeload.github.com/TimeWarpEngineering/timewarp-options-validation/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimeWarpEngineering%2Ftimewarp-options-validation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29258761,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-09T04:11:57.159Z","status":"ssl_error","status_checked_at":"2026-02-09T04:11:56.117Z","response_time":56,"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":[],"created_at":"2024-11-06T13:35:46.732Z","updated_at":"2026-02-09T07:35:05.694Z","avatar_url":"https://github.com/TimeWarpEngineering.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Dotnet](https://img.shields.io/badge/dotnet-10.0-blue)](https://dotnet.microsoft.com)\n[![Stars](https://img.shields.io/github/stars/TimeWarpEngineering/timewarp-options-validation?logo=github)](https://github.com/TimeWarpEngineering/timewarp-options-validation)\n[![Discord](https://img.shields.io/discord/715274085940199487?logo=discord)](https://discord.gg/7F4bS2T)\n[![workflow](https://github.com/TimeWarpEngineering/timewarp-options-validation/actions/workflows/release-build.yml/badge.svg)](https://github.com/TimeWarpEngineering/timewarp-options-validation/actions)\n[![nuget](https://img.shields.io/nuget/v/TimeWarp.OptionsValidation?logo=nuget)](https://www.nuget.org/packages/TimeWarp.OptionsValidation/)\n[![nuget](https://img.shields.io/nuget/dt/TimeWarp.OptionsValidation?logo=nuget)](https://www.nuget.org/packages/TimeWarp.OptionsValidation/)\n[![Issues Open](https://img.shields.io/github/issues/TimeWarpEngineering/timewarp-options-validation.svg?logo=github)](https://github.com/TimeWarpEngineering/timewarp-options-validation/issues)\n[![Forks](https://img.shields.io/github/forks/TimeWarpEngineering/timewarp-options-validation)](https://github.com/TimeWarpEngineering/timewarp-options-validation)\n[![License](https://img.shields.io/github/license/TimeWarpEngineering/timewarp-options-validation.svg?style=flat-square\u0026logo=github)](https://github.com/TimeWarpEngineering/timewarp-options-validation/issues)\n[![Twitter](https://img.shields.io/twitter/url?style=social\u0026url=https%3A%2F%2Fgithub.com%2FTimeWarpEngineering%2Ftimewarp-options-validation)](https://twitter.com/intent/tweet?url=https://github.com/TimeWarpEngineering/timewarp-options-validation)\n\n[![Twitter](https://img.shields.io/twitter/follow/StevenTCramer.svg)](https://twitter.com/intent/follow?screen_name=StevenTCramer)\n[![Twitter](https://img.shields.io/twitter/follow/TheFreezeTeam1.svg)](https://twitter.com/intent/follow?screen_name=TheFreezeTeam1)\n\n# TimeWarp.OptionsValidation\n\n![TimeWarp Logo](assets/logo.png)\n\nTimeWarp.OptionsValidation integrates FluentValidation with Microsoft.Extensions.Options to provide automatic validation of your configuration settings at application startup.\n\n## Why Use This Library?\n\nConfiguration errors are a common source of runtime failures. TimeWarp.OptionsValidation helps you **fail fast** by validating all configuration settings when your application starts, rather than discovering errors when the configuration is first accessed (which could be hours or days later in production).\n\n**Key Benefits:**\n- Validates configuration settings using FluentValidation rules\n- Integrates seamlessly with Microsoft.Extensions.Options\n- Catches configuration errors at startup, not at runtime\n- Provides clear, actionable error messages\n- Supports both IConfiguration binding and programmatic configuration\n\n## Give a Star! :star:\n\nIf you like or are using this project please give it a star. Thank you!\n\n## Installation\n\n```console\ndotnet add package TimeWarp.OptionsValidation\n```\n\nYou can see the latest NuGet packages from the official [TimeWarp NuGet page](https://www.nuget.org/profiles/TimeWarp.Enterprises).\n\n* [TimeWarp.OptionsValidation](https://www.nuget.org/packages/TimeWarp.OptionsValidation/) [![nuget](https://img.shields.io/nuget/v/TimeWarp.OptionsValidation?logo=nuget)](https://www.nuget.org/packages/TimeWarp.OptionsValidation/)\n\n## Usage\n\n### Basic Setup with Automatic Startup Validation\n\nUse `AddFluentValidatedOptions()` which returns `OptionsBuilder\u003cT\u003e`, allowing you to chain with `.ValidateOnStart()` for automatic startup validation.\n\n#### 1. Define Your Options Class with Nested Validator\n\n```csharp\nusing FluentValidation;\n\npublic class DatabaseOptions\n{\n  public string ConnectionString { get; set; } = string.Empty;\n  public int MaxRetries { get; set; }\n  public int CommandTimeout { get; set; }\n\n  // Nested validator - sealed and only used here\n  public sealed class Validator : AbstractValidator\u003cDatabaseOptions\u003e\n  {\n    public Validator()\n    {\n      RuleFor(x =\u003e x.ConnectionString)\n        .NotEmpty()\n        .WithMessage(\"Database connection string is required\");\n\n      RuleFor(x =\u003e x.MaxRetries)\n        .GreaterThan(0)\n        .LessThanOrEqualTo(10)\n        .WithMessage(\"MaxRetries must be between 1 and 10\");\n\n      RuleFor(x =\u003e x.CommandTimeout)\n        .GreaterThanOrEqualTo(30)\n        .WithMessage(\"CommandTimeout must be at least 30 seconds\");\n    }\n  }\n}\n```\n\n#### 2. Register with Automatic Startup Validation\n\n```csharp\nusing Microsoft.Extensions.DependencyInjection;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Register options with automatic startup validation\nbuilder.Services\n  .AddFluentValidatedOptions\u003cDatabaseOptions, DatabaseOptions.Validator\u003e(builder.Configuration)\n  .ValidateOnStart(); // ✅ Validates when host starts, throws on error\n\nvar app = builder.Build();\napp.Run(); // Validation happens automatically before this runs\n```\n\n**What this does:**\n- Binds the `DatabaseOptions` section from appsettings.json\n- Registers the FluentValidation validator\n- **Validates configuration at startup** (before `app.Run()`)\n- **Fails fast with clear error messages** if configuration is invalid\n- No manual validation calls needed!\n\n### Configuration Binding\n\nThe library automatically discovers which configuration section to bind based on simple, predictable rules.\n\n#### Default: Class Name\n\nBy default, the library uses the **class name** as the configuration section name:\n\n```csharp\npublic class DatabaseOptions\n{\n  public string ConnectionString { get; set; } = string.Empty;\n  // ...\n}\n```\n\nBinds to `\"DatabaseOptions\"` section:\n\n```json\n{\n  \"DatabaseOptions\": {\n    \"ConnectionString\": \"Server=localhost;Database=myapp;\",\n    \"MaxRetries\": 3,\n    \"CommandTimeout\": 30\n  }\n}\n```\n\n```csharp\n// Automatically binds to \"DatabaseOptions\" section\nservices\n  .AddFluentValidatedOptions\u003cDatabaseOptions, DatabaseOptions.Validator\u003e(configuration)\n  .ValidateOnStart();\n```\n\n#### Custom Configuration Key with `[ConfigurationKey]` Attribute\n\nOverride the default by decorating your options class with `[ConfigurationKey]`:\n\n**Simple Configuration Key:**\n```csharp\nusing TimeWarp.OptionsValidation;\n\n[ConfigurationKey(\"Database\")]\npublic class DatabaseOptions\n{\n  public string ConnectionString { get; set; } = string.Empty;\n  // ...\n}\n```\n\nBinds to `\"Database\"` section:\n\n```json\n{\n  \"Database\": {\n    \"ConnectionString\": \"Server=localhost;Database=myapp;\",\n    \"MaxRetries\": 3,\n    \"CommandTimeout\": 30\n  }\n}\n```\n\n**Hierarchical Key with Colon Separator:**\n```csharp\n[ConfigurationKey(\"MyApp:Settings:Database\")]\npublic class DatabaseOptions\n{\n  public string ConnectionString { get; set; } = string.Empty;\n  // ...\n}\n```\n\nBinds to nested `\"MyApp\" → \"Settings\" → \"Database\"` path:\n\n```json\n{\n  \"MyApp\": {\n    \"Settings\": {\n      \"Database\": {\n        \"ConnectionString\": \"Server=localhost;Database=myapp;\",\n        \"MaxRetries\": 3,\n        \"CommandTimeout\": 30\n      }\n    }\n  }\n}\n```\n\n```csharp\n// Automatically binds to configuration key specified in attribute\nservices\n  .AddFluentValidatedOptions\u003cDatabaseOptions, DatabaseOptions.Validator\u003e(configuration)\n  .ValidateOnStart();\n```\n\n#### Advanced: Manual Section Binding\n\nFor dynamic section paths or complex scenarios not covered by the attribute:\n\n```csharp\n// Manual binding for runtime-determined paths\nstring environment = builder.Environment.EnvironmentName;\nservices.AddOptions\u003cDatabaseOptions\u003e()\n  .Bind(configuration.GetSection($\"{environment}:Database\"))\n  .ValidateFluentValidation\u003cDatabaseOptions, DatabaseOptions.Validator\u003e()\n  .ValidateOnStart();\n```\n\n**Automatic Configuration Key Resolution Summary:**\n- ✅ Uses class name: `DatabaseOptions` → `\"DatabaseOptions\"`\n- ✅ Simple override: `[ConfigurationKey(\"Database\")]` → `\"Database\"`\n- ✅ Hierarchical paths: `[ConfigurationKey(\"MyApp:Settings:Database\")]` → `\"MyApp\" → \"Settings\" → \"Database\"`\n- ❌ Does NOT trim suffixes like \"Options\" automatically\n- ❌ Does NOT pluralize names automatically\n\n### Programmatic Configuration\n\nYou can also configure options programmatically without IConfiguration:\n\n```csharp\nservices\n  .AddFluentValidatedOptions\u003cDatabaseOptions, DatabaseOptions.Validator\u003e(options =\u003e\n  {\n    options.ConnectionString = \"Server=localhost;Database=myapp;\";\n    options.MaxRetries = 3;\n    options.CommandTimeout = 30;\n  })\n  .ValidateOnStart();\n```\n\n### Complete Startup Example\n\n```csharp\nusing Microsoft.Extensions.DependencyInjection;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Register multiple validated options with automatic startup validation\nbuilder.Services\n  .AddFluentValidatedOptions\u003cDatabaseOptions, DatabaseOptions.Validator\u003e(builder.Configuration)\n  .ValidateOnStart();\n\nbuilder.Services\n  .AddFluentValidatedOptions\u003cCacheOptions, CacheOptions.Validator\u003e(builder.Configuration)\n  .ValidateOnStart();\n\nbuilder.Services\n  .AddFluentValidatedOptions\u003cEmailOptions, EmailOptions.Validator\u003e(builder.Configuration)\n  .ValidateOnStart();\n\nvar app = builder.Build();\napp.Run(); // All options validated before this runs\n```\n\nIf any configuration is invalid, the application will **fail to start** with clear error messages indicating exactly which settings are invalid and why.\n\n### Without Startup Validation\n\nIf you don't need automatic startup validation, simply omit `.ValidateOnStart()`:\n\n```csharp\n// Validates on first access instead of at startup\nservices.AddFluentValidatedOptions\u003cDatabaseOptions, DatabaseOptions.Validator\u003e(configuration);\n// No .ValidateOnStart() call - validation happens lazily\n```\n\nThis approach validates options when they're first accessed rather than at application startup.\n\n## Features\n\n- **Automatic Startup Validation**: Use `.ValidateOnStart()` to fail fast on invalid configuration\n- **Automatic Key Discovery**: Uses the class name as the configuration key by default\n- **Custom Key Mapping**: Use `[ConfigurationKey]` attribute to override the configuration key\n- **Hierarchical Keys**: Support for nested configuration paths using colon separators\n- **Seamless Integration**: Works with Microsoft.Extensions.Options infrastructure and `OptionsBuilder\u003cT\u003e`\n- **FluentValidation Power**: Rich validation rules, custom validators, conditional validation\n- **Clear Error Messages**: Detailed, actionable error messages from FluentValidation\n- **Type Safety**: Strongly-typed options with compile-time checking\n- **Flexible API**: Choose between fluent API (with `.ValidateOnStart()`) or simple registration\n\n## Releases\n\nSee the [Release Notes](./documentation/releases.md)\n## Unlicense\n\n[![License](https://img.shields.io/github/license/TimeWarpEngineering/timewarp-options-validation.svg?style=flat-square\u0026logo=github)](https://unlicense.org)\n\n## Contributing\n\nTime is of the essence.  Before developing a Pull Request I recommend opening a [discussion](https://github.com/TimeWarpEngineering/timewarp-options-validation/discussions).\n\nPlease feel free to make suggestions and help out with the [documentation](https://timewarpengineering.github.io/timewarp-options-validation/).\nPlease refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files.\n\n## Contact\n\nSometimes the github notifications get lost in the shuffle.  If you file an [issue](https://github.com/TimeWarpEngineering/timewarp-options-validation/issues) and don't get a response in a timely manner feel free to ping on our [Discord server](https://discord.gg/A55JARGKKP).\n\n[![Discord](https://img.shields.io/discord/715274085940199487?logo=discord)](https://discord.gg/7F4bS2T)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimewarpengineering%2Ftimewarp-options-validation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimewarpengineering%2Ftimewarp-options-validation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimewarpengineering%2Ftimewarp-options-validation/lists"}