From fcf7635a0fd07f8f32a198be909905f67bd6f5d2 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:16:46 +0700 Subject: [PATCH 01/37] Rename LICENSE to lowercase filename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standardize file naming convention by using lowercase for license file. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- LICENSE => license | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE => license (100%) diff --git a/LICENSE b/license similarity index 100% rename from LICENSE rename to license From b2ce2397658fed2faa900ccc4909e2a9089d3f93 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:17:02 +0700 Subject: [PATCH 02/37] Rename README.md to lowercase filename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standardize file naming convention by using lowercase for readme file. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md => readme.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.md => readme.md (100%) diff --git a/README.md b/readme.md similarity index 100% rename from README.md rename to readme.md From 00cdaa1970d1c444c8c329470f1a12f7a492a571 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:22:42 +0700 Subject: [PATCH 03/37] Add VSCode workspace color configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Configure Peacock extension with red theme for visual workspace identification. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .vscode/settings.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b72137c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#f58b87", + "activityBar.background": "#f58b87", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#e2fde3", + "activityBarBadge.foreground": "#15202b", + "commandCenter.border": "#15202b99", + "sash.hoverBorder": "#f58b87", + "statusBar.background": "#f15e58", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#ed3129", + "statusBarItem.remoteBackground": "#f15e58", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#f15e58", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#f15e5899", + "titleBar.inactiveForeground": "#15202b99" + }, + "peacock.remoteColor": "#F15E58" +} \ No newline at end of file From 035d080a2d1fb2dce48727911e180cc8bd0bcf18 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:23:10 +0700 Subject: [PATCH 04/37] Add project avatar asset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SVG avatar for timewarp-options-validation project branding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- assets/timewarp-options-validation-avatar.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 assets/timewarp-options-validation-avatar.svg diff --git a/assets/timewarp-options-validation-avatar.svg b/assets/timewarp-options-validation-avatar.svg new file mode 100644 index 0000000..4b1286a --- /dev/null +++ b/assets/timewarp-options-validation-avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file From 733d4caaeb0ebdf45966c90ab892566a1a07cc70 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:33:31 +0700 Subject: [PATCH 05/37] Rename Assets/Logo.png to lowercase path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standardize asset directory and filename to lowercase for consistency with other project files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Assets/Logo.png | Bin 4277 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Assets/Logo.png diff --git a/Assets/Logo.png b/Assets/Logo.png deleted file mode 100644 index cc32aa01112839e1716ebd58fabae55d78949be1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4277 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 XB4!WHxdQ*Z00000NkvXXu0mjfXjA>b From 120b846df594ef125b176413d9a0f5e891794bff Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:37:19 +0700 Subject: [PATCH 06/37] Add logo.png to lowercase assets directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete asset directory standardization by adding logo with lowercase filename. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- assets/logo.png | Bin 0 -> 4277 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/logo.png diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cc32aa01112839e1716ebd58fabae55d78949be1 GIT binary patch literal 4277 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 XB4!WHxdQ*Z00000NkvXXu0mjfXjA>b literal 0 HcmV?d00001 From 07741a585a0f89e059cd0a09f05573fe84e7cdf9 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:37:38 +0700 Subject: [PATCH 07/37] Update logo reference to lowercase path in readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update image link to reference lowercase assets/logo.png path. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 7897c7e..b3d9ce5 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ # TimeWarp.OptionsValidation -![TimeWarp Logo](Assets/Logo.png) +![TimeWarp Logo](assets/logo.png) TimeWarp.OptionsValidation uses fluent validation to check your configuration settings. From 3622aefe69f7d6ddedae1b0cfdf8afc5fcbd9a9d Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:37:59 +0700 Subject: [PATCH 08/37] Update package file references to lowercase paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update NuGet package file inclusions to reference lowercase asset and documentation files (logo.png, readme.md, license). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../TimeWarp.OptionsValidation.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj b/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj index 09bda60..27a8d7c 100644 --- a/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj +++ b/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj @@ -8,9 +8,9 @@ - - - + + + From 3f9530b647ac5c9e6fdd15d0d2b9e0cd381d7da8 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:39:10 +0700 Subject: [PATCH 09/37] Bump version to 1.0.0-beta.3 and update package metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Increment package version to beta.3 and update PackageIcon and PackageReadmeFile references to lowercase filenames. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Build.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index bd23c15..c09d803 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,10 +10,10 @@ https://github.com/TimeWarpEngineering/timewarp-options-validation.git https://timewarpengineering.github.io/timewarp-options-validation/ TimeWarp; Options Validation;OptionsValidation - 1.0.0-beta.2+8.0.205 + 1.0.0-beta.3 Unlicense - Logo.png - README.md + logo.png + readme.md From e4d3044f06605c27915f085ca7f415db98ce012f Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:48:51 +0700 Subject: [PATCH 10/37] Add CLAUDE.md for AI pair programming context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document build commands, architecture, testing framework, and project conventions to help future Claude Code instances be productive immediately. Key sections: - Build, test, and package commands - Core architecture (OptionsValidation, ServiceCollectionExtensions, SectionNameAttribute) - Fixie testing framework usage - Central package management configuration - File naming conventions (lowercase) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..17b72d1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,130 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +TimeWarp.OptionsValidation is a .NET library that integrates FluentValidation with Microsoft.Extensions.Options to validate configuration settings at startup. The library provides extension methods to configure and validate options using FluentValidation validators. + +## Build and Test Commands + +### Build +```bash +# Build the entire solution +dotnet build + +# Build specific project +cd Source/TimeWarp.OptionsValidation/ +dotnet build --configuration Debug +``` + +### Test +```bash +# Run tests using Fixie (requires tool restore) +cd Tests/TimeWarp.OptionsValidation.Tests/ +dotnet tool restore +dotnet restore +dotnet fixie --configuration Debug + +# Run tests from solution root +dotnet fixie --configuration Debug --project Tests/TimeWarp.OptionsValidation.Tests/ +``` + +### Package +```bash +# Package is auto-generated on build (GeneratePackageOnBuild=true) +# Output: Source/TimeWarp.OptionsValidation/bin/Packages/ + +# Build and pack manually if needed +cd Source/TimeWarp.OptionsValidation/ +dotnet pack --configuration Release +``` + +### Tools +```bash +# Restore local tools (defined in .config/dotnet-tools.json) +dotnet tool restore + +# Check for outdated packages +dotnet outdated +``` + +## Architecture + +### Core Components + +**OptionsValidation** +- Bridge between FluentValidation and Microsoft.Extensions.Options +- Implements `IValidateOptions` +- Invoked automatically by the options framework when options are accessed +- Located in [Source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs](Source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs) + +**ServiceCollectionExtensions** +- Provides `ConfigureOptions` extension methods +- Two overloads: one accepts `IConfiguration`, the other accepts `Action` +- Automatically discovers configuration section names via `SectionNameAttribute` or defaults to type name +- Registers both the validator and the `IValidateOptions` implementation +- Located in [Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs](Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs) + +**SectionNameAttribute** +- Allows overriding the configuration section name +- Applied to options classes when the section name differs from the class name +- Located in [Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs](Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs) + +### Usage Pattern + +1. Define an options class (e.g., `MyOptions`) +2. Create a FluentValidation validator (e.g., `MyOptionsValidator : AbstractValidator`) +3. Optionally decorate options class with `[SectionName("ConfigSectionName")]` +4. Register in DI: `services.ConfigureOptions(configuration)` +5. Validation executes automatically when options are first accessed + +### Project Structure + +``` +/Source/TimeWarp.OptionsValidation/ - Main library + /Configuration/ - Core validation logic + /Extensions/ - DI extension methods +/Tests/TimeWarp.OptionsValidation.Tests/ - Test project using Fixie +``` + +## Build Configuration + +### Central Package Management +- Uses `Directory.Packages.props` for centralized version management +- `ManagePackageVersionsCentrally` is enabled +- Package references in .csproj files don't specify versions + +### Common Properties (Directory.Build.props) +- **Target Framework**: net8.0 +- **LangVersion**: preview +- **Nullable**: enabled +- **TreatWarningsAsErrors**: true +- **ImplicitUsings**: enabled +- **Package Version**: Defined in Directory.Build.props (1.0.0-beta.3) +- **Embedded Resources**: Auto-embeds `.scriban` and `.cstemplate` files + +### Package Metadata +- NuGet packages include: logo.png, readme.md, and license files +- All asset references use lowercase filenames +- PackageOutputPath: `./bin/Packages` + +## Testing Framework + +Uses **Fixie** (not xUnit/NUnit/MSTest): +- Custom test discovery convention via TimeWarp.Fixie +- Test classes end with `_Should_` by convention +- Test methods are public static void methods +- Supports `[Skip]` attribute for skipping tests +- Supports `[TestTag]` for categorization +- Supports `[Input]` for parameterized tests +- FluentAssertions for assertions + +## File Naming Conventions + +All repository root files use **lowercase names**: +- `license` (not LICENSE) +- `readme.md` (not README.md) +- `assets/logo.png` (not Assets/Logo.png) + +When updating package metadata, ensure references match these lowercase conventions. From 35dc30b918f2ad89a43f420a15ab7d83b3569a88 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:55:03 +0700 Subject: [PATCH 11/37] Update dotnet-outdated-tool to 4.6.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch update for dotnet-outdated-tool from 4.6.1 to 4.6.8. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 9fd703d..a8448fd 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -10,7 +10,7 @@ "rollForward": false }, "dotnet-outdated-tool": { - "version": "4.6.1", + "version": "4.6.8", "commands": [ "dotnet-outdated" ], From 472199a51add084ff6bbb391cfb811bcb150477d Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 09:55:30 +0700 Subject: [PATCH 12/37] Update NuGet package dependencies to latest versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major version updates: - Microsoft.Extensions.* packages: 8.0.x → 9.0.10 - FluentValidation: 11.9.1 → 12.0.0 - FluentAssertions: 6.12.0 → 8.8.0 - Fixie.TestAdapter: 3.4.0 → 4.1.0 - TimeWarp.Fixie: 1.0.1 → 2.0.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Packages.props | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d8c418f..f62c80a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,17 +2,17 @@ - - - + + + - - - - - + + + + + From fa6a8b49fbb47febd9bad925eb28d5137759f837 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 10:26:22 +0700 Subject: [PATCH 13/37] Restructure Directory.Build.props following Nuru pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize build configuration with labeled property groups: - Custom Repository Variables: Define base paths (RepositoryRoot, SourceDirectory, TestsDirectory, etc.) - MSBuild/NuGet Configuration: Central package management and output paths - Project Defaults: Target net10.0 with latest language features - Code Quality and Analysis: Enhanced analyzer settings (WarningLevel 5, AnalysisMode All) - Package Metadata: NuGet package information - Code Analyzers: Include Roslynator and Microsoft analyzers Add comprehensive warning suppressions with documentation for each suppressed rule. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Build.props | 97 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index c09d803..466e8ca 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,77 @@ - - + + + timewarp-options-validation + $(RepositoryRoot)timewarp-options-validation.sln + $(MSBuildThisFileDirectory) + $(RepositoryRoot)Source/ + $(RepositoryRoot)Tests/ + $(RepositoryRoot)Documentation/ + $(RepositoryRoot)artifacts/ + $(ArtifactsDirectory)packages/ + + + + + + $(PackagesDirectory) + + + true + + + true + + + + + net10.0 + enable + enable + latest + false + true + + + + + + true + 5 + true + true + All + latest-all + + + true + true + + + true + + + true + + + + + + + + + + + + + + + $(NoWarn);CA1014;CA1031;CA1052;CA1515;CA1707;CA1724;CA1812;CA1848;CA1852;CA2007;RCS1102;IL2026;IL2067;IL2070;IL2075;IL3050;IL2104;IL3053 + + + + Steven T. Cramer TimeWarp Enterprises TimeWarp.OptionsValidation @@ -9,26 +79,21 @@ git https://github.com/TimeWarpEngineering/timewarp-options-validation.git https://timewarpengineering.github.io/timewarp-options-validation/ - TimeWarp; Options Validation;OptionsValidation + TimeWarp;Options Validation;OptionsValidation;FluentValidation;Configuration 1.0.0-beta.3 Unlicense logo.png readme.md - - - true - true - true - true - enable - preview - true - enable - net8.0 - true - + + + + + + + + From 835dd642f49bfb39d0057c682e1d78f7e3844e4b Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 10:26:54 +0700 Subject: [PATCH 14/37] Reorganize Directory.Packages.props with labeled groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure package versions with clear categorization: - MSBuild/NuGet Configuration - Microsoft Extensions - FluentValidation - Testing - Code Analyzers - Roslyn Analyzer Dependencies Update analyzer packages to latest versions (Roslynator 4.14.1, Microsoft.CodeAnalysis 4.14.0). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Packages.props | 44 +++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f62c80a..8830420 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,20 +1,44 @@ - + + + + true + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file From d7fdb40f4a0576d85d83baf11784dfdb791b201d Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 10:27:13 +0700 Subject: [PATCH 15/37] Update csproj to use repository base variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hardcoded relative paths with MSBuild variables: - Use $(RepositoryRoot) for package file references - Remove hardcoded PackageOutputPath (now defined in Directory.Build.props) - Simplify PackagePath to empty string (root of package) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../TimeWarp.OptionsValidation.csproj | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj b/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj index 27a8d7c..8a02a9d 100644 --- a/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj +++ b/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj @@ -1,16 +1,15 @@ - + true TimeWarp.OptionsValidation - ./bin/Packages true - - - + + + From f4f4116bdebf3e6f829a406f09c5d5f8f53466b4 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 10:27:51 +0700 Subject: [PATCH 16/37] Fix analyzer warnings in SectionNameAttribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make attribute sealed and use read-only property for constructor parameter to comply with CA1813 and CA1019 analyzer rules. Changes: - Mark class as sealed - Make SectionName property read-only (init in constructor) - Remove BOM from file - Update parameter naming to follow conventions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Configuration/SectionNameAttribute.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs b/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs index 8b7f5a8..79071e4 100644 --- a/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs +++ b/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs @@ -1,14 +1,15 @@ -namespace TimeWarp.OptionsValidation; +namespace TimeWarp.OptionsValidation; /// /// The section name in appsettings.json to which the class should be mapped /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] -public class SectionNameAttribute : Attribute +public sealed class SectionNameAttribute : Attribute { - public string SectionName { get; set; } - public SectionNameAttribute(string aSectionName) + public SectionNameAttribute(string sectionName) { - this.SectionName = aSectionName; + SectionName = sectionName; } + + public string SectionName { get; } } From b7d0dc1bc334beb3bd70ca99cf6202d58c6a2069 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 10:35:43 +0700 Subject: [PATCH 17/37] Update readme with comprehensive purpose and usage documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand readme from minimal documentation to complete user guide: Purpose Section: - Explain fail-fast validation approach - Clarify integration of FluentValidation with Microsoft.Extensions.Options - List key benefits (startup validation, clear errors, IConfiguration support) Usage Section: - Complete step-by-step example (options class, validator, registration, startup validation) - Configuration binding examples (appsettings.json with default and custom section names) - Programmatic configuration example - Complete startup example showing multiple options validation Features Section: - Automatic section discovery - SectionName attribute for custom mapping - Startup validation helper - Clear error messages Updates: - Change dotnet badge from 6.0 to 10.0 - Remove reference to non-existent samples - Add practical, actionable code examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- readme.md | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index b3d9ce5..7a7167a 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -[![Dotnet](https://img.shields.io/badge/dotnet-6.0-blue)](https://dotnet.microsoft.com) +[![Dotnet](https://img.shields.io/badge/dotnet-10.0-blue)](https://dotnet.microsoft.com) [![Stars](https://img.shields.io/github/stars/TimeWarpEngineering/timewarp-options-validation?logo=github)](https://github.com/TimeWarpEngineering/timewarp-options-validation) [![Discord](https://img.shields.io/discord/715274085940199487?logo=discord)](https://discord.gg/7F4bS2T) [![workflow](https://github.com/TimeWarpEngineering/timewarp-options-validation/actions/workflows/release-build.yml/badge.svg)](https://github.com/TimeWarpEngineering/timewarp-options-validation/actions) @@ -16,15 +16,22 @@ ![TimeWarp Logo](assets/logo.png) -TimeWarp.OptionsValidation uses fluent validation to check your configuration settings. +TimeWarp.OptionsValidation integrates FluentValidation with Microsoft.Extensions.Options to provide automatic validation of your configuration settings at application startup. -## Give a Star! :star: +## Why Use This Library? -If you like or are using this project please give it a star. Thank you! +Configuration 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). -## Getting started +**Key Benefits:** +- Validates configuration settings using FluentValidation rules +- Integrates seamlessly with Microsoft.Extensions.Options +- Catches configuration errors at startup, not at runtime +- Provides clear, actionable error messages +- Supports both IConfiguration binding and programmatic configuration -To quickly get started I recommend reviewing the samples in this repo. +## Give a Star! :star: + +If you like or are using this project please give it a star. Thank you! ## Installation @@ -36,6 +43,168 @@ You can see the latest NuGet packages from the official [TimeWarp NuGet page](ht * [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/) +## Usage + +### Basic Example + +Here's a complete example showing how to validate database configuration settings: + +#### 1. Define Your Options Class + +```csharp +public class DatabaseOptions +{ + public string ConnectionString { get; set; } = string.Empty; + public int MaxRetries { get; set; } + public int CommandTimeout { get; set; } +} +``` + +#### 2. Create a FluentValidation Validator + +```csharp +using FluentValidation; + +public class DatabaseOptionsValidator : AbstractValidator +{ + public DatabaseOptionsValidator() + { + RuleFor(x => x.ConnectionString) + .NotEmpty() + .WithMessage("Database connection string is required"); + + RuleFor(x => x.MaxRetries) + .GreaterThan(0) + .LessThanOrEqualTo(10) + .WithMessage("MaxRetries must be between 1 and 10"); + + RuleFor(x => x.CommandTimeout) + .GreaterThanOrEqualTo(30) + .WithMessage("CommandTimeout must be at least 30 seconds"); + } +} +``` + +#### 3. Register in Your DI Container + +```csharp +using Microsoft.Extensions.DependencyInjection; + +// In your Program.cs or Startup.cs +services.ConfigureOptions(configuration); +``` + +This will: +- Bind the `DatabaseOptions` section from your configuration (appsettings.json) +- Register the validator +- Automatically validate when the options are first accessed + +#### 4. (Optional) Validate at Startup + +To catch configuration errors immediately when your application starts: + +```csharp +var serviceProvider = services.BuildServiceProvider(); + +// Validate all registered options +serviceProvider.ValidateOptions(services, logger); +``` + +If validation fails, this will throw an exception with detailed error messages, preventing your application from starting with invalid configuration. + +### Configuration Binding + +#### Using appsettings.json + +By default, the library looks for a configuration section with the same name as your options class: + +```json +{ + "DatabaseOptions": { + "ConnectionString": "Server=localhost;Database=myapp;", + "MaxRetries": 3, + "CommandTimeout": 30 + } +} +``` + +```csharp +services.ConfigureOptions(configuration); +``` + +#### Custom Section Names + +Use the `[SectionName]` attribute to map to a different configuration section: + +```csharp +using TimeWarp.OptionsValidation; + +[SectionName("Database")] +public class DatabaseOptions +{ + public string ConnectionString { get; set; } = string.Empty; + // ... +} +``` + +Now it will bind to the "Database" section instead of "DatabaseOptions": + +```json +{ + "Database": { + "ConnectionString": "Server=localhost;Database=myapp;", + "MaxRetries": 3, + "CommandTimeout": 30 + } +} +``` + +### Programmatic Configuration + +You can also configure options without IConfiguration: + +```csharp +services.ConfigureOptions(options => +{ + options.ConnectionString = "Server=localhost;Database=myapp;"; + options.MaxRetries = 3; + options.CommandTimeout = 30; +}); +``` + +### Complete Startup Example + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +var builder = WebApplication.CreateBuilder(args); + +// Register validated options +builder.Services.ConfigureOptions(builder.Configuration); +builder.Services.ConfigureOptions(builder.Configuration); +builder.Services.ConfigureOptions(builder.Configuration); + +var app = builder.Build(); + +// Validate all options at startup (recommended) +app.Services.ValidateOptions(builder.Services, app.Logger); + +app.Run(); +``` + +If any configuration is invalid, the application will fail to start with clear error messages indicating exactly which settings are invalid and why. + +## Features + +- **Automatic Section Discovery**: Uses the class name as the configuration section name by default +- **Custom Section Mapping**: Use `[SectionName]` attribute to override the section name +- **Seamless Integration**: Works with Microsoft.Extensions.Options infrastructure +- **Startup Validation**: Optional helper method to validate all options when the application starts +- **Clear Error Messages**: FluentValidation provides detailed, actionable error messages +- **Type Safety**: Strongly-typed options with compile-time checking + ## Releases See the [Release Notes](./documentation/releases.md) From db9a3f44039d22d854ea42668ddc6b00de4d8f23 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 11:32:50 +0700 Subject: [PATCH 18/37] Add fluent API with .ValidateOnStart() support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create new OptionsBuilderExtensions with ValidateFluentValidation method that returns OptionsBuilder, enabling integration with the standard Microsoft.Extensions.Options .ValidateOnStart() infrastructure. New APIs: - OptionsBuilderExtensions.ValidateFluentValidation() - Extension for OptionsBuilder - ServiceCollectionExtensions.AddFluentValidatedOptions(IConfiguration) - Returns OptionsBuilder - ServiceCollectionExtensions.AddFluentValidatedOptions(Action) - Returns OptionsBuilder Benefits: - Supports .ValidateOnStart() for automatic startup validation - Fails fast at startup on invalid configuration - Integrates with host lifecycle - Fluent API follows .NET conventions - Backward compatible - existing ConfigureOptions() methods unchanged The fluent API is the recommended approach for production applications. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Extensions/OptionsBuilderExtensions.cs | 45 +++++++ .../Extensions/ServiceCollectionExtensions.cs | 121 ++++++++++++++++-- 2 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs diff --git a/Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs b/Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs new file mode 100644 index 0000000..90279bf --- /dev/null +++ b/Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs @@ -0,0 +1,45 @@ +namespace Microsoft.Extensions.Options; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using FluentValidation; +using TimeWarp.OptionsValidation; + +/// +/// Extension methods for OptionsBuilder to integrate FluentValidation +/// +public static class OptionsBuilderExtensions +{ + /// + /// Adds FluentValidation to the options configuration. + /// This enables validation using FluentValidation rules and allows chaining with .ValidateOnStart() + /// + /// The options type to validate + /// The FluentValidation validator type + /// The options builder + /// The options builder for method chaining + /// + /// + /// services.AddOptions<DatabaseOptions>() + /// .Bind(configuration.GetSection("Database")) + /// .ValidateFluentValidation<DatabaseOptions, DatabaseOptionsValidator>() + /// .ValidateOnStart(); + /// + /// + public static OptionsBuilder ValidateFluentValidation( + this OptionsBuilder optionsBuilder) + where TOptions : class + where TValidator : AbstractValidator + { + // Register the FluentValidation validator as a singleton + optionsBuilder.Services.TryAddSingleton(); + + // Register the bridge that connects FluentValidation to IValidateOptions + optionsBuilder.Services.TryAddEnumerable( + ServiceDescriptor.Singleton, + OptionsValidation>() + ); + + return optionsBuilder; + } +} diff --git a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs b/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs index acd2aa3..a26295b 100644 --- a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs +++ b/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs @@ -1,12 +1,93 @@ namespace Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + public static class ServiceCollectionExtensions { - public static IServiceCollection ConfigureOptions - ( + /// + /// Adds options with automatic configuration binding and FluentValidation. + /// Returns OptionsBuilder for chaining (e.g., .ValidateOnStart()) + /// + /// The options type to configure and validate + /// The FluentValidation validator type + /// The service collection + /// The configuration instance to bind from + /// The OptionsBuilder for method chaining (supports .ValidateOnStart()) + /// + /// + /// // Binds from "DatabaseOptions" section in appsettings.json + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptionsValidator>(configuration) + /// .ValidateOnStart(); + /// + /// // Or with custom section name using [SectionName("Database")] attribute + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptionsValidator>(configuration) + /// .ValidateOnStart(); + /// + /// + public static OptionsBuilder AddFluentValidatedOptions( + this IServiceCollection services, + IConfiguration configuration) + where TOptions : class + where TValidator : AbstractValidator + { + // Auto-discover section name using SectionNameAttribute or class name + Type type = typeof(TOptions); + var sectionNameAttribute = (SectionNameAttribute?)type + .GetCustomAttributes(typeof(SectionNameAttribute), false) + .FirstOrDefault(); + string sectionName = sectionNameAttribute?.SectionName ?? type.Name; + + return services.AddOptions() + .Bind(configuration.GetSection(sectionName)) + .ValidateFluentValidation(); + } + + /// + /// Adds options with programmatic configuration and FluentValidation. + /// Returns OptionsBuilder for chaining (e.g., .ValidateOnStart()) + /// + /// The options type to configure and validate + /// The FluentValidation validator type + /// The service collection + /// Action to configure the options + /// The OptionsBuilder for method chaining (supports .ValidateOnStart()) + /// + /// + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptionsValidator>(options => + /// { + /// options.ConnectionString = "Server=localhost;Database=myapp;"; + /// options.MaxRetries = 3; + /// }) + /// .ValidateOnStart(); + /// + /// + public static OptionsBuilder AddFluentValidatedOptions( + this IServiceCollection services, + Action configureOptions) + where TOptions : class + where TValidator : AbstractValidator + { + return services.AddOptions() + .Configure(configureOptions) + .ValidateFluentValidation(); + } + + /// + /// Configures options with automatic configuration binding and FluentValidation. + /// This is a simpler alternative to AddFluentValidatedOptions that doesn't support .ValidateOnStart() + /// + /// The options type to configure and validate + /// The FluentValidation validator type + /// The service collection + /// The configuration instance to bind from + /// The service collection for method chaining + /// + /// This method validates options on first access, not at startup. + /// For startup validation, use AddFluentValidatedOptions().ValidateOnStart() instead. + /// + public static IServiceCollection ConfigureOptions( this IServiceCollection aServiceCollection, - IConfiguration aConfiguration - ) + IConfiguration aConfiguration) where TOptions : class where TOptionsValidator : AbstractValidator { @@ -19,25 +100,38 @@ IConfiguration aConfiguration return RegisterOptionsValidator(aServiceCollection); } - public static IServiceCollection ConfigureOptions(this IServiceCollection aServiceCollection, Action aOptionsAction) - where TOptions : class - where TOptionsValidator : AbstractValidator + /// + /// Configures options with programmatic configuration and FluentValidation. + /// This is a simpler alternative to AddFluentValidatedOptions that doesn't support .ValidateOnStart() + /// + /// The options type to configure and validate + /// The FluentValidation validator type + /// The service collection + /// Action to configure the options + /// The service collection for method chaining + /// + /// This method validates options on first access, not at startup. + /// For startup validation, use AddFluentValidatedOptions().ValidateOnStart() instead. + /// + public static IServiceCollection ConfigureOptions( + this IServiceCollection aServiceCollection, + Action aOptionsAction) + where TOptions : class + where TOptionsValidator : AbstractValidator { aServiceCollection.Configure(aOptionsAction); return RegisterOptionsValidator(aServiceCollection); } - private static IServiceCollection RegisterOptionsValidator - (IServiceCollection aServiceCollection) + private static IServiceCollection RegisterOptionsValidator( + IServiceCollection aServiceCollection) where TOptions : class where TOptionsValidator : AbstractValidator { aServiceCollection.TryAddSingleton(); - aServiceCollection.TryAddEnumerable - ( - ServiceDescriptor.Singleton - < + aServiceCollection.TryAddEnumerable( + ServiceDescriptor.Singleton< IValidateOptions, OptionsValidation >() @@ -46,3 +140,4 @@ private static IServiceCollection RegisterOptionsValidator Date: Sat, 25 Oct 2025 11:33:18 +0700 Subject: [PATCH 19/37] Update documentation for fluent API with .ValidateOnStart() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit readme.md changes: - Recommend AddFluentValidatedOptions() as primary approach - Show .ValidateOnStart() usage throughout examples - Add comparison table between fluent API and simple API - Update all code examples to use recommended pattern - Add note about ConfigureOptions() as alternative for simple scenarios - Update features list to highlight automatic startup validation CLAUDE.md changes: - Document both API patterns (fluent vs simple) - Add OptionsBuilderExtensions to core components - Update target framework to net10.0 - Update usage patterns to show fluent API as recommended - Document when to use each approach 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 45 ++++++++++++++++++++---- readme.md | 102 ++++++++++++++++++++++++++++++++---------------------- 2 files changed, 100 insertions(+), 47 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 17b72d1..ae83991 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,24 +60,56 @@ dotnet outdated - Located in [Source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs](Source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs) **ServiceCollectionExtensions** -- Provides `ConfigureOptions` extension methods -- Two overloads: one accepts `IConfiguration`, the other accepts `Action` +- Provides two API patterns for configuring options with FluentValidation: + - `AddFluentValidatedOptions()` - Returns `OptionsBuilder`, supports `.ValidateOnStart()` + - `ConfigureOptions()` - Returns `IServiceCollection`, simpler API +- Two overloads for each: one accepts `IConfiguration`, the other accepts `Action` - Automatically discovers configuration section names via `SectionNameAttribute` or defaults to type name - Registers both the validator and the `IValidateOptions` implementation - Located in [Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs](Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs) +**OptionsBuilderExtensions** +- Provides `ValidateFluentValidation()` extension for `OptionsBuilder` +- Enables chaining with standard `.ValidateOnStart()` method +- Integrates FluentValidation with Microsoft.Extensions.Options infrastructure +- Located in [Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs](Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs) + **SectionNameAttribute** - Allows overriding the configuration section name - Applied to options classes when the section name differs from the class name - Located in [Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs](Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs) +### API Patterns + +The library provides two API patterns: + +**Fluent API (Recommended for Production)** +```csharp +services.AddFluentValidatedOptions(configuration) + .ValidateOnStart(); // Validates at startup, fails fast +``` +- Returns `OptionsBuilder` for method chaining +- Supports `.ValidateOnStart()` for automatic startup validation +- Integrates with host lifecycle (validates before app runs) +- Fails fast on invalid configuration at startup +- Best for production applications + +**Simple API (For Development/Simple Scenarios)** +```csharp +services.ConfigureOptions(configuration); +``` +- Returns `IServiceCollection` for simple chaining +- Validates on first access (lazy validation) +- Simpler syntax for basic scenarios +- Best for development or when startup validation isn't critical + ### Usage Pattern 1. Define an options class (e.g., `MyOptions`) 2. Create a FluentValidation validator (e.g., `MyOptionsValidator : AbstractValidator`) 3. Optionally decorate options class with `[SectionName("ConfigSectionName")]` -4. Register in DI: `services.ConfigureOptions(configuration)` -5. Validation executes automatically when options are first accessed +4. Register with fluent API: `services.AddFluentValidatedOptions(configuration).ValidateOnStart()` +5. Validation executes automatically at startup (with `.ValidateOnStart()`) or on first access (without) ### Project Structure @@ -96,13 +128,14 @@ dotnet outdated - Package references in .csproj files don't specify versions ### Common Properties (Directory.Build.props) -- **Target Framework**: net8.0 -- **LangVersion**: preview +- **Target Framework**: net10.0 +- **LangVersion**: latest - **Nullable**: enabled - **TreatWarningsAsErrors**: true - **ImplicitUsings**: enabled - **Package Version**: Defined in Directory.Build.props (1.0.0-beta.3) - **Embedded Resources**: Auto-embeds `.scriban` and `.cstemplate` files +- **Package Output**: `artifacts/packages/` directory ### Package Metadata - NuGet packages include: logo.png, readme.md, and license files diff --git a/readme.md b/readme.md index 7a7167a..e62e145 100644 --- a/readme.md +++ b/readme.md @@ -45,9 +45,9 @@ You can see the latest NuGet packages from the official [TimeWarp NuGet page](ht ## Usage -### Basic Example +### Recommended: Fluent API with Automatic Startup Validation -Here's a complete example showing how to validate database configuration settings: +The recommended approach uses `AddFluentValidatedOptions()` which returns `OptionsBuilder`, allowing you to chain with `.ValidateOnStart()` for automatic startup validation. #### 1. Define Your Options Class @@ -85,32 +85,28 @@ public class DatabaseOptionsValidator : AbstractValidator } ``` -#### 3. Register in Your DI Container +#### 3. Register with Automatic Startup Validation ```csharp using Microsoft.Extensions.DependencyInjection; -// In your Program.cs or Startup.cs -services.ConfigureOptions(configuration); -``` - -This will: -- Bind the `DatabaseOptions` section from your configuration (appsettings.json) -- Register the validator -- Automatically validate when the options are first accessed - -#### 4. (Optional) Validate at Startup - -To catch configuration errors immediately when your application starts: +var builder = WebApplication.CreateBuilder(args); -```csharp -var serviceProvider = services.BuildServiceProvider(); +// Register options with automatic startup validation +builder.Services + .AddFluentValidatedOptions(builder.Configuration) + .ValidateOnStart(); // ✅ Validates when host starts, throws on error -// Validate all registered options -serviceProvider.ValidateOptions(services, logger); +var app = builder.Build(); +app.Run(); // Validation happens automatically before this runs ``` -If validation fails, this will throw an exception with detailed error messages, preventing your application from starting with invalid configuration. +**What this does:** +- Binds the `DatabaseOptions` section from appsettings.json +- Registers the FluentValidation validator +- **Validates configuration at startup** (before `app.Run()`) +- **Fails fast with clear error messages** if configuration is invalid +- No manual validation calls needed! ### Configuration Binding @@ -129,7 +125,9 @@ By default, the library looks for a configuration section with the same name as ``` ```csharp -services.ConfigureOptions(configuration); +services + .AddFluentValidatedOptions(configuration) + .ValidateOnStart(); ``` #### Custom Section Names @@ -161,49 +159,71 @@ Now it will bind to the "Database" section instead of "DatabaseOptions": ### Programmatic Configuration -You can also configure options without IConfiguration: +You can also configure options programmatically without IConfiguration: ```csharp -services.ConfigureOptions(options => -{ - options.ConnectionString = "Server=localhost;Database=myapp;"; - options.MaxRetries = 3; - options.CommandTimeout = 30; -}); +services + .AddFluentValidatedOptions(options => + { + options.ConnectionString = "Server=localhost;Database=myapp;"; + options.MaxRetries = 3; + options.CommandTimeout = 30; + }) + .ValidateOnStart(); ``` ### Complete Startup Example ```csharp using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; var builder = WebApplication.CreateBuilder(args); -// Register validated options -builder.Services.ConfigureOptions(builder.Configuration); -builder.Services.ConfigureOptions(builder.Configuration); -builder.Services.ConfigureOptions(builder.Configuration); +// Register multiple validated options with automatic startup validation +builder.Services + .AddFluentValidatedOptions(builder.Configuration) + .ValidateOnStart(); + +builder.Services + .AddFluentValidatedOptions(builder.Configuration) + .ValidateOnStart(); + +builder.Services + .AddFluentValidatedOptions(builder.Configuration) + .ValidateOnStart(); var app = builder.Build(); +app.Run(); // All options validated before this runs +``` + +If any configuration is invalid, the application will **fail to start** with clear error messages indicating exactly which settings are invalid and why. -// Validate all options at startup (recommended) -app.Services.ValidateOptions(builder.Services, app.Logger); +### Alternative: Simple Registration (Without Startup Validation) -app.Run(); +If you don't need automatic startup validation, you can use the simpler `ConfigureOptions()` method: + +```csharp +// Simpler syntax, but validates on first access instead of at startup +services.ConfigureOptions(configuration); ``` -If any configuration is invalid, the application will fail to start with clear error messages indicating exactly which settings are invalid and why. +**Comparison:** + +| Approach | Validates | Supports `.ValidateOnStart()` | Best For | +|----------|-----------|-------------------------------|----------| +| `AddFluentValidatedOptions()` | At startup (with `.ValidateOnStart()`) | ✅ Yes | Production apps - fail fast on invalid config | +| `ConfigureOptions()` | On first access | ❌ No | Simple scenarios, development | ## Features +- **Automatic Startup Validation**: Use `.ValidateOnStart()` to fail fast on invalid configuration - **Automatic Section Discovery**: Uses the class name as the configuration section name by default - **Custom Section Mapping**: Use `[SectionName]` attribute to override the section name -- **Seamless Integration**: Works with Microsoft.Extensions.Options infrastructure -- **Startup Validation**: Optional helper method to validate all options when the application starts -- **Clear Error Messages**: FluentValidation provides detailed, actionable error messages +- **Seamless Integration**: Works with Microsoft.Extensions.Options infrastructure and `OptionsBuilder` +- **FluentValidation Power**: Rich validation rules, custom validators, conditional validation +- **Clear Error Messages**: Detailed, actionable error messages from FluentValidation - **Type Safety**: Strongly-typed options with compile-time checking +- **Flexible API**: Choose between fluent API (with `.ValidateOnStart()`) or simple registration ## Releases From 7153df154e28f601503c109c93c4a30e9f36e92a Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 15:22:32 +0700 Subject: [PATCH 20/37] BREAKING: Remove backward compatibility and obsolete code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we're in beta, remove unnecessary backward compatibility: Removed: - ConfigureOptions() methods - Use AddFluentValidatedOptions() instead - ServiceProviderExtensions.ValidateOptions() - Use .ValidateOnStart() instead - Manual validation helper methods Updated documentation: - Use nested sealed Validator classes as recommended pattern - All examples now show DatabaseOptions.Validator pattern - Removed comparison tables between old/new APIs - Simplified documentation to single recommended approach Breaking changes for beta users: - Old: services.ConfigureOptions(config) New: services.AddFluentValidatedOptions(config).ValidateOnStart() - Old: serviceProvider.ValidateOptions(services, logger) New: Use .ValidateOnStart() when registering options Benefits: - Cleaner, simpler API with single recommended pattern - Better follows .NET conventions - Nested sealed validators reduce namespace pollution - No obsolete/legacy code to maintain 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 26 +++---- .../Extensions/OptionsBuilderExtensions.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 73 +----------------- .../Extensions/ServiceProviderExtensions.cs | 68 ---------------- readme.md | 77 +++++++++---------- 5 files changed, 50 insertions(+), 196 deletions(-) delete mode 100644 Source/TimeWarp.OptionsValidation/Extensions/ServiceProviderExtensions.cs diff --git a/CLAUDE.md b/CLAUDE.md index ae83991..41fec23 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,10 +60,9 @@ dotnet outdated - Located in [Source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs](Source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs) **ServiceCollectionExtensions** -- Provides two API patterns for configuring options with FluentValidation: - - `AddFluentValidatedOptions()` - Returns `OptionsBuilder`, supports `.ValidateOnStart()` - - `ConfigureOptions()` - Returns `IServiceCollection`, simpler API -- Two overloads for each: one accepts `IConfiguration`, the other accepts `Action` +- Provides `AddFluentValidatedOptions()` extension methods +- Two overloads: one accepts `IConfiguration`, the other accepts `Action` +- Returns `OptionsBuilder`, enabling chaining with `.ValidateOnStart()` - Automatically discovers configuration section names via `SectionNameAttribute` or defaults to type name - Registers both the validator and the `IValidateOptions` implementation - Located in [Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs](Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs) @@ -79,29 +78,26 @@ dotnet outdated - Applied to options classes when the section name differs from the class name - Located in [Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs](Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs) -### API Patterns +### API Usage -The library provides two API patterns: - -**Fluent API (Recommended for Production)** +**With Startup Validation (Recommended)** ```csharp services.AddFluentValidatedOptions(configuration) .ValidateOnStart(); // Validates at startup, fails fast ``` - Returns `OptionsBuilder` for method chaining -- Supports `.ValidateOnStart()` for automatic startup validation +- Validates automatically at startup with `.ValidateOnStart()` - Integrates with host lifecycle (validates before app runs) - Fails fast on invalid configuration at startup -- Best for production applications +- Recommended for production applications -**Simple API (For Development/Simple Scenarios)** +**Without Startup Validation** ```csharp -services.ConfigureOptions(configuration); +services.AddFluentValidatedOptions(configuration); +// Omit .ValidateOnStart() for lazy validation ``` -- Returns `IServiceCollection` for simple chaining - Validates on first access (lazy validation) -- Simpler syntax for basic scenarios -- Best for development or when startup validation isn't critical +- Useful for development or when startup validation isn't needed ### Usage Pattern diff --git a/Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs b/Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs index 90279bf..1fa6f02 100644 --- a/Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs +++ b/Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs @@ -22,7 +22,7 @@ public static class OptionsBuilderExtensions /// /// services.AddOptions<DatabaseOptions>() /// .Bind(configuration.GetSection("Database")) - /// .ValidateFluentValidation<DatabaseOptions, DatabaseOptionsValidator>() + /// .ValidateFluentValidation<DatabaseOptions, DatabaseOptions.Validator>() /// .ValidateOnStart(); /// /// diff --git a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs b/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs index a26295b..7c0a555 100644 --- a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs +++ b/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs @@ -16,11 +16,11 @@ public static class ServiceCollectionExtensions /// /// /// // Binds from "DatabaseOptions" section in appsettings.json - /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptionsValidator>(configuration) + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) /// .ValidateOnStart(); /// /// // Or with custom section name using [SectionName("Database")] attribute - /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptionsValidator>(configuration) + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) /// .ValidateOnStart(); /// /// @@ -53,7 +53,7 @@ public static OptionsBuilder AddFluentValidatedOptionsThe OptionsBuilder for method chaining (supports .ValidateOnStart()) /// /// - /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptionsValidator>(options => + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(options => /// { /// options.ConnectionString = "Server=localhost;Database=myapp;"; /// options.MaxRetries = 3; @@ -72,72 +72,5 @@ public static OptionsBuilder AddFluentValidatedOptions(); } - /// - /// Configures options with automatic configuration binding and FluentValidation. - /// This is a simpler alternative to AddFluentValidatedOptions that doesn't support .ValidateOnStart() - /// - /// The options type to configure and validate - /// The FluentValidation validator type - /// The service collection - /// The configuration instance to bind from - /// The service collection for method chaining - /// - /// This method validates options on first access, not at startup. - /// For startup validation, use AddFluentValidatedOptions().ValidateOnStart() instead. - /// - public static IServiceCollection ConfigureOptions( - this IServiceCollection aServiceCollection, - IConfiguration aConfiguration) - where TOptions : class - where TOptionsValidator : AbstractValidator - { - Type type = typeof(TOptions); - var sectionNameAttribute = (SectionNameAttribute?)type.GetCustomAttributes(typeof(SectionNameAttribute), false).FirstOrDefault(); - string sectionName = sectionNameAttribute?.SectionName ?? type.Name; - IConfigurationSection configurationSection = aConfiguration.GetSection(sectionName); - - aServiceCollection.Configure(configurationSection); - return RegisterOptionsValidator(aServiceCollection); - } - - /// - /// Configures options with programmatic configuration and FluentValidation. - /// This is a simpler alternative to AddFluentValidatedOptions that doesn't support .ValidateOnStart() - /// - /// The options type to configure and validate - /// The FluentValidation validator type - /// The service collection - /// Action to configure the options - /// The service collection for method chaining - /// - /// This method validates options on first access, not at startup. - /// For startup validation, use AddFluentValidatedOptions().ValidateOnStart() instead. - /// - public static IServiceCollection ConfigureOptions( - this IServiceCollection aServiceCollection, - Action aOptionsAction) - where TOptions : class - where TOptionsValidator : AbstractValidator - { - aServiceCollection.Configure(aOptionsAction); - return RegisterOptionsValidator(aServiceCollection); - } - - private static IServiceCollection RegisterOptionsValidator( - IServiceCollection aServiceCollection) - where TOptions : class - where TOptionsValidator : AbstractValidator - { - aServiceCollection.TryAddSingleton(); - - aServiceCollection.TryAddEnumerable( - ServiceDescriptor.Singleton< - IValidateOptions, - OptionsValidation - >() - ); - - return aServiceCollection; - } } diff --git a/Source/TimeWarp.OptionsValidation/Extensions/ServiceProviderExtensions.cs b/Source/TimeWarp.OptionsValidation/Extensions/ServiceProviderExtensions.cs deleted file mode 100644 index 2ae7f25..0000000 --- a/Source/TimeWarp.OptionsValidation/Extensions/ServiceProviderExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Microsoft.Extensions.DependencyInjection; - -using Logging; - -/// -/// Run Validation on all the IOptions that have validation -/// This will iterate through all the IConfigureOptions in the IServiceCollection -/// Then it will access each of those which will trigger the validation. -/// -public static class ServiceProviderExtensions -{ - public static void ValidateOptions - ( - this IServiceProvider serviceProvider, - IServiceCollection serviceCollection, - ILogger logger - ) - { - using IServiceScope scope = serviceProvider.CreateScope(); - IServiceProvider scopedProvider = scope.ServiceProvider; - ValidateOptionsInternal(scopedProvider, serviceCollection, logger); - } - - private static void ValidateOptionsInternal - ( - this IServiceProvider serviceProvider, - IServiceCollection serviceCollection, - ILogger logger - ) - { - IEnumerable optionTypes = - serviceCollection - .Where - ( - serviceDescriptor => - serviceDescriptor.ServiceType.IsGenericType && - serviceDescriptor.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>) - ) - .Select - ( - serviceDescriptor => serviceDescriptor.ServiceType.GetGenericArguments()[0] - ).Distinct(); - - Func? originalDisplayNameResolver = ValidatorOptions.Global.DisplayNameResolver; - - ValidatorOptions.Global.DisplayNameResolver = - (type, memberInfo, _) => - type != null && memberInfo != null ? $"{type.Name}:{memberInfo.Name}" : null; - - - foreach (Type optionType in optionTypes) - { - try - { - Type optionsAccessorType = typeof(IOptions<>).MakeGenericType(new Type[] { optionType }); - object? optionsAccessor = serviceProvider.GetService(optionsAccessorType); - // Accessing the value triggers the validation. - object? _ = optionsAccessor?.GetType().GetProperty(nameof(IOptions.Value))?.GetValue(optionsAccessor); - } - catch (Exception e) - { - logger.LogWarning("Failed to validate options for {Name}: {Message}", optionType.Name, e.Message); - } - } - - ValidatorOptions.Global.DisplayNameResolver = originalDisplayNameResolver; - } -} diff --git a/readme.md b/readme.md index e62e145..1ac39e4 100644 --- a/readme.md +++ b/readme.md @@ -45,47 +45,44 @@ You can see the latest NuGet packages from the official [TimeWarp NuGet page](ht ## Usage -### Recommended: Fluent API with Automatic Startup Validation +### Basic Setup with Automatic Startup Validation -The recommended approach uses `AddFluentValidatedOptions()` which returns `OptionsBuilder`, allowing you to chain with `.ValidateOnStart()` for automatic startup validation. +Use `AddFluentValidatedOptions()` which returns `OptionsBuilder`, allowing you to chain with `.ValidateOnStart()` for automatic startup validation. -#### 1. Define Your Options Class +#### 1. Define Your Options Class with Nested Validator ```csharp +using FluentValidation; + public class DatabaseOptions { public string ConnectionString { get; set; } = string.Empty; public int MaxRetries { get; set; } public int CommandTimeout { get; set; } -} -``` - -#### 2. Create a FluentValidation Validator -```csharp -using FluentValidation; - -public class DatabaseOptionsValidator : AbstractValidator -{ - public DatabaseOptionsValidator() + // Nested validator - sealed and only used here + public sealed class Validator : AbstractValidator { - RuleFor(x => x.ConnectionString) - .NotEmpty() - .WithMessage("Database connection string is required"); - - RuleFor(x => x.MaxRetries) - .GreaterThan(0) - .LessThanOrEqualTo(10) - .WithMessage("MaxRetries must be between 1 and 10"); - - RuleFor(x => x.CommandTimeout) - .GreaterThanOrEqualTo(30) - .WithMessage("CommandTimeout must be at least 30 seconds"); + public Validator() + { + RuleFor(x => x.ConnectionString) + .NotEmpty() + .WithMessage("Database connection string is required"); + + RuleFor(x => x.MaxRetries) + .GreaterThan(0) + .LessThanOrEqualTo(10) + .WithMessage("MaxRetries must be between 1 and 10"); + + RuleFor(x => x.CommandTimeout) + .GreaterThanOrEqualTo(30) + .WithMessage("CommandTimeout must be at least 30 seconds"); + } } } ``` -#### 3. Register with Automatic Startup Validation +#### 2. Register with Automatic Startup Validation ```csharp using Microsoft.Extensions.DependencyInjection; @@ -94,7 +91,7 @@ var builder = WebApplication.CreateBuilder(args); // Register options with automatic startup validation builder.Services - .AddFluentValidatedOptions(builder.Configuration) + .AddFluentValidatedOptions(builder.Configuration) .ValidateOnStart(); // ✅ Validates when host starts, throws on error var app = builder.Build(); @@ -126,7 +123,7 @@ By default, the library looks for a configuration section with the same name as ```csharp services - .AddFluentValidatedOptions(configuration) + .AddFluentValidatedOptions(configuration) .ValidateOnStart(); ``` @@ -163,7 +160,7 @@ You can also configure options programmatically without IConfiguration: ```csharp services - .AddFluentValidatedOptions(options => + .AddFluentValidatedOptions(options => { options.ConnectionString = "Server=localhost;Database=myapp;"; options.MaxRetries = 3; @@ -181,15 +178,15 @@ var builder = WebApplication.CreateBuilder(args); // Register multiple validated options with automatic startup validation builder.Services - .AddFluentValidatedOptions(builder.Configuration) + .AddFluentValidatedOptions(builder.Configuration) .ValidateOnStart(); builder.Services - .AddFluentValidatedOptions(builder.Configuration) + .AddFluentValidatedOptions(builder.Configuration) .ValidateOnStart(); builder.Services - .AddFluentValidatedOptions(builder.Configuration) + .AddFluentValidatedOptions(builder.Configuration) .ValidateOnStart(); var app = builder.Build(); @@ -198,21 +195,17 @@ app.Run(); // All options validated before this runs If any configuration is invalid, the application will **fail to start** with clear error messages indicating exactly which settings are invalid and why. -### Alternative: Simple Registration (Without Startup Validation) +### Without Startup Validation -If you don't need automatic startup validation, you can use the simpler `ConfigureOptions()` method: +If you don't need automatic startup validation, simply omit `.ValidateOnStart()`: ```csharp -// Simpler syntax, but validates on first access instead of at startup -services.ConfigureOptions(configuration); +// Validates on first access instead of at startup +services.AddFluentValidatedOptions(configuration); +// No .ValidateOnStart() call - validation happens lazily ``` -**Comparison:** - -| Approach | Validates | Supports `.ValidateOnStart()` | Best For | -|----------|-----------|-------------------------------|----------| -| `AddFluentValidatedOptions()` | At startup (with `.ValidateOnStart()`) | ✅ Yes | Production apps - fail fast on invalid config | -| `ConfigureOptions()` | On first access | ❌ No | Simple scenarios, development | +This approach validates options when they're first accessed rather than at application startup. ## Features From c7919c907567e7d74bc2fb3e1f0eb165a017c097 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 15:27:47 +0700 Subject: [PATCH 21/37] Document automatic configuration section name resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarify the two supported strategies for configuration section discovery: 1. Default: Class Name - DatabaseOptions → "DatabaseOptions" section - Simple and predictable 2. Custom: [SectionName] Attribute - [SectionName("Database")] → "Database" section - Explicit override when needed 3. Advanced: Manual Binding - For nested paths (MyApp:Settings:Database) - For complex scenarios Added explicit documentation about what is NOT supported: - ❌ Nested path auto-discovery - ❌ Suffix trimming (DatabaseOptions → Database) - ❌ Pluralization This makes the behavior clear and predictable for users. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- readme.md | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 1ac39e4..b6a2306 100644 --- a/readme.md +++ b/readme.md @@ -107,9 +107,21 @@ app.Run(); // Validation happens automatically before this runs ### Configuration Binding -#### Using appsettings.json +The library automatically discovers which configuration section to bind based on simple, predictable rules. -By default, the library looks for a configuration section with the same name as your options class: +#### Default: Class Name + +By default, the library uses the **class name** as the configuration section name: + +```csharp +public class DatabaseOptions +{ + public string ConnectionString { get; set; } = string.Empty; + // ... +} +``` + +Binds to `"DatabaseOptions"` section: ```json { @@ -122,14 +134,15 @@ By default, the library looks for a configuration section with the same name as ``` ```csharp +// Automatically binds to "DatabaseOptions" section services .AddFluentValidatedOptions(configuration) .ValidateOnStart(); ``` -#### Custom Section Names +#### Custom Section Name with `[SectionName]` Attribute -Use the `[SectionName]` attribute to map to a different configuration section: +Override the default by decorating your options class with `[SectionName]`: ```csharp using TimeWarp.OptionsValidation; @@ -142,7 +155,7 @@ public class DatabaseOptions } ``` -Now it will bind to the "Database" section instead of "DatabaseOptions": +Now binds to `"Database"` section: ```json { @@ -154,6 +167,32 @@ Now it will bind to the "Database" section instead of "DatabaseOptions": } ``` +```csharp +// Automatically binds to "Database" section (via attribute) +services + .AddFluentValidatedOptions(configuration) + .ValidateOnStart(); +``` + +#### Advanced: Manual Section Path + +For complex scenarios (nested paths, environment-specific sections, etc.), use the manual approach: + +```csharp +// Manual binding to nested section path +services.AddOptions() + .Bind(configuration.GetSection("MyApp:Settings:Database")) + .ValidateFluentValidation() + .ValidateOnStart(); +``` + +**Automatic Section Name Resolution:** +- ✅ Uses class name: `DatabaseOptions` → `"DatabaseOptions"` +- ✅ Respects `[SectionName("Custom")]` attribute +- ❌ Does NOT automatically handle nested paths (use manual binding) +- ❌ Does NOT trim suffixes like "Options" +- ❌ Does NOT pluralize names + ### Programmatic Configuration You can also configure options programmatically without IConfiguration: From 4279ab7e8b2872f3f72f9d53cfe81ff6b3f5f2a8 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 15:32:08 +0700 Subject: [PATCH 22/37] Document nested path support in [SectionName] attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add comprehensive XML documentation to SectionNameAttribute showing nested path support - Expand readme section on custom section names with nested path examples - Update ServiceCollectionExtensions XML docs with nested path examples - Show all three resolution patterns: class name, simple section name, and nested paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Configuration/SectionNameAttribute.cs | 24 ++++++++- .../Extensions/ServiceCollectionExtensions.cs | 14 +++++- readme.md | 50 +++++++++++++++---- 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs b/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs index 79071e4..f97ddc8 100644 --- a/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs +++ b/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs @@ -1,15 +1,37 @@ namespace TimeWarp.OptionsValidation; /// -/// The section name in appsettings.json to which the class should be mapped +/// Specifies the configuration section path to bind the options class to. +/// Supports both simple names ("Database") and nested paths ("MyApp:Settings:Database"). /// +/// +/// +/// // Simple section name +/// [SectionName("Database")] +/// public class DatabaseOptions { } +/// +/// // Nested path using colon separator +/// [SectionName("MyApp:Settings:Database")] +/// public class DatabaseOptions { } +/// +/// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class SectionNameAttribute : Attribute { + /// + /// Initializes a new instance of the SectionNameAttribute. + /// + /// + /// The configuration section path. Can be a simple name ("Database") + /// or a nested path using colon separators ("MyApp:Settings:Database"). + /// public SectionNameAttribute(string sectionName) { SectionName = sectionName; } + /// + /// Gets the configuration section path. + /// public string SectionName { get; } } diff --git a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs b/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs index 7c0a555..87227f1 100644 --- a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs +++ b/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs @@ -13,13 +13,23 @@ public static class ServiceCollectionExtensions /// The service collection /// The configuration instance to bind from /// The OptionsBuilder for method chaining (supports .ValidateOnStart()) + /// + /// Section name resolution: + /// - Default: Uses class name (DatabaseOptions → "DatabaseOptions") + /// - Custom: Uses [SectionName("Database")] → "Database" + /// - Nested: Uses [SectionName("MyApp:Settings:Database")] → nested path + /// /// /// - /// // Binds from "DatabaseOptions" section in appsettings.json + /// // Default: Binds from "DatabaseOptions" section /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) /// .ValidateOnStart(); /// - /// // Or with custom section name using [SectionName("Database")] attribute + /// // Custom: [SectionName("Database")] binds from "Database" section + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) + /// .ValidateOnStart(); + /// + /// // Nested: [SectionName("MyApp:Settings:Database")] binds from nested path /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) /// .ValidateOnStart(); /// diff --git a/readme.md b/readme.md index b6a2306..03997ef 100644 --- a/readme.md +++ b/readme.md @@ -144,6 +144,7 @@ services Override the default by decorating your options class with `[SectionName]`: +**Simple Section Name:** ```csharp using TimeWarp.OptionsValidation; @@ -155,7 +156,7 @@ public class DatabaseOptions } ``` -Now binds to `"Database"` section: +Binds to `"Database"` section: ```json { @@ -167,31 +168,58 @@ Now binds to `"Database"` section: } ``` +**Nested Path with Colon Separator:** ```csharp -// Automatically binds to "Database" section (via attribute) +[SectionName("MyApp:Settings:Database")] +public class DatabaseOptions +{ + public string ConnectionString { get; set; } = string.Empty; + // ... +} +``` + +Binds to nested `"MyApp" → "Settings" → "Database"` path: + +```json +{ + "MyApp": { + "Settings": { + "Database": { + "ConnectionString": "Server=localhost;Database=myapp;", + "MaxRetries": 3, + "CommandTimeout": 30 + } + } + } +} +``` + +```csharp +// Automatically binds to section specified in attribute services .AddFluentValidatedOptions(configuration) .ValidateOnStart(); ``` -#### Advanced: Manual Section Path +#### Advanced: Manual Section Binding -For complex scenarios (nested paths, environment-specific sections, etc.), use the manual approach: +For dynamic section paths or complex scenarios not covered by the attribute: ```csharp -// Manual binding to nested section path +// Manual binding for runtime-determined paths +string environment = builder.Environment.EnvironmentName; services.AddOptions() - .Bind(configuration.GetSection("MyApp:Settings:Database")) + .Bind(configuration.GetSection($"{environment}:Database")) .ValidateFluentValidation() .ValidateOnStart(); ``` -**Automatic Section Name Resolution:** +**Automatic Section Name Resolution Summary:** - ✅ Uses class name: `DatabaseOptions` → `"DatabaseOptions"` -- ✅ Respects `[SectionName("Custom")]` attribute -- ❌ Does NOT automatically handle nested paths (use manual binding) -- ❌ Does NOT trim suffixes like "Options" -- ❌ Does NOT pluralize names +- ✅ Simple override: `[SectionName("Database")]` → `"Database"` +- ✅ Nested paths: `[SectionName("MyApp:Settings:Database")]` → `"MyApp" → "Settings" → "Database"` +- ❌ Does NOT trim suffixes like "Options" automatically +- ❌ Does NOT pluralize names automatically ### Programmatic Configuration From ddcea1b48224feec5af4f81907562781a035ac68 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 15:40:53 +0700 Subject: [PATCH 23/37] Rename SectionNameAttribute to ConfigurationKeyAttribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns with Microsoft's naming convention where IConfiguration.GetSection uses a 'key' parameter. This better reflects that the attribute supports both simple keys and hierarchical paths with colon separators. Changes: - Rename SectionNameAttribute.cs to ConfigurationKeyAttribute.cs - Update attribute class name and property (SectionName -> Key) - Update ServiceCollectionExtensions to use ConfigurationKeyAttribute - Update readme.md with new attribute name and "hierarchical key" terminology - Update CLAUDE.md documentation with new attribute details - Add XML remarks noting alignment with IConfiguration.GetSection(string key) The term "key" is more accurate than "section name" since: 1. Matches Microsoft's GetSection parameter naming 2. Clearly supports hierarchical paths (e.g., "MyApp:Settings:Database") 3. Distinguishes from file system paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 19 +++++---- .../ConfigurationKeyAttribute.cs | 41 +++++++++++++++++++ .../Configuration/SectionNameAttribute.cs | 37 ----------------- .../Extensions/ServiceCollectionExtensions.cs | 20 ++++----- readme.md | 25 +++++------ 5 files changed, 75 insertions(+), 67 deletions(-) create mode 100644 Source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs delete mode 100644 Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs diff --git a/CLAUDE.md b/CLAUDE.md index 41fec23..f2a0c2a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,7 +63,8 @@ dotnet outdated - Provides `AddFluentValidatedOptions()` extension methods - Two overloads: one accepts `IConfiguration`, the other accepts `Action` - Returns `OptionsBuilder`, enabling chaining with `.ValidateOnStart()` -- Automatically discovers configuration section names via `SectionNameAttribute` or defaults to type name +- Automatically discovers configuration keys via `ConfigurationKeyAttribute` or defaults to type name +- Supports hierarchical keys with colon separators (e.g., "MyApp:Settings:Database") - Registers both the validator and the `IValidateOptions` implementation - Located in [Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs](Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs) @@ -73,10 +74,12 @@ dotnet outdated - Integrates FluentValidation with Microsoft.Extensions.Options infrastructure - Located in [Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs](Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs) -**SectionNameAttribute** -- Allows overriding the configuration section name -- Applied to options classes when the section name differs from the class name -- Located in [Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs](Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs) +**ConfigurationKeyAttribute** +- Allows overriding the configuration key for binding +- Applied to options classes when the key differs from the class name +- Supports simple keys ("Database") and hierarchical keys ("MyApp:Settings:Database") +- Aligns with Microsoft.Extensions.Configuration.IConfiguration.GetSection(string key) parameter naming +- Located in [Source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs](Source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs) ### API Usage @@ -102,9 +105,9 @@ services.AddFluentValidatedOptions(configuration); ### Usage Pattern 1. Define an options class (e.g., `MyOptions`) -2. Create a FluentValidation validator (e.g., `MyOptionsValidator : AbstractValidator`) -3. Optionally decorate options class with `[SectionName("ConfigSectionName")]` -4. Register with fluent API: `services.AddFluentValidatedOptions(configuration).ValidateOnStart()` +2. Create a nested sealed FluentValidation validator (e.g., `MyOptions.Validator : AbstractValidator`) +3. Optionally decorate options class with `[ConfigurationKey("Key")]` or `[ConfigurationKey("App:Settings:Key")]` +4. Register with fluent API: `services.AddFluentValidatedOptions(configuration).ValidateOnStart()` 5. Validation executes automatically at startup (with `.ValidateOnStart()`) or on first access (without) ### Project Structure diff --git a/Source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs b/Source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs new file mode 100644 index 0000000..5bcda4f --- /dev/null +++ b/Source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs @@ -0,0 +1,41 @@ +namespace TimeWarp.OptionsValidation; + +/// +/// Specifies the configuration key to bind the options class to. +/// Supports both simple keys ("Database") and hierarchical keys ("MyApp:Settings:Database"). +/// +/// +/// This attribute aligns with , +/// which accepts a configuration key that can represent both simple section names and nested paths using colon separators. +/// +/// +/// +/// // Simple configuration key +/// [ConfigurationKey("Database")] +/// public class DatabaseOptions { } +/// +/// // Hierarchical key using colon separator +/// [ConfigurationKey("MyApp:Settings:Database")] +/// public class DatabaseOptions { } +/// +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +public sealed class ConfigurationKeyAttribute : Attribute +{ + /// + /// Initializes a new instance of the ConfigurationKeyAttribute. + /// + /// + /// The configuration key. Can be a simple key ("Database") + /// or a hierarchical key using colon separators ("MyApp:Settings:Database"). + /// + public ConfigurationKeyAttribute(string key) + { + Key = key; + } + + /// + /// Gets the configuration key. + /// + public string Key { get; } +} diff --git a/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs b/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs deleted file mode 100644 index f97ddc8..0000000 --- a/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace TimeWarp.OptionsValidation; - -/// -/// Specifies the configuration section path to bind the options class to. -/// Supports both simple names ("Database") and nested paths ("MyApp:Settings:Database"). -/// -/// -/// -/// // Simple section name -/// [SectionName("Database")] -/// public class DatabaseOptions { } -/// -/// // Nested path using colon separator -/// [SectionName("MyApp:Settings:Database")] -/// public class DatabaseOptions { } -/// -/// -[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] -public sealed class SectionNameAttribute : Attribute -{ - /// - /// Initializes a new instance of the SectionNameAttribute. - /// - /// - /// The configuration section path. Can be a simple name ("Database") - /// or a nested path using colon separators ("MyApp:Settings:Database"). - /// - public SectionNameAttribute(string sectionName) - { - SectionName = sectionName; - } - - /// - /// Gets the configuration section path. - /// - public string SectionName { get; } -} diff --git a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs b/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs index 87227f1..54321e0 100644 --- a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs +++ b/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs @@ -14,10 +14,10 @@ public static class ServiceCollectionExtensions /// The configuration instance to bind from /// The OptionsBuilder for method chaining (supports .ValidateOnStart()) /// - /// Section name resolution: + /// Configuration key resolution: /// - Default: Uses class name (DatabaseOptions → "DatabaseOptions") - /// - Custom: Uses [SectionName("Database")] → "Database" - /// - Nested: Uses [SectionName("MyApp:Settings:Database")] → nested path + /// - Custom: Uses [ConfigurationKey("Database")] → "Database" + /// - Hierarchical: Uses [ConfigurationKey("MyApp:Settings:Database")] → nested path /// /// /// @@ -25,11 +25,11 @@ public static class ServiceCollectionExtensions /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) /// .ValidateOnStart(); /// - /// // Custom: [SectionName("Database")] binds from "Database" section + /// // Custom: [ConfigurationKey("Database")] binds from "Database" section /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) /// .ValidateOnStart(); /// - /// // Nested: [SectionName("MyApp:Settings:Database")] binds from nested path + /// // Hierarchical: [ConfigurationKey("MyApp:Settings:Database")] binds from nested path /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) /// .ValidateOnStart(); /// @@ -40,15 +40,15 @@ public static OptionsBuilder AddFluentValidatedOptions { - // Auto-discover section name using SectionNameAttribute or class name + // Auto-discover configuration key using ConfigurationKeyAttribute or class name Type type = typeof(TOptions); - var sectionNameAttribute = (SectionNameAttribute?)type - .GetCustomAttributes(typeof(SectionNameAttribute), false) + var configurationKeyAttribute = (ConfigurationKeyAttribute?)type + .GetCustomAttributes(typeof(ConfigurationKeyAttribute), false) .FirstOrDefault(); - string sectionName = sectionNameAttribute?.SectionName ?? type.Name; + string key = configurationKeyAttribute?.Key ?? type.Name; return services.AddOptions() - .Bind(configuration.GetSection(sectionName)) + .Bind(configuration.GetSection(key)) .ValidateFluentValidation(); } diff --git a/readme.md b/readme.md index 03997ef..8d952f3 100644 --- a/readme.md +++ b/readme.md @@ -140,15 +140,15 @@ services .ValidateOnStart(); ``` -#### Custom Section Name with `[SectionName]` Attribute +#### Custom Configuration Key with `[ConfigurationKey]` Attribute -Override the default by decorating your options class with `[SectionName]`: +Override the default by decorating your options class with `[ConfigurationKey]`: -**Simple Section Name:** +**Simple Configuration Key:** ```csharp using TimeWarp.OptionsValidation; -[SectionName("Database")] +[ConfigurationKey("Database")] public class DatabaseOptions { public string ConnectionString { get; set; } = string.Empty; @@ -168,9 +168,9 @@ Binds to `"Database"` section: } ``` -**Nested Path with Colon Separator:** +**Hierarchical Key with Colon Separator:** ```csharp -[SectionName("MyApp:Settings:Database")] +[ConfigurationKey("MyApp:Settings:Database")] public class DatabaseOptions { public string ConnectionString { get; set; } = string.Empty; @@ -195,7 +195,7 @@ Binds to nested `"MyApp" → "Settings" → "Database"` path: ``` ```csharp -// Automatically binds to section specified in attribute +// Automatically binds to configuration key specified in attribute services .AddFluentValidatedOptions(configuration) .ValidateOnStart(); @@ -214,10 +214,10 @@ services.AddOptions() .ValidateOnStart(); ``` -**Automatic Section Name Resolution Summary:** +**Automatic Configuration Key Resolution Summary:** - ✅ Uses class name: `DatabaseOptions` → `"DatabaseOptions"` -- ✅ Simple override: `[SectionName("Database")]` → `"Database"` -- ✅ Nested paths: `[SectionName("MyApp:Settings:Database")]` → `"MyApp" → "Settings" → "Database"` +- ✅ Simple override: `[ConfigurationKey("Database")]` → `"Database"` +- ✅ Hierarchical paths: `[ConfigurationKey("MyApp:Settings:Database")]` → `"MyApp" → "Settings" → "Database"` - ❌ Does NOT trim suffixes like "Options" automatically - ❌ Does NOT pluralize names automatically @@ -277,8 +277,9 @@ This approach validates options when they're first accessed rather than at appli ## Features - **Automatic Startup Validation**: Use `.ValidateOnStart()` to fail fast on invalid configuration -- **Automatic Section Discovery**: Uses the class name as the configuration section name by default -- **Custom Section Mapping**: Use `[SectionName]` attribute to override the section name +- **Automatic Key Discovery**: Uses the class name as the configuration key by default +- **Custom Key Mapping**: Use `[ConfigurationKey]` attribute to override the configuration key +- **Hierarchical Keys**: Support for nested configuration paths using colon separators - **Seamless Integration**: Works with Microsoft.Extensions.Options infrastructure and `OptionsBuilder` - **FluentValidation Power**: Rich validation rules, custom validators, conditional validation - **Clear Error Messages**: Detailed, actionable error messages from FluentValidation From cb1f5eb4d79723455b5ac9624545c996ae33f9fa Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 15:50:57 +0700 Subject: [PATCH 24/37] Replace separate CI/CD workflows with unified workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following the Nuru repository pattern, consolidate ci-build.yml and release-build.yml into a single ci-cd.yml workflow. Changes: - Create unified ci-cd.yml that runs on PRs, master push, and releases - Build and test always run for validation - Publishing to NuGet only happens on GitHub release creation - Remove old ci-build.yml and release-build.yml - Update to .NET 10.0.x - Use artifacts/packages/ output path - Add path filtering to skip unnecessary runs - Upload build artifacts for all runs (helps debugging) Benefits: - Single source of truth for build/test/publish logic - Better control: publish only when explicitly creating a release - Consistent with other TimeWarp repositories - Reduced maintenance overhead 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/ci-build.yml | 54 -------------- .github/workflows/ci-cd.yml | 82 +++++++++++++++++++++ .github/workflows/release-build.yml | 108 ---------------------------- 3 files changed, 82 insertions(+), 162 deletions(-) delete mode 100644 .github/workflows/ci-build.yml create mode 100644 .github/workflows/ci-cd.yml delete mode 100644 .github/workflows/release-build.yml diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml deleted file mode 100644 index c855e14..0000000 --- a/.github/workflows/ci-build.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Build and Test - -on: - pull_request: - workflow_dispatch: - -env: - DOTNET_NOLOGO: true # Disable the .NET logo in the console output - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true # Disable the .NET first time experience to skip caching NuGet packages and speed up the build - DOTNET_CLI_TELEMETRY_OPTOUT: true - NUGET_AUTH_TOKEN: ${{secrets.PUBLISH_TO_NUGET_ORG}} # <-- This is the token for the GitHub account you want to use. - -jobs: - build-and-test: - runs-on: ubuntu-latest - strategy: - matrix: - project: - - name: TimeWarp.OptionsValidation - path: Source/TimeWarp.OptionsValidation/ - testPath: Tests/TimeWarp.OptionsValidation.Tests/ - - steps: - - name: Print Job Info - run: | - echo "🎉 Job triggered by a ${{ github.event_name }} event." - echo "🐧 Running on a ${{ runner.os }} server hosted by GitHub." - echo "🔎 Branch name: ${{ github.ref }}, repository: ${{ github.repository }}." - - - name: Check out repository code - uses: actions/checkout@v3 - - - name: Setup .NET - uses: actions/setup-dotnet@v2 - with: - dotnet-version: 8.0.x - - - name: Build ${{ matrix.project.name }} - run: | - cd ${{ matrix.project.path }} - dotnet build --configuration Debug - shell: pwsh - - - name: Test ${{ matrix.project.name }} - run: | - cd ${{ matrix.project.testPath }} - dotnet tool restore - dotnet restore - dotnet fixie --configuration Debug - shell: pwsh - - - name: Print Job Status - run: | - echo "🍏 Job status: ${{ job.status }}." diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..b69d8da --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,82 @@ +name: NuGet Publish + +on: + push: + branches: + - master + paths: + - 'Source/**' + - 'Tests/**' + - '.github/workflows/**' + - 'Directory.Build.props' + - 'Directory.Packages.props' + pull_request: + branches: + - master + paths: + - 'Source/**' + - 'Tests/**' + - '.github/workflows/**' + - 'Directory.Build.props' + - 'Directory.Packages.props' + release: + types: [published] # Triggered when a release is published via GitHub Releases UI or gh CLI + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 1.0.0-beta.3)' + required: false + type: string + +jobs: + build-and-publish: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Create artifacts directory + run: mkdir -p artifacts/packages + + - name: Build + run: dotnet build --configuration Release + + - name: Test + run: | + cd Tests/TimeWarp.OptionsValidation.Tests + dotnet tool restore + dotnet restore + dotnet fixie --configuration Release + + - name: Publish to NuGet.org (Releases only) + if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.version != '') + run: | + # Get version from either release tag or manual input + if [ "${{ github.event_name }}" == "release" ]; then + VERSION="${{ github.event.release.tag_name }}" + VERSION="${VERSION#v}" # Remove 'v' prefix if present + else + VERSION="${{ github.event.inputs.version }}" + fi + + echo "Publishing version: $VERSION" + + dotnet nuget push artifacts/packages/TimeWarp.OptionsValidation.$VERSION.nupkg \ + --api-key ${{ secrets.PUBLISH_TO_NUGET_ORG }} \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate + env: + DOTNET_NUGET_SIGNATURE_VERIFICATION: false + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: Packages-${{ github.run_number }} + path: artifacts/packages/*.nupkg diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml deleted file mode 100644 index b7df503..0000000 --- a/.github/workflows/release-build.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: Build and Deploy - -on: - push: - branches: - - master - workflow_dispatch: - -env: - DOTNET_NOLOGO: true # Disable the .NET logo in the console output - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true # Disable the .NET first time experience to skip caching NuGet packages and speed up the build - DOTNET_CLI_TELEMETRY_OPTOUT: true - NUGET_AUTH_TOKEN: ${{ secrets.PUBLISH_TO_NUGET_ORG }} # <-- This is the token for the GitHub account you want to use. - -defaults: - run: - shell: pwsh - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - - steps: - - name: Print Job Info - run: | - echo "🎉 Job triggered by a ${{ github.event_name }} event." - echo "🐧 Running on a ${{ runner.os }} server hosted by GitHub." - echo "🔎 Branch name: ${{ github.ref }}, repository: ${{ github.repository }}." - - - name: Check out repository code - uses: actions/checkout@v3 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - - name: Build TimeWarp.OptionsValidation - run: | - cd Source/TimeWarp.OptionsValidation/ - dotnet build --configuration Debug - - - name: Publish TimeWarp.OptionsValidation - run: | - cd Source/TimeWarp.OptionsValidation/bin/Packages - dotnet nuget push *.nupkg --skip-duplicate --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.PUBLISH_TO_NUGET_ORG }} - - - name: Verify Directory.Build.props - run: | - if (Test-Path -Path "Directory.Build.props") { - Get-Content -Path "Directory.Build.props" - } else { - Write-Error "Directory.Build.props not found at the expected path." - } - - - name: Tag commit with version - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - $version = (Select-String -Path "Directory.Build.props" -Pattern '(.*?)').Matches.Groups[1].Value - git tag -a "$version" -m "Release $version" - git push origin "$version" - - - name: Check if version is not a pre-release - id: check_pre_release - run: | - $version = (Select-String -Path "Directory.Build.props" -Pattern '(.*?)').Matches.Groups[1].Value - $isPreRelease = $version -match '-(alpha|beta)' - echo "IsPreRelease=$isPreRelease" - # Setting output that indicates whether it's a pre-release version - echo "::set-output name=IS_PRE_RELEASE::$isPreRelease" - - - name: Generate Release Notes - id: generate_release_notes - uses: release-drafter/release-drafter@v5 - with: - version: ${{ steps.tag_commit.outputs.VERSION }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Save Release Notes to File - if: steps.check_pre_release.outputs.IS_PRE_RELEASE == 'False' - run: | - $releaseNotes = "${{ steps.generate_release_notes.outputs.body }}" - $filePath = "Documentation/ReleaseNotes/Release_${{ steps.tag_commit.outputs.VERSION }}.md" - if (-not (Test-Path -Path "Documentation/ReleaseNotes")) { - New-Item -ItemType Directory -Path "Documentation/ReleaseNotes" -Force - } - Set-Content -Path $filePath -Value $releaseNotes - git add $filePath - git commit -m "Add release notes for version ${{ steps.tag_commit.outputs.VERSION }}" - git push origin master - - - name: Create GitHub Release - if: steps.check_pre_release.outputs.IS_PRE_RELEASE == 'False' - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.tag_commit.outputs.VERSION }} - release_name: Release ${{ steps.tag_commit.outputs.VERSION }} - draft: true - prerelease: false - body: ${{ steps.generate_release_notes.outputs.body }} - - - name: Print Job Status - run: | - echo "🍏 Job status: ${{ job.status }}." From 701b0d43b4e09d53dd410232c916112a72f77a20 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 16:04:02 +0700 Subject: [PATCH 25/37] Simplify NuGet.config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unnecessary packageSourceMapping and disabledPackageSources sections. Only nuget.org source is needed for this project. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- NuGet.config | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/NuGet.config b/NuGet.config index 1abb5cd..80f5bd7 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,14 +4,4 @@ - - - - - - - - - - From 6080f2efd24b2b5f2546a6a89b2b931e73deaa60 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 16:05:10 +0700 Subject: [PATCH 26/37] Clean up repository files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove CodeMaid.config (unused configuration) - Remove Documentation/releases.md (outdated) - Rename CLAUDE.md to lowercase claude.md for consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CodeMaid.config | 71 --------------------------------------- Documentation/releases.md | 0 CLAUDE.md => claude.md | 0 3 files changed, 71 deletions(-) delete mode 100644 CodeMaid.config delete mode 100644 Documentation/releases.md rename CLAUDE.md => claude.md (100%) diff --git a/CodeMaid.config b/CodeMaid.config deleted file mode 100644 index 810ab01..0000000 --- a/CodeMaid.config +++ /dev/null @@ -1,71 +0,0 @@ - - - - -
- - - - - - False - - - False - - - True - - - 1 - - - \.Designer\.cs$||\.Designer\.vb$||\.resx$||\.min\.css$||\.min\.js$||\\lib\\ - - - False - - - False - - - 1 - - - Constructors||3||Constructors - - - Properties||2||Properties - - - Enums||7||Enums - - - Destructors||4||Destructors - - - Delegates||5||Delegates - - - Fields||1||Fields - - - Interfaces||8||Interfaces - - - Events||6||Events - - - True - - - True - - - True - - - - \ No newline at end of file diff --git a/Documentation/releases.md b/Documentation/releases.md deleted file mode 100644 index e69de29..0000000 diff --git a/CLAUDE.md b/claude.md similarity index 100% rename from CLAUDE.md rename to claude.md From 1e2f925682df6d66ce93d43192b1600b60f8c5f8 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 16:14:47 +0700 Subject: [PATCH 27/37] Reorganize Directory.Build.props following Nuru pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate repository-wide settings from package-specific settings by creating Source/Directory.Build.props for package metadata. Changes: - Create Source/Directory.Build.props with package metadata - Imports root Directory.Build.props - Sets IsPackable=true and GeneratePackageOnBuild=true - Contains all package metadata (Version, Authors, Description, etc.) - Includes shared package assets (logo, readme, license) - Update root Directory.Build.props - Remove package metadata (moved to Source/Directory.Build.props) - Remove EmbedLocal target (not used in this project) - Keep repository-wide settings (paths, analyzers, project defaults) - Simplify TimeWarp.OptionsValidation.csproj - Remove IsPackable and GeneratePackageOnBuild (now in Source/Directory.Build.props) - Remove package assets (now in Source/Directory.Build.props) - Keep only project-specific settings (RootNamespace, package references) Benefits: - Clear separation of concerns - Package settings apply only to Source/ projects - Test projects default to IsPackable=false - Follows Nuru repository pattern - Easier to maintain and understand 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Build.props | 24 ------------- Source/Directory.Build.props | 34 +++++++++++++++++++ .../TimeWarp.OptionsValidation.csproj | 8 ----- 3 files changed, 34 insertions(+), 32 deletions(-) create mode 100644 Source/Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props index 466e8ca..0cd289f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -70,22 +70,6 @@ $(NoWarn);CA1014;CA1031;CA1052;CA1515;CA1707;CA1724;CA1812;CA1848;CA1852;CA2007;RCS1102;IL2026;IL2067;IL2070;IL2075;IL3050;IL2104;IL3053 - - - Steven T. Cramer - TimeWarp Enterprises - TimeWarp.OptionsValidation - TimeWarp.OptionsValidation uses fluent validation to check your configuration settings. - git - https://github.com/TimeWarpEngineering/timewarp-options-validation.git - https://timewarpengineering.github.io/timewarp-options-validation/ - TimeWarp;Options Validation;OptionsValidation;FluentValidation;Configuration - 1.0.0-beta.3 - Unlicense - logo.png - readme.md - - @@ -95,12 +79,4 @@ - - - - - - - - diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props new file mode 100644 index 0000000..44ec259 --- /dev/null +++ b/Source/Directory.Build.props @@ -0,0 +1,34 @@ + + + + + + + 1.0.0-beta.3 + Steven T. Cramer + TimeWarp Enterprises + TimeWarp.OptionsValidation + Integrates FluentValidation with Microsoft.Extensions.Options for configuration validation at startup. + Copyright © 2025 TimeWarp Enterprises + Unlicense + logo.png + readme.md + https://github.com/TimeWarpEngineering/timewarp-options-validation + git + https://timewarpengineering.github.io/timewarp-options-validation/ + FluentValidation;Options;Configuration;Validation;Microsoft.Extensions.Options + See https://github.com/TimeWarpEngineering/timewarp-options-validation/releases + + + + true + true + + + + + + + + + diff --git a/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj b/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj index 8a02a9d..bc67a2c 100644 --- a/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj +++ b/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj @@ -1,17 +1,9 @@ - true TimeWarp.OptionsValidation - true - - - - - - From 26cfcb221c78cb07ef6800844d52233edb4a79c8 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 16:21:01 +0700 Subject: [PATCH 28/37] Add TimeWarp.Build.Tasks to Source/Directory.Build.props MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following Nuru pattern, add TimeWarp.Build.Tasks as a build task package reference in Source/Directory.Build.props so it applies to all source projects. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Packages.props | 11 ++++------- Source/Directory.Build.props | 4 ++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 8830420..a3cbc4a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,22 +1,22 @@ - true - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - - @@ -26,7 +26,6 @@ - @@ -34,11 +33,9 @@ - - \ No newline at end of file diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index 44ec259..cee3b8b 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -31,4 +31,8 @@ + + + + From 3c604fea6c3128fae3966cde2cc36dac1cabfb39 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 16:35:44 +0700 Subject: [PATCH 29/37] Add TimeWarp build packages to Source/Directory.Build.props MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add TimeWarp.Build.Tasks and TimeWarp.SourceGenerators to Source/Directory.Build.props following Nuru pattern. These packages apply to all source projects for build-time code generation and build tasks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Packages.props | 1 + Source/Directory.Build.props | 1 + 2 files changed, 2 insertions(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index a3cbc4a..cfb103a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,6 +13,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index cee3b8b..eb92710 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -33,6 +33,7 @@ + From 832243b57659971c8957ce0b19ed6131c2bf473e Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 17:04:38 +0700 Subject: [PATCH 30/37] Enable TWA001 kebab-case naming convention analyzer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set TWA001 analyzer rule to warning severity to enforce kebab-case file naming conventions. This will cause build errors (due to TreatWarningsAsErrors=true) that need to be fixed by renaming files to kebab-case. Files that need renaming will be fixed incrementally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index 573ba3c..d797f0a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -255,6 +255,9 @@ dotnet_naming_style.local_function_style.capitalization = pascal_case #### Analyizer settings #### dotnet_code_quality.null_check_validation_methods = NotNull +# TWA001: Enforce kebab-case naming convention +dotnet_diagnostic.TWA001.severity = warning + # CA1062: Validate arguments of public methods # TODO: Turn this back on when figure out how to get Dawn.Guard to not trigger it. dotnet_diagnostic.CA1062.severity = silent From 0ed537de1f7a951392d685453d4a714002d9eb65 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 17:15:28 +0700 Subject: [PATCH 31/37] Convert to .slnx and rename Source to source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete old .sln file and create new .slnx solution file - Rename Source/ directory to source/ for kebab-case compliance - Update Directory.Build.props: - Change SourceDirectory to source/ - Change SolutionFile to .slnx - Update test project to use $(SourceDirectory) variable instead of relative path 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Build.props | 4 +- .../TimeWarp.OptionsValidation.Tests.csproj | 2 +- {Source => source}/Directory.Build.props | 0 .../ConfigurationKeyAttribute.cs | 0 .../Configuration/OptionsValidation.cs | 0 .../Extensions/OptionsBuilderExtensions.cs | 0 .../Extensions/ServiceCollectionExtensions.cs | 0 .../GlobalSuppressions.cs | 0 .../GlobalUsings.cs | 0 .../TimeWarp.OptionsValidation.csproj | 0 .../TimeWarp.OptionsValidation/_Imports.cs | 0 timewarp-options-validation.sln | 43 ------------------- timewarp-options-validation.slnx | 8 ++++ 13 files changed, 11 insertions(+), 46 deletions(-) rename {Source => source}/Directory.Build.props (100%) rename {Source => source}/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs (100%) rename {Source => source}/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs (100%) rename {Source => source}/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs (100%) rename {Source => source}/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs (100%) rename {Source => source}/TimeWarp.OptionsValidation/GlobalSuppressions.cs (100%) rename {Source => source}/TimeWarp.OptionsValidation/GlobalUsings.cs (100%) rename {Source => source}/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj (100%) rename {Source => source}/TimeWarp.OptionsValidation/_Imports.cs (100%) delete mode 100644 timewarp-options-validation.sln create mode 100644 timewarp-options-validation.slnx diff --git a/Directory.Build.props b/Directory.Build.props index 0cd289f..673c37d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,9 +3,9 @@ timewarp-options-validation - $(RepositoryRoot)timewarp-options-validation.sln + $(RepositoryRoot)timewarp-options-validation.slnx $(MSBuildThisFileDirectory) - $(RepositoryRoot)Source/ + $(RepositoryRoot)source/ $(RepositoryRoot)Tests/ $(RepositoryRoot)Documentation/ $(RepositoryRoot)artifacts/ diff --git a/Tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj b/Tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj index 39d9258..5df74f2 100644 --- a/Tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj +++ b/Tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/Source/Directory.Build.props b/source/Directory.Build.props similarity index 100% rename from Source/Directory.Build.props rename to source/Directory.Build.props diff --git a/Source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs b/source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs similarity index 100% rename from Source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs rename to source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs diff --git a/Source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs b/source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs similarity index 100% rename from Source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs rename to source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs diff --git a/Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs b/source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs similarity index 100% rename from Source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs rename to source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs diff --git a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs b/source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs similarity index 100% rename from Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs rename to source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs diff --git a/Source/TimeWarp.OptionsValidation/GlobalSuppressions.cs b/source/TimeWarp.OptionsValidation/GlobalSuppressions.cs similarity index 100% rename from Source/TimeWarp.OptionsValidation/GlobalSuppressions.cs rename to source/TimeWarp.OptionsValidation/GlobalSuppressions.cs diff --git a/Source/TimeWarp.OptionsValidation/GlobalUsings.cs b/source/TimeWarp.OptionsValidation/GlobalUsings.cs similarity index 100% rename from Source/TimeWarp.OptionsValidation/GlobalUsings.cs rename to source/TimeWarp.OptionsValidation/GlobalUsings.cs diff --git a/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj b/source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj similarity index 100% rename from Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj rename to source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj diff --git a/Source/TimeWarp.OptionsValidation/_Imports.cs b/source/TimeWarp.OptionsValidation/_Imports.cs similarity index 100% rename from Source/TimeWarp.OptionsValidation/_Imports.cs rename to source/TimeWarp.OptionsValidation/_Imports.cs diff --git a/timewarp-options-validation.sln b/timewarp-options-validation.sln deleted file mode 100644 index f41d663..0000000 --- a/timewarp-options-validation.sln +++ /dev/null @@ -1,43 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{59C752B9-F1AF-478D-BACC-38074CB5E272}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitignore = .gitignore - CodeMaid.config = CodeMaid.config - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props - LICENSE = LICENSE - NuGet.config = NuGet.config - README.md = README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimeWarp.OptionsValidation.Tests", "Tests\TimeWarp.OptionsValidation.Tests\TimeWarp.OptionsValidation.Tests.csproj", "{A353C15D-D878-43E7-B567-AAE845FC41BA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimeWarp.OptionsValidation", "Source\TimeWarp.OptionsValidation\TimeWarp.OptionsValidation.csproj", "{CAC853CD-7002-4D2F-B53E-112653F779D5}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A353C15D-D878-43E7-B567-AAE845FC41BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A353C15D-D878-43E7-B567-AAE845FC41BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A353C15D-D878-43E7-B567-AAE845FC41BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A353C15D-D878-43E7-B567-AAE845FC41BA}.Release|Any CPU.Build.0 = Release|Any CPU - {CAC853CD-7002-4D2F-B53E-112653F779D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CAC853CD-7002-4D2F-B53E-112653F779D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CAC853CD-7002-4D2F-B53E-112653F779D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CAC853CD-7002-4D2F-B53E-112653F779D5}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {19789E18-C00D-400F-A284-2B9452DB9DFF} - EndGlobalSection -EndGlobal diff --git a/timewarp-options-validation.slnx b/timewarp-options-validation.slnx new file mode 100644 index 0000000..49bee80 --- /dev/null +++ b/timewarp-options-validation.slnx @@ -0,0 +1,8 @@ + + + + + + + + From e108789c2b92428d3bcad0d907ea0c0b50a04b63 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 19:57:42 +0700 Subject: [PATCH 32/37] Rename Tests to lowercase tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename Tests/ directory to tests/ for kebab-case compliance - Update Directory.Build.props TestsDirectory to tests/ - Update timewarp-options-validation.slnx to reference tests/ path 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Build.props | 2 +- .../TimeWarp.OptionsValidation.Tests/ConventionTests.cs | 0 .../TestingConvention/TestingConvention.cs | 0 .../TimeWarp.OptionsValidation.Tests.csproj | 0 timewarp-options-validation.slnx | 4 ++-- 5 files changed, 3 insertions(+), 3 deletions(-) rename {Tests => tests}/TimeWarp.OptionsValidation.Tests/ConventionTests.cs (100%) rename {Tests => tests}/TimeWarp.OptionsValidation.Tests/TestingConvention/TestingConvention.cs (100%) rename {Tests => tests}/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj (100%) diff --git a/Directory.Build.props b/Directory.Build.props index 673c37d..2695885 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ $(RepositoryRoot)timewarp-options-validation.slnx $(MSBuildThisFileDirectory) $(RepositoryRoot)source/ - $(RepositoryRoot)Tests/ + $(RepositoryRoot)tests/ $(RepositoryRoot)Documentation/ $(RepositoryRoot)artifacts/ $(ArtifactsDirectory)packages/ diff --git a/Tests/TimeWarp.OptionsValidation.Tests/ConventionTests.cs b/tests/TimeWarp.OptionsValidation.Tests/ConventionTests.cs similarity index 100% rename from Tests/TimeWarp.OptionsValidation.Tests/ConventionTests.cs rename to tests/TimeWarp.OptionsValidation.Tests/ConventionTests.cs diff --git a/Tests/TimeWarp.OptionsValidation.Tests/TestingConvention/TestingConvention.cs b/tests/TimeWarp.OptionsValidation.Tests/TestingConvention/TestingConvention.cs similarity index 100% rename from Tests/TimeWarp.OptionsValidation.Tests/TestingConvention/TestingConvention.cs rename to tests/TimeWarp.OptionsValidation.Tests/TestingConvention/TestingConvention.cs diff --git a/Tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj b/tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj similarity index 100% rename from Tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj rename to tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj diff --git a/timewarp-options-validation.slnx b/timewarp-options-validation.slnx index 49bee80..b291e17 100644 --- a/timewarp-options-validation.slnx +++ b/timewarp-options-validation.slnx @@ -2,7 +2,7 @@ - - + + From 31f76d41c3ba9e5dd022df8dceb6ac2af1e18e13 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 20:41:36 +0700 Subject: [PATCH 33/37] Convert all files and folders to kebab-case naming convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comprehensive rename to comply with TWA001 kebab-case analyzer rule. Source project changes: - Rename source/TimeWarp.OptionsValidation/ → source/timewarp-options-validation/ - Rename TimeWarp.OptionsValidation.csproj → timewarp-options-validation.csproj - Add AssemblyName property to maintain TimeWarp.OptionsValidation assembly name - Rename Configuration/ → configuration/ - Rename Extensions/ → extensions/ - Rename all .cs files to kebab-case: - ConfigurationKeyAttribute.cs → configuration-key-attribute.cs - OptionsValidation.cs → options-validation.cs - OptionsBuilderExtensions.cs → options-builder-extensions.cs - ServiceCollectionExtensions.cs → service-collection-extensions.cs - GlobalSuppressions.cs → global-suppressions.cs - GlobalUsings.cs → global-usings.cs - Delete unused _Imports.cs file Test project changes: - Rename tests/TimeWarp.OptionsValidation.Tests/ → tests/timewarp-options-validation-tests/ - Rename TimeWarp.OptionsValidation.Tests.csproj → timewarp-options-validation-tests.csproj - Add AssemblyName property to maintain TimeWarp.OptionsValidation.Tests assembly name - Rename TestingConvention/ → testing-convention/ - Rename all test .cs files to kebab-case: - ConventionTests.cs → convention-tests.cs - TestingConvention.cs → testing-convention.cs Configuration changes: - Update .editorconfig to suppress TWA001 for _*.cs files - Update solution file with new project paths - Update project references to use new kebab-case paths Build verified: 0 warnings, 0 errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .editorconfig | 3 ++- source/TimeWarp.OptionsValidation/_Imports.cs | 1 - .../configuration/configuration-key-attribute.cs} | 0 .../configuration/options-validation.cs} | 0 .../extensions/options-builder-extensions.cs} | 0 .../extensions/service-collection-extensions.cs} | 0 .../global-suppressions.cs} | 0 .../global-usings.cs} | 0 .../timewarp-options-validation.csproj} | 1 + .../convention-tests.cs} | 0 .../testing-convention/testing-convention.cs} | 0 .../timewarp-options-validation-tests.csproj} | 6 +++++- timewarp-options-validation.slnx | 4 ++-- 13 files changed, 10 insertions(+), 5 deletions(-) delete mode 100644 source/TimeWarp.OptionsValidation/_Imports.cs rename source/{TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs => timewarp-options-validation/configuration/configuration-key-attribute.cs} (100%) rename source/{TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs => timewarp-options-validation/configuration/options-validation.cs} (100%) rename source/{TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs => timewarp-options-validation/extensions/options-builder-extensions.cs} (100%) rename source/{TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs => timewarp-options-validation/extensions/service-collection-extensions.cs} (100%) rename source/{TimeWarp.OptionsValidation/GlobalSuppressions.cs => timewarp-options-validation/global-suppressions.cs} (100%) rename source/{TimeWarp.OptionsValidation/GlobalUsings.cs => timewarp-options-validation/global-usings.cs} (100%) rename source/{TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj => timewarp-options-validation/timewarp-options-validation.csproj} (90%) rename tests/{TimeWarp.OptionsValidation.Tests/ConventionTests.cs => timewarp-options-validation-tests/convention-tests.cs} (100%) rename tests/{TimeWarp.OptionsValidation.Tests/TestingConvention/TestingConvention.cs => timewarp-options-validation-tests/testing-convention/testing-convention.cs} (100%) rename tests/{TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj => timewarp-options-validation-tests/timewarp-options-validation-tests.csproj} (54%) diff --git a/.editorconfig b/.editorconfig index d797f0a..4de8261 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,8 +8,9 @@ root = true #### Core EditorConfig Options #### # So code cleanup will not run on save. -[_Imports.cs] +[_*.cs] generated_code = true +dotnet_diagnostic.TWA001.severity = none [*.csproj] generated_code = true diff --git a/source/TimeWarp.OptionsValidation/_Imports.cs b/source/TimeWarp.OptionsValidation/_Imports.cs deleted file mode 100644 index d2d2fca..0000000 --- a/source/TimeWarp.OptionsValidation/_Imports.cs +++ /dev/null @@ -1 +0,0 @@ -// global using xyz; diff --git a/source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs b/source/timewarp-options-validation/configuration/configuration-key-attribute.cs similarity index 100% rename from source/TimeWarp.OptionsValidation/Configuration/ConfigurationKeyAttribute.cs rename to source/timewarp-options-validation/configuration/configuration-key-attribute.cs diff --git a/source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs b/source/timewarp-options-validation/configuration/options-validation.cs similarity index 100% rename from source/TimeWarp.OptionsValidation/Configuration/OptionsValidation.cs rename to source/timewarp-options-validation/configuration/options-validation.cs diff --git a/source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs b/source/timewarp-options-validation/extensions/options-builder-extensions.cs similarity index 100% rename from source/TimeWarp.OptionsValidation/Extensions/OptionsBuilderExtensions.cs rename to source/timewarp-options-validation/extensions/options-builder-extensions.cs diff --git a/source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs b/source/timewarp-options-validation/extensions/service-collection-extensions.cs similarity index 100% rename from source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs rename to source/timewarp-options-validation/extensions/service-collection-extensions.cs diff --git a/source/TimeWarp.OptionsValidation/GlobalSuppressions.cs b/source/timewarp-options-validation/global-suppressions.cs similarity index 100% rename from source/TimeWarp.OptionsValidation/GlobalSuppressions.cs rename to source/timewarp-options-validation/global-suppressions.cs diff --git a/source/TimeWarp.OptionsValidation/GlobalUsings.cs b/source/timewarp-options-validation/global-usings.cs similarity index 100% rename from source/TimeWarp.OptionsValidation/GlobalUsings.cs rename to source/timewarp-options-validation/global-usings.cs diff --git a/source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj b/source/timewarp-options-validation/timewarp-options-validation.csproj similarity index 90% rename from source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj rename to source/timewarp-options-validation/timewarp-options-validation.csproj index bc67a2c..e1cddd4 100644 --- a/source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj +++ b/source/timewarp-options-validation/timewarp-options-validation.csproj @@ -1,6 +1,7 @@ + TimeWarp.OptionsValidation TimeWarp.OptionsValidation diff --git a/tests/TimeWarp.OptionsValidation.Tests/ConventionTests.cs b/tests/timewarp-options-validation-tests/convention-tests.cs similarity index 100% rename from tests/TimeWarp.OptionsValidation.Tests/ConventionTests.cs rename to tests/timewarp-options-validation-tests/convention-tests.cs diff --git a/tests/TimeWarp.OptionsValidation.Tests/TestingConvention/TestingConvention.cs b/tests/timewarp-options-validation-tests/testing-convention/testing-convention.cs similarity index 100% rename from tests/TimeWarp.OptionsValidation.Tests/TestingConvention/TestingConvention.cs rename to tests/timewarp-options-validation-tests/testing-convention/testing-convention.cs diff --git a/tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj b/tests/timewarp-options-validation-tests/timewarp-options-validation-tests.csproj similarity index 54% rename from tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj rename to tests/timewarp-options-validation-tests/timewarp-options-validation-tests.csproj index 5df74f2..adc150c 100644 --- a/tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj +++ b/tests/timewarp-options-validation-tests/timewarp-options-validation-tests.csproj @@ -1,5 +1,9 @@ + + TimeWarp.OptionsValidation.Tests + + @@ -7,7 +11,7 @@ - + \ No newline at end of file diff --git a/timewarp-options-validation.slnx b/timewarp-options-validation.slnx index b291e17..38a5a96 100644 --- a/timewarp-options-validation.slnx +++ b/timewarp-options-validation.slnx @@ -1,8 +1,8 @@ - + - + From ffed57c2101ff4e88d3b3c2ea3088d1c9db69145 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 20:55:52 +0700 Subject: [PATCH 34/37] Update fixie.console to version 4.1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update fixie.console from 3.4.0 to latest version 4.1.0. dotnet-outdated-tool already at latest (4.6.8). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a8448fd..7dafabe 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "fixie.console": { - "version": "3.4.0", + "version": "4.1.0", "commands": [ "fixie" ], From 7ad021691625e9e1953916994b92deb9f57751d0 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 21:05:48 +0700 Subject: [PATCH 35/37] Update TimeWarp.Fixie to version 3.1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update TimeWarp.Fixie from 2.0.0 to 3.1.0 (major version update). Build verified: 0 warnings, 0 errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index cfb103a..b5e59c8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,7 +25,7 @@ - + From 16ec964839bb64dde6ce1ff65fa330f32214e847 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 21:14:06 +0700 Subject: [PATCH 36/37] Replace FluentAssertions with Shouldly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove FluentAssertions package reference - Add Shouldly 4.3.0 package - Update test assertions to use Shouldly syntax: - true.Should().BeTrue() → true.ShouldBeTrue() - result.Should().Be(expected) → result.ShouldBe(expected) - All tests pass (4 passed, 1 skipped) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Directory.Packages.props | 1 + .../convention-tests.cs | 10 +++++----- .../timewarp-options-validation-tests.csproj | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b5e59c8..0731f86 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,6 +9,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/timewarp-options-validation-tests/convention-tests.cs b/tests/timewarp-options-validation-tests/convention-tests.cs index 6b22c7e..05441f5 100644 --- a/tests/timewarp-options-validation-tests/convention-tests.cs +++ b/tests/timewarp-options-validation-tests/convention-tests.cs @@ -1,24 +1,24 @@ namespace ConventionTest_; -using FluentAssertions; +using Shouldly; using TimeWarp.Fixie; [TestTag(TestTags.Fast)] public class SimpleNoApplicationTest_Should_ { - public static void AlwaysPass() => true.Should().BeTrue(); + public static void AlwaysPass() => true.ShouldBeTrue(); [Skip("Demonstrates skip attribute")] - public static void SkipExample() => true.Should().BeFalse(); + public static void SkipExample() => true.ShouldBeFalse(); [TestTag(TestTags.Fast)] - public static void TagExample() => true.Should().BeTrue(); + public static void TagExample() => true.ShouldBeTrue(); [Input(5, 3, 2)] [Input(8, 5, 3)] public static void Subtract(int aX, int aY, int aExpectedDifference) { int result = aX - aY; - result.Should().Be(aExpectedDifference); + result.ShouldBe(aExpectedDifference); } } diff --git a/tests/timewarp-options-validation-tests/timewarp-options-validation-tests.csproj b/tests/timewarp-options-validation-tests/timewarp-options-validation-tests.csproj index adc150c..c283594 100644 --- a/tests/timewarp-options-validation-tests/timewarp-options-validation-tests.csproj +++ b/tests/timewarp-options-validation-tests/timewarp-options-validation-tests.csproj @@ -6,7 +6,7 @@ - + From 962a36ae01b06b51d17d457e04ac3096914b10fc Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sat, 25 Oct 2025 21:21:29 +0700 Subject: [PATCH 37/37] Update CI/CD workflow to use kebab-case directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change Source/** to source/** in path filters - Change Tests/** to tests/** in path filters - Update test path from Tests/TimeWarp.OptionsValidation.Tests to tests/timewarp-options-validation-tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/ci-cd.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b69d8da..f62ef4f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -5,8 +5,8 @@ on: branches: - master paths: - - 'Source/**' - - 'Tests/**' + - 'source/**' + - 'tests/**' - '.github/workflows/**' - 'Directory.Build.props' - 'Directory.Packages.props' @@ -14,8 +14,8 @@ on: branches: - master paths: - - 'Source/**' - - 'Tests/**' + - 'source/**' + - 'tests/**' - '.github/workflows/**' - 'Directory.Build.props' - 'Directory.Packages.props' @@ -50,7 +50,7 @@ jobs: - name: Test run: | - cd Tests/TimeWarp.OptionsValidation.Tests + cd tests/timewarp-options-validation-tests dotnet tool restore dotnet restore dotnet fixie --configuration Release