diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 9fd703d..7dafabe 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,14 +3,14 @@ "isRoot": true, "tools": { "fixie.console": { - "version": "3.4.0", + "version": "4.1.0", "commands": [ "fixie" ], "rollForward": false }, "dotnet-outdated-tool": { - "version": "4.6.1", + "version": "4.6.8", "commands": [ "dotnet-outdated" ], diff --git a/.editorconfig b/.editorconfig index 573ba3c..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 @@ -255,6 +256,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 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..f62ef4f --- /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-options-validation-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 }}." 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 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/Directory.Build.props b/Directory.Build.props index bd23c15..2695885 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,41 +1,82 @@ - - - - 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 - 1.0.0-beta.2+8.0.205 - Unlicense - Logo.png - README.md + + + + timewarp-options-validation + $(RepositoryRoot)timewarp-options-validation.slnx + $(MSBuildThisFileDirectory) + $(RepositoryRoot)source/ + $(RepositoryRoot)tests/ + $(RepositoryRoot)Documentation/ + $(RepositoryRoot)artifacts/ + $(ArtifactsDirectory)packages/ - - - true - true - true - true - enable - preview + + + + $(PackagesDirectory) + + true + + + true + + + + + net10.0 + enable enable - net8.0 + 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 - - - - - - - + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index d8c418f..0731f86 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,20 +1,43 @@ - + + + true + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + - - - + + - - - - - - - - - + + + + + + + + + + + + + \ 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/NuGet.config b/NuGet.config index 1abb5cd..80f5bd7 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,14 +4,4 @@ - - - - - - - - - - diff --git a/README.md b/README.md deleted file mode 100644 index 7897c7e..0000000 --- a/README.md +++ /dev/null @@ -1,57 +0,0 @@ -[![Dotnet](https://img.shields.io/badge/dotnet-6.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) -[![nuget](https://img.shields.io/nuget/v/TimeWarp.OptionsValidation?logo=nuget)](https://www.nuget.org/packages/TimeWarp.OptionsValidation/) -[![nuget](https://img.shields.io/nuget/dt/TimeWarp.OptionsValidation?logo=nuget)](https://www.nuget.org/packages/TimeWarp.OptionsValidation/) -[![Issues Open](https://img.shields.io/github/issues/TimeWarpEngineering/timewarp-options-validation.svg?logo=github)](https://github.com/TimeWarpEngineering/timewarp-options-validation/issues) -[![Forks](https://img.shields.io/github/forks/TimeWarpEngineering/timewarp-options-validation)](https://github.com/TimeWarpEngineering/timewarp-options-validation) -[![License](https://img.shields.io/github/license/TimeWarpEngineering/timewarp-options-validation.svg?style=flat-square&logo=github)](https://github.com/TimeWarpEngineering/timewarp-options-validation/issues) -[![Twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2FTimeWarpEngineering%2Ftimewarp-options-validation)](https://twitter.com/intent/tweet?url=https://github.com/TimeWarpEngineering/timewarp-options-validation) - -[![Twitter](https://img.shields.io/twitter/follow/StevenTCramer.svg)](https://twitter.com/intent/follow?screen_name=StevenTCramer) -[![Twitter](https://img.shields.io/twitter/follow/TheFreezeTeam1.svg)](https://twitter.com/intent/follow?screen_name=TheFreezeTeam1) - -# TimeWarp.OptionsValidation - -![TimeWarp Logo](Assets/Logo.png) - -TimeWarp.OptionsValidation uses fluent validation to check your configuration settings. - -## Give a Star! :star: - -If you like or are using this project please give it a star. Thank you! - -## Getting started - -To quickly get started I recommend reviewing the samples in this repo. - -## Installation - -```console -dotnet add package TimeWarp.OptionsValidation -``` - -You can see the latest NuGet packages from the official [TimeWarp NuGet page](https://www.nuget.org/profiles/TimeWarp.Enterprises). - -* [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/) - -## Releases - -See the [Release Notes](./documentation/releases.md) -## Unlicense - -[![License](https://img.shields.io/github/license/TimeWarpEngineering/timewarp-options-validation.svg?style=flat-square&logo=github)](https://unlicense.org) - -## Contributing - -Time is of the essence. Before developing a Pull Request I recommend opening a [discussion](https://github.com/TimeWarpEngineering/timewarp-options-validation/discussions). - -Please feel free to make suggestions and help out with the [documentation](https://timewarpengineering.github.io/timewarp-options-validation/). -Please refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. - -## Contact - -Sometimes the github notifications get lost in the shuffle. If you file an [issue](https://github.com/TimeWarpEngineering/timewarp-options-validation/issues) and don't get a response in a timely manner feel free to ping on our [Discord server](https://discord.gg/A55JARGKKP). - -[![Discord](https://img.shields.io/discord/715274085940199487?logo=discord)](https://discord.gg/7F4bS2T) diff --git a/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs b/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs deleted file mode 100644 index 8b7f5a8..0000000 --- a/Source/TimeWarp.OptionsValidation/Configuration/SectionNameAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -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 string SectionName { get; set; } - public SectionNameAttribute(string aSectionName) - { - this.SectionName = aSectionName; - } -} diff --git a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs b/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index acd2aa3..0000000 --- a/Source/TimeWarp.OptionsValidation/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace Microsoft.Extensions.DependencyInjection; - -public static class ServiceCollectionExtensions -{ - 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); - } - - 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/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/Tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj b/Tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj deleted file mode 100644 index 39d9258..0000000 --- a/Tests/TimeWarp.OptionsValidation.Tests/TimeWarp.OptionsValidation.Tests.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/Assets/Logo.png b/assets/logo.png similarity index 100% rename from Assets/Logo.png rename to assets/logo.png 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 diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..f2a0c2a --- /dev/null +++ b/claude.md @@ -0,0 +1,162 @@ +# 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 `AddFluentValidatedOptions()` extension methods +- Two overloads: one accepts `IConfiguration`, the other accepts `Action` +- Returns `OptionsBuilder`, enabling chaining with `.ValidateOnStart()` +- 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) + +**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) + +**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 + +**With Startup Validation (Recommended)** +```csharp +services.AddFluentValidatedOptions(configuration) + .ValidateOnStart(); // Validates at startup, fails fast +``` +- Returns `OptionsBuilder` for method chaining +- Validates automatically at startup with `.ValidateOnStart()` +- Integrates with host lifecycle (validates before app runs) +- Fails fast on invalid configuration at startup +- Recommended for production applications + +**Without Startup Validation** +```csharp +services.AddFluentValidatedOptions(configuration); +// Omit .ValidateOnStart() for lazy validation +``` +- Validates on first access (lazy validation) +- Useful for development or when startup validation isn't needed + +### Usage Pattern + +1. Define an options class (e.g., `MyOptions`) +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 + +``` +/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**: 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 +- 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. diff --git a/LICENSE b/license similarity index 100% rename from LICENSE rename to license diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..8d952f3 --- /dev/null +++ b/readme.md @@ -0,0 +1,307 @@ +[![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) +[![nuget](https://img.shields.io/nuget/v/TimeWarp.OptionsValidation?logo=nuget)](https://www.nuget.org/packages/TimeWarp.OptionsValidation/) +[![nuget](https://img.shields.io/nuget/dt/TimeWarp.OptionsValidation?logo=nuget)](https://www.nuget.org/packages/TimeWarp.OptionsValidation/) +[![Issues Open](https://img.shields.io/github/issues/TimeWarpEngineering/timewarp-options-validation.svg?logo=github)](https://github.com/TimeWarpEngineering/timewarp-options-validation/issues) +[![Forks](https://img.shields.io/github/forks/TimeWarpEngineering/timewarp-options-validation)](https://github.com/TimeWarpEngineering/timewarp-options-validation) +[![License](https://img.shields.io/github/license/TimeWarpEngineering/timewarp-options-validation.svg?style=flat-square&logo=github)](https://github.com/TimeWarpEngineering/timewarp-options-validation/issues) +[![Twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2FTimeWarpEngineering%2Ftimewarp-options-validation)](https://twitter.com/intent/tweet?url=https://github.com/TimeWarpEngineering/timewarp-options-validation) + +[![Twitter](https://img.shields.io/twitter/follow/StevenTCramer.svg)](https://twitter.com/intent/follow?screen_name=StevenTCramer) +[![Twitter](https://img.shields.io/twitter/follow/TheFreezeTeam1.svg)](https://twitter.com/intent/follow?screen_name=TheFreezeTeam1) + +# TimeWarp.OptionsValidation + +![TimeWarp Logo](assets/logo.png) + +TimeWarp.OptionsValidation integrates FluentValidation with Microsoft.Extensions.Options to provide automatic validation of your configuration settings at application startup. + +## Why Use This Library? + +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). + +**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 + +## Give a Star! :star: + +If you like or are using this project please give it a star. Thank you! + +## Installation + +```console +dotnet add package TimeWarp.OptionsValidation +``` + +You can see the latest NuGet packages from the official [TimeWarp NuGet page](https://www.nuget.org/profiles/TimeWarp.Enterprises). + +* [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 Setup with Automatic Startup Validation + +Use `AddFluentValidatedOptions()` which returns `OptionsBuilder`, allowing you to chain with `.ValidateOnStart()` for automatic startup validation. + +#### 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; } + + // Nested validator - sealed and only used here + public sealed class Validator : AbstractValidator + { + 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"); + } + } +} +``` + +#### 2. Register with Automatic Startup Validation + +```csharp +using Microsoft.Extensions.DependencyInjection; + +var builder = WebApplication.CreateBuilder(args); + +// Register options with automatic startup validation +builder.Services + .AddFluentValidatedOptions(builder.Configuration) + .ValidateOnStart(); // ✅ Validates when host starts, throws on error + +var app = builder.Build(); +app.Run(); // Validation happens automatically before this runs +``` + +**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 + +The library automatically discovers which configuration section to bind based on simple, predictable rules. + +#### 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 +{ + "DatabaseOptions": { + "ConnectionString": "Server=localhost;Database=myapp;", + "MaxRetries": 3, + "CommandTimeout": 30 + } +} +``` + +```csharp +// Automatically binds to "DatabaseOptions" section +services + .AddFluentValidatedOptions(configuration) + .ValidateOnStart(); +``` + +#### Custom Configuration Key with `[ConfigurationKey]` Attribute + +Override the default by decorating your options class with `[ConfigurationKey]`: + +**Simple Configuration Key:** +```csharp +using TimeWarp.OptionsValidation; + +[ConfigurationKey("Database")] +public class DatabaseOptions +{ + public string ConnectionString { get; set; } = string.Empty; + // ... +} +``` + +Binds to `"Database"` section: + +```json +{ + "Database": { + "ConnectionString": "Server=localhost;Database=myapp;", + "MaxRetries": 3, + "CommandTimeout": 30 + } +} +``` + +**Hierarchical Key with Colon Separator:** +```csharp +[ConfigurationKey("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 configuration key specified in attribute +services + .AddFluentValidatedOptions(configuration) + .ValidateOnStart(); +``` + +#### Advanced: Manual Section Binding + +For dynamic section paths or complex scenarios not covered by the attribute: + +```csharp +// Manual binding for runtime-determined paths +string environment = builder.Environment.EnvironmentName; +services.AddOptions() + .Bind(configuration.GetSection($"{environment}:Database")) + .ValidateFluentValidation() + .ValidateOnStart(); +``` + +**Automatic Configuration Key Resolution Summary:** +- ✅ Uses class name: `DatabaseOptions` → `"DatabaseOptions"` +- ✅ 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 + +### Programmatic Configuration + +You can also configure options programmatically without IConfiguration: + +```csharp +services + .AddFluentValidatedOptions(options => + { + options.ConnectionString = "Server=localhost;Database=myapp;"; + options.MaxRetries = 3; + options.CommandTimeout = 30; + }) + .ValidateOnStart(); +``` + +### Complete Startup Example + +```csharp +using Microsoft.Extensions.DependencyInjection; + +var builder = WebApplication.CreateBuilder(args); + +// 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. + +### Without Startup Validation + +If you don't need automatic startup validation, simply omit `.ValidateOnStart()`: + +```csharp +// Validates on first access instead of at startup +services.AddFluentValidatedOptions(configuration); +// No .ValidateOnStart() call - validation happens lazily +``` + +This approach validates options when they're first accessed rather than at application startup. + +## Features + +- **Automatic Startup Validation**: Use `.ValidateOnStart()` to fail fast on invalid configuration +- **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 +- **Type Safety**: Strongly-typed options with compile-time checking +- **Flexible API**: Choose between fluent API (with `.ValidateOnStart()`) or simple registration + +## Releases + +See the [Release Notes](./documentation/releases.md) +## Unlicense + +[![License](https://img.shields.io/github/license/TimeWarpEngineering/timewarp-options-validation.svg?style=flat-square&logo=github)](https://unlicense.org) + +## Contributing + +Time is of the essence. Before developing a Pull Request I recommend opening a [discussion](https://github.com/TimeWarpEngineering/timewarp-options-validation/discussions). + +Please feel free to make suggestions and help out with the [documentation](https://timewarpengineering.github.io/timewarp-options-validation/). +Please refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. + +## Contact + +Sometimes the github notifications get lost in the shuffle. If you file an [issue](https://github.com/TimeWarpEngineering/timewarp-options-validation/issues) and don't get a response in a timely manner feel free to ping on our [Discord server](https://discord.gg/A55JARGKKP). + +[![Discord](https://img.shields.io/discord/715274085940199487?logo=discord)](https://discord.gg/7F4bS2T) diff --git a/source/Directory.Build.props b/source/Directory.Build.props new file mode 100644 index 0000000..eb92710 --- /dev/null +++ b/source/Directory.Build.props @@ -0,0 +1,39 @@ + + + + + + + 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-options-validation/configuration/configuration-key-attribute.cs b/source/timewarp-options-validation/configuration/configuration-key-attribute.cs new file mode 100644 index 0000000..5bcda4f --- /dev/null +++ b/source/timewarp-options-validation/configuration/configuration-key-attribute.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/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-options-validation/extensions/options-builder-extensions.cs b/source/timewarp-options-validation/extensions/options-builder-extensions.cs new file mode 100644 index 0000000..1fa6f02 --- /dev/null +++ b/source/timewarp-options-validation/extensions/options-builder-extensions.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, DatabaseOptions.Validator>() + /// .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-options-validation/extensions/service-collection-extensions.cs b/source/timewarp-options-validation/extensions/service-collection-extensions.cs new file mode 100644 index 0000000..54321e0 --- /dev/null +++ b/source/timewarp-options-validation/extensions/service-collection-extensions.cs @@ -0,0 +1,86 @@ +namespace Microsoft.Extensions.DependencyInjection; + +using Microsoft.Extensions.Options; + +public static class ServiceCollectionExtensions +{ + /// + /// 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()) + /// + /// Configuration key resolution: + /// - Default: Uses class name (DatabaseOptions → "DatabaseOptions") + /// - Custom: Uses [ConfigurationKey("Database")] → "Database" + /// - Hierarchical: Uses [ConfigurationKey("MyApp:Settings:Database")] → nested path + /// + /// + /// + /// // Default: Binds from "DatabaseOptions" section + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) + /// .ValidateOnStart(); + /// + /// // Custom: [ConfigurationKey("Database")] binds from "Database" section + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) + /// .ValidateOnStart(); + /// + /// // Hierarchical: [ConfigurationKey("MyApp:Settings:Database")] binds from nested path + /// services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration) + /// .ValidateOnStart(); + /// + /// + public static OptionsBuilder AddFluentValidatedOptions( + this IServiceCollection services, + IConfiguration configuration) + where TOptions : class + where TValidator : AbstractValidator + { + // Auto-discover configuration key using ConfigurationKeyAttribute or class name + Type type = typeof(TOptions); + var configurationKeyAttribute = (ConfigurationKeyAttribute?)type + .GetCustomAttributes(typeof(ConfigurationKeyAttribute), false) + .FirstOrDefault(); + string key = configurationKeyAttribute?.Key ?? type.Name; + + return services.AddOptions() + .Bind(configuration.GetSection(key)) + .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, DatabaseOptions.Validator>(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(); + } + +} + 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 55% rename from Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj rename to source/timewarp-options-validation/timewarp-options-validation.csproj index 09bda60..e1cddd4 100644 --- a/Source/TimeWarp.OptionsValidation/TimeWarp.OptionsValidation.csproj +++ b/source/timewarp-options-validation/timewarp-options-validation.csproj @@ -1,18 +1,10 @@ - + - true + TimeWarp.OptionsValidation TimeWarp.OptionsValidation - ./bin/Packages - true - - - - - - diff --git a/Tests/TimeWarp.OptionsValidation.Tests/ConventionTests.cs b/tests/timewarp-options-validation-tests/convention-tests.cs similarity index 57% rename from Tests/TimeWarp.OptionsValidation.Tests/ConventionTests.cs rename to tests/timewarp-options-validation-tests/convention-tests.cs index 6b22c7e..05441f5 100644 --- a/Tests/TimeWarp.OptionsValidation.Tests/ConventionTests.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.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-options-validation-tests/timewarp-options-validation-tests.csproj b/tests/timewarp-options-validation-tests/timewarp-options-validation-tests.csproj new file mode 100644 index 0000000..c283594 --- /dev/null +++ b/tests/timewarp-options-validation-tests/timewarp-options-validation-tests.csproj @@ -0,0 +1,17 @@ + + + + TimeWarp.OptionsValidation.Tests + + + + + + + + + + + + + \ No newline at end of file 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..38a5a96 --- /dev/null +++ b/timewarp-options-validation.slnx @@ -0,0 +1,8 @@ + + + + + + + +