diff --git a/.editorconfig b/.editorconfig index 536dae9..d324a52 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,27 +1,173 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# Create portable, custom editor settings with EditorConfig +# https://docs.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options + +# .NET coding convention settings for EditorConfig +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference?view=vs-2019 + +# Language conventions +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019 + +# Formatting conventions +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019 + +# .NET naming conventions for EditorConfig +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 + +# Top-most EditorConfig file root = true +# Editor default newlines with a newline ending every file [*] -trim_trailing_whitespace = true insert_final_newline = true +charset = utf-8 indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.json] +insert_final_newline = false + +[*.cs] indent_size = 4 -charset = utf-8 -end_of_line = lf -[*.{csproj,json,config,yml,props}] -indent_size = 2 +# Do not insert newline for ApiApprovalTests +[*.txt] +insert_final_newline = false -[*.sh] -end_of_line = lf +# Code files +[*.{cs,vb}] -[*.{cmd, bat}] -end_of_line = crlf +# .NET code style settings - "This." and "Me." qualifiers +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#this-and-me +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning -# C# formatting settings - Namespace options -csharp_style_namespace_declarations = file_scoped:suggestion +# .NET code style settings - Language keywords instead of framework type names for type references +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#language-keywords +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error + +# .NET code style settings - Modifier preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#normalize-modifiers +dotnet_style_require_accessibility_modifiers = always:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning +dotnet_style_readonly_field = true:warning + +# .NET code style settings - Parentheses preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#parentheses-preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:suggestion +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion + +# .NET code style settings - Expression-level preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#expression-level-preferences +dotnet_style_object_initializer = true:error +dotnet_style_collection_initializer = true:error +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_compound_assignment = true:warning + +# .NET code style settings - Null-checking preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#null-checking-preferences +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:error + +# .NET code quality settings - Parameter preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#parameter-preferences +dotnet_code_quality_unused_parameters = all:warning + +# C# code style settings - Implicit and explicit types +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#implicit-and-explicit-types +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:suggestion + +# C# code style settings - Expression-bodied members +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#expression-bodied-members +csharp_style_expression_bodied_methods = when_on_single_line:suggestion +csharp_style_expression_bodied_constructors = false:warning +csharp_style_expression_bodied_operators = when_on_single_line:warning +csharp_style_expression_bodied_properties = when_on_single_line:warning +csharp_style_expression_bodied_indexers = when_on_single_line:warning +csharp_style_expression_bodied_accessors = when_on_single_line:warning +csharp_style_expression_bodied_lambdas = when_on_single_line:warning +csharp_style_expression_bodied_local_functions = when_on_single_line:warning + +# C# code style settings - Pattern matching +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#pattern-matching +csharp_style_pattern_matching_over_is_with_cast_check = true:error +csharp_style_pattern_matching_over_as_with_null_check = true:error + +# C# code style settings - Inlined variable declaration +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#inlined-variable-declarations +csharp_style_inlined_variable_declaration = true:error + +# C# code style settings - C# expression-level preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#c-expression-level-preferences +csharp_prefer_simple_default_expression = true:suggestion + +# C# code style settings - C# null-checking preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#c-null-checking-preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# C# code style settings - Code block preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#code-block-preferences +csharp_prefer_braces = when_multiline:suggestion + +# C# code style - Unused value preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#unused-value-preferences +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion + +# C# code style - Index and range preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#index-and-range-preferences +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:suggestion + +# C# code style - Miscellaneous preferences +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#miscellaneous-preferences +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_using_directive_placement = outside_namespace:warning +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:suggestion csharp_style_prefer_switch_expression = true:suggestion +# .NET formatting settings - Organize using directives +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019#organize-using-directives +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# C# formatting settings - New-line options +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019#new-line-options +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# C# formatting settings - Indentation options +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019#indentation-options +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents_when_block = false + # C# formatting settings - Spacing options csharp_space_after_cast = false csharp_space_after_keywords_in_control_flow_statements = true @@ -45,3 +191,75 @@ csharp_space_around_declaration_statements = false csharp_space_before_open_square_brackets = false csharp_space_between_empty_square_brackets = false csharp_space_between_square_brackets = false + +# C# formatting settings - Wrap options +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019#wrap-options +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +# C# formatting settings - Namespace options +csharp_style_namespace_declarations = file_scoped:warning + +########## name all private fields using camelCase with underscore prefix ########## +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 +# dotnet_naming_rule..symbols = +dotnet_naming_rule.private_fields_with_underscore.symbols = private_fields + +# dotnet_naming_symbols.. = +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private + +# dotnet_naming_rule..style = +dotnet_naming_rule.private_fields_with_underscore.style = prefix_underscore + +# dotnet_naming_style.. = +dotnet_naming_style.prefix_underscore.capitalization = camel_case +dotnet_naming_style.prefix_underscore.required_prefix = _ + +# dotnet_naming_rule..severity = +dotnet_naming_rule.private_fields_with_underscore.severity = warning + +########## name all constant fields using UPPER_CASE ########## +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 +# dotnet_naming_rule..symbols = +dotnet_naming_rule.constant_fields_should_be_upper_case.symbols = constant_fields + +# dotnet_naming_symbols.. = +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +# dotnet_naming_rule..style = +dotnet_naming_rule.constant_fields_should_be_upper_case.style = upper_case_style + +# dotnet_naming_style.. = +dotnet_naming_style.upper_case_style.capitalization = all_upper +dotnet_naming_style.upper_case_style.word_separator = _ + +# dotnet_naming_rule..severity = +dotnet_naming_rule.constant_fields_should_be_upper_case.severity = warning + +########## Async methods should have "Async" suffix ########## +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 +# dotnet_naming_rule..symbols = +dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods + +# dotnet_naming_symbols.. = +dotnet_naming_symbols.any_async_methods.applicable_kinds = method +dotnet_naming_symbols.any_async_methods.applicable_accessibilities = * +dotnet_naming_symbols.any_async_methods.required_modifiers = async + +# dotnet_naming_rule..style = +dotnet_naming_rule.async_methods_end_in_async.style = end_in_async_style + +# dotnet_naming_style.. = +dotnet_naming_style.end_in_async_style.capitalization = pascal_case +dotnet_naming_style.end_in_async_style.word_separator = +dotnet_naming_style.end_in_async_style.required_prefix = +dotnet_naming_style.end_in_async_style.required_suffix = Async + +# dotnet_naming_rule..severity = +dotnet_naming_rule.async_methods_end_in_async.severity = warning + +# Remove unnecessary import https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0005 +dotnet_diagnostic.IDE0005.severity = warning diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..33725c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,17 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: sungam3r + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots and any additional context** +If applicable, add screenshots or any other context here to help explain your problem. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..7cbbb73 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,14 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: sungam3r + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. + +**Describe the solution you'd like** +This is optional description of what you want to happen. diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..877c249 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,3 @@ +# https://docs.codecov.com/docs/codecov-yaml +comment: + behavior: new diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..67d247d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + +- package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "daily" + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..129462d --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,21 @@ +tests: +- changed-files: + - any-glob-to-any-file: src/Destructurama.Attributed.Tests/**/* + +CI: +- changed-files: + - any-glob-to-any-file: + - .github/workflows/**/* + - .github/dependabot.yml + - .github/codecov.yml + - .github/labeler.yml + +code style: +- changed-files: + - any-glob-to-any-file: + - .editorconfig + +documentation: +- changed-files: + - any-glob-to-any-file: + - README.md diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 0000000..b10929a --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,45 @@ +# https://github.com/benchmark-action/github-action-benchmark +name: Continuous benchmarking +on: + pull_request: + branches: + - master + push: + branches: + - master + +permissions: + contents: write + deployments: write + +jobs: + benchmark: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Run benchmarks + working-directory: src/Benchmarks + run: dotnet run -c Release --exporters json --filter '*' + + - name: Combine benchmarks results + working-directory: src/Benchmarks + run: dotnet tool install -g dotnet-script && dotnet script combine-bechmarks.csx + + - name: Store benchmarks results + uses: benchmark-action/github-action-benchmark@v1 + with: + name: Benchmarks + tool: 'benchmarkdotnet' + output-file-path: src/Benchmarks/BenchmarkDotNet.Artifacts/results/Combined.Benchmarks.json + github-token: ${{ secrets.GITHUB_TOKEN }} + alert-threshold: '101%' + comment-on-alert: true + - name: Push benchmarks results + if: github.event_name != 'pull_request' + run: git push 'https://${{ github.repository_owner }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git' gh-pages:gh-pages diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..880ece5 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,39 @@ +# https://github.com/github/codeql +# https://github.com/github/codeql-action +name: CodeQL analysis + +on: + push: + branches: [master, dev] + pull_request: + branches: [master, dev] + +jobs: + analyze: + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + queries: security-and-quality + languages: csharp + + - name: Install dependencies + working-directory: src + run: dotnet restore + + - name: Build solution + working-directory: src + run: dotnet build --no-restore + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml new file mode 100644 index 0000000..f34d7e1 --- /dev/null +++ b/.github/workflows/label.yml @@ -0,0 +1,21 @@ +# This workflow will triage pull requests and apply a label based on the +# paths that are modified in the pull request. +# +# To use this workflow, you will need to set up a .github/labeler.yml +# file with configuration. For more information, see: +# https://github.com/actions/labeler/blob/master/README.md + +name: Labeler +on: + pull_request_target: + types: + - opened # when PR is opened + +jobs: + label: + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + continue-on-error: true diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml new file mode 100644 index 0000000..ceb498a --- /dev/null +++ b/.github/workflows/publish-preview.yml @@ -0,0 +1,49 @@ +name: Publish preview to GitHub registry + +# ==== NOTE: do not rename this yml file or the $GITHUB_RUN_NUMBER will be reset ==== + +on: + push: + branches: + - master + - dev + paths: + - src/** + - .github/workflows/** + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + pack: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + source-url: https://nuget.pkg.github.com/destructurama/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Install dependencies + working-directory: src + run: dotnet restore + - name: Build solution [Release] + working-directory: src + run: dotnet build --no-restore -c Release + - name: Pack solution [Release] + working-directory: src + run: dotnet pack --no-restore -c Release -o out + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: Nuget packages + path: | + src/out/* + - name: Publish Nuget packages to GitHub registry + working-directory: src + run: dotnet nuget push "out/*" -k ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..b75f83d --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,63 @@ +name: Publish release to Nuget registry + +on: + release: + types: + - published + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + source-url: https://api.nuget.org/v3/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.NUGET_AUTH_TOKEN}} + - name: Install dependencies + working-directory: src + run: dotnet restore + - name: Build solution [Release] + working-directory: src + run: dotnet build --no-restore -c Release + - name: Pack solution [Release] + working-directory: src + run: dotnet pack --no-restore --no-build -c Release -o out + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: Nuget packages + path: | + src/out/* + - name: Publish Nuget packages to Nuget registry + working-directory: src + run: dotnet nuget push "out/*" -k ${{secrets.NUGET_AUTH_TOKEN}} + - name: Upload Nuget packages as release artifacts + uses: actions/github-script@v7 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + console.log('environment', process.versions); + const fs = require('fs').promises; + const { repo: { owner, repo }, sha } = context; + + for (let file of await fs.readdir('src/out')) { + console.log('uploading', file); + + await github.rest.repos.uploadReleaseAsset({ + owner, + repo, + release_id: ${{ github.event.release.id }}, + name: file, + data: await fs.readFile(`src/out/${file}`) + }); + } diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..bd21f4d --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,22 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue was marked as stale since it has not been active for a long time' + stale-pr-message: 'This pull request was marked as stale since it has not been active for a long time' + stale-issue-label: 'stale' + stale-pr-label: 'stale' + days-before-stale: 30 + days-before-close: 60 + exempt-issue-label: 'not so stale' + exempt-pr-label: 'not so stale' + operations-per-run: 30 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..51b9ad6 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,79 @@ +name: Run unit tests + +on: + pull_request: + branches: + - master + - dev + paths: + - src/** + - .github/workflows/** + # Upload code coverage results when PRs are merged + push: + branches: + - master + - dev + paths: + - src/** + - .github/workflows/** + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET SDKs + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + source-url: https://nuget.pkg.github.com/destructurama/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Install dependencies + working-directory: src + run: dotnet restore + - name: Check formatting + if: ${{ startsWith(matrix.os, 'ubuntu') }} + working-directory: src + run: | + dotnet format --no-restore --verify-no-changes --severity warn || (echo "Run 'dotnet format' to fix issues" && exit 1) + - name: Build solution [Release] + working-directory: src + run: dotnet build --no-restore -c Release + - name: Build solution [Debug] + working-directory: src + run: dotnet build --no-restore -c Debug + - name: Test solution [Debug] + working-directory: src + run: dotnet test --no-restore -p:CollectCoverage=true + - name: Upload coverage to codecov + if: ${{ startsWith(matrix.os, 'ubuntu') }} + uses: codecov/codecov-action@v3 + with: + files: .coverage/Destructurama.ByIgnoring.Tests/coverage.net8.opencover.xml + + buildcheck: + needs: + - test + runs-on: ubuntu-latest + if: always() + steps: + - name: Pass build check + if: ${{ needs.test.result == 'success' }} + run: exit 0 + - name: Fail build check + if: ${{ needs.test.result != 'success' }} + run: exit 1 diff --git a/.gitignore b/.gitignore index 0a1348b..e44dd1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,198 +1,3 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -build/ -bld/ -[Bb]in/ -[Oo]bj/ - -# Visual Studo 2015 cache/options directory .vs/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding addin-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config - -# Windows Azure Build Output -csx/ -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Others -*.[Cc]ache -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings -node_modules/ -bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -.idea/ +obj/ +bin/ diff --git a/Build.ps1 b/Build.ps1 deleted file mode 100644 index 9b92612..0000000 --- a/Build.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -echo "build: Build started" - -Push-Location $PSScriptRoot - -if(Test-Path .\artifacts) { - echo "build: Cleaning .\artifacts" - Remove-Item .\artifacts -Force -Recurse -} - -& dotnet restore --no-cache - -$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; -$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; -$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"] -$commitHash = $(git rev-parse --short HEAD) -$buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] - -echo "build: Package version suffix is $suffix" -echo "build: Build version suffix is $buildSuffix" - -foreach ($src in ls src/*) { - Push-Location $src - - echo "build: Packaging project in $src" - - if ($buildSuffix) { - & dotnet build -c Release --version-suffix=$buildSuffix - } else { - & dotnet build -c Release - } - if($LASTEXITCODE -ne 0) { exit 1 } - - if ($suffix) { - & dotnet pack -c Release --include-symbols -o ..\..\artifacts --version-suffix=$suffix --no-build - } else { - & dotnet pack -c Release --include-symbols -o ..\..\artifacts --no-build - } - if($LASTEXITCODE -ne 0) { exit 1 } - - Pop-Location -} - -foreach ($test in ls test/*.Tests) { - Push-Location $test - - echo "build: Testing project in $test" - - & dotnet test -c Release - if($LASTEXITCODE -ne 0) { exit 3 } - - Pop-Location -} - -Pop-Location diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index 5d24e03..0000000 --- a/Directory.Build.props +++ /dev/null @@ -1,13 +0,0 @@ - - - - latest - true - embedded - true - $(MSBuildThisFileDirectory)key.snk - false - true - enable - - \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 8fc6156..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: '{build}' -skip_tags: true -image: Visual Studio 2022 -configuration: Release -test: off -build_script: -- ps: ./Build.ps1 -artifacts: -- path: artifacts/Destructurama.*.nupkg -deploy: -- provider: NuGet - api_key: - secure: Oa5/xyLBrdomUyVn2gbtmAxMHjoR/xlKpLLHWks/uV5YZjEkDEBx73JQtdbsUiL9 - skip_symbols: true - on: - branch: /^(master|dev)$/ diff --git a/key.snk b/assets/Destructurama.snk similarity index 100% rename from key.snk rename to assets/Destructurama.snk diff --git a/destructurama-attributed.sln b/destructurama-attributed.sln deleted file mode 100644 index b59937d..0000000 --- a/destructurama-attributed.sln +++ /dev/null @@ -1,52 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31507.150 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Destructurama.Attributed", "src\Destructurama.Attributed\Destructurama.Attributed.csproj", "{A79F906E-0298-49DC-93EC-CE4F1F5D13E2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Destructurama.Attributed.Tests", "test\Destructurama.Attributed.Tests\Destructurama.Attributed.Tests.csproj", "{182ECDA3-A97D-4BB6-BC6F-60A137478B92}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{A195B0EF-8A8C-4B5C-8A9D-743B95C8356A}" - ProjectSection(SolutionItems) = preProject - .gitattributes = .gitattributes - .gitignore = .gitignore - appveyor.yml = appveyor.yml - Build.ps1 = Build.ps1 - CHANGES.md = CHANGES.md - Directory.Build.props = Directory.Build.props - LICENSE = LICENSE - README.md = README.md - global.json = global.json - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{677924A1-EB38-4E04-9D13-3DD4C3C9C514}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F56115EE-0998-40B3-9FA7-734D83DCF884}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A79F906E-0298-49DC-93EC-CE4F1F5D13E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A79F906E-0298-49DC-93EC-CE4F1F5D13E2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A79F906E-0298-49DC-93EC-CE4F1F5D13E2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A79F906E-0298-49DC-93EC-CE4F1F5D13E2}.Release|Any CPU.Build.0 = Release|Any CPU - {182ECDA3-A97D-4BB6-BC6F-60A137478B92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {182ECDA3-A97D-4BB6-BC6F-60A137478B92}.Debug|Any CPU.Build.0 = Debug|Any CPU - {182ECDA3-A97D-4BB6-BC6F-60A137478B92}.Release|Any CPU.ActiveCfg = Release|Any CPU - {182ECDA3-A97D-4BB6-BC6F-60A137478B92}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {A79F906E-0298-49DC-93EC-CE4F1F5D13E2} = {677924A1-EB38-4E04-9D13-3DD4C3C9C514} - {182ECDA3-A97D-4BB6-BC6F-60A137478B92} = {F56115EE-0998-40B3-9FA7-734D83DCF884} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F09D8B7B-37C6-417E-B8DA-296735211A4E} - EndGlobalSection -EndGlobal diff --git a/destructurama-attributed.sln.DotSettings b/destructurama-attributed.sln.DotSettings deleted file mode 100644 index d98a03d..0000000 --- a/destructurama-attributed.sln.DotSettings +++ /dev/null @@ -1,223 +0,0 @@ - - True - True - True - True - False - SOLUTION - DO_NOT_SHOW - DO_NOT_SHOW - DO_NOT_SHOW - ERROR - DO_NOT_SHOW - DO_NOT_SHOW - DO_NOT_SHOW - DO_NOT_SHOW - ERROR - ERROR - WARNING - ERROR - HINT - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - DO_NOT_SHOW - DO_NOT_SHOW - DO_NOT_SHOW - DO_NOT_SHOW - HINT - DO_NOT_SHOW - HINT - ERROR - WARNING - ERROR - ERROR - ERROR - HINT - ERROR - ERROR - ERROR - ERROR - ERROR - WARNING - HINT - ERROR - SUGGESTION - ERROR - ERROR - ERROR - ERROR - SUGGESTION - DO_NOT_SHOW - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - SUGGESTION - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - WARNING - ERROR - ERROR - ERROR - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - DoHide - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - ERROR - DO_NOT_SHOW - SUGGESTION - ERROR - HINT - ERROR - ERROR - ERROR - ERROR - HINT - <?xml version="1.0" encoding="utf-16"?><Profile name="Format My Code Using &quot;Particular&quot; conventions"><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><JsInsertSemicolon>True</JsInsertSemicolon><JsReformatCode>True</JsReformatCode><CssReformatCode>True</CssReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><HtmlReformatCode>True</HtmlReformatCode><CSShortenReferences>True</CSShortenReferences><CSharpFormatDocComments>True</CSharpFormatDocComments><CssAlphabetizeProperties>True</CssAlphabetizeProperties></Profile> - Default: Reformat Code - Format My Code Using "Particular" conventions - Implicit - Implicit - False - DO_NOT_CHANGE - DO_NOT_CHANGE - DO_NOT_CHANGE - DO_NOT_CHANGE - DO_NOT_CHANGE - NEVER - False - False - False - CHOP_ALWAYS - False - CHOP_ALWAYS - CHOP_ALWAYS - True - True - CustomLayout - True - False - True - False - False - False - True - Automatic property - True - False - False - False - AD - DB - DTC - GT - ID - NSB - SLA - $object$_On$event$ - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - $object$_On$event$ - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - <data /> - <data><IncludeFilters /><ExcludeFilters /></data> - True - True - True - True - True - True - True - True - \ No newline at end of file diff --git a/global.json b/global.json deleted file mode 100644 index d497446..0000000 --- a/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "7.0.306", - "allowPrerelease": false, - "rollForward": "latestFeature" - } -} diff --git a/src/Benchmarks/AttributedBenchmarks.cs b/src/Benchmarks/AttributedBenchmarks.cs new file mode 100644 index 0000000..5872bfa --- /dev/null +++ b/src/Benchmarks/AttributedBenchmarks.cs @@ -0,0 +1,31 @@ +// Copyright 2017 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using BenchmarkDotNet.Attributes; + +namespace Benchmarks; + +public class AttributedBenchmarks +{ + [GlobalSetup] + public void Setup() + { + } + + //[Benchmark] + public void Execute() + { + //TODO: implement + } +} diff --git a/src/Benchmarks/Benchmarks.csproj b/src/Benchmarks/Benchmarks.csproj new file mode 100644 index 0000000..31f21ff --- /dev/null +++ b/src/Benchmarks/Benchmarks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + $(NoWarn);1591 + false + + + + + + + + + + + diff --git a/src/Benchmarks/Program.cs b/src/Benchmarks/Program.cs new file mode 100644 index 0000000..f66753c --- /dev/null +++ b/src/Benchmarks/Program.cs @@ -0,0 +1,25 @@ +// Copyright 2017 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Running; +using Benchmarks; + +new AttributedBenchmarks().Setup(); +var config = ManualConfig + .Create(DefaultConfig.Instance) + .AddDiagnoser(MemoryDiagnoser.Default); + +BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); diff --git a/src/Benchmarks/combine-bechmarks.csx b/src/Benchmarks/combine-bechmarks.csx new file mode 100644 index 0000000..f257195 --- /dev/null +++ b/src/Benchmarks/combine-bechmarks.csx @@ -0,0 +1,57 @@ +// Copyright 2017 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.IO; +using System.Linq; +using System.Text.Json.Nodes; + +string resultsDir = "./BenchmarkDotNet.Artifacts/results"; +string resultsFile = "Combined.Benchmarks"; +string searchPattern = "*-report-full-compressed.json"; + +var resultsPath = Path.Combine(resultsDir, resultsFile + ".json"); + +if (!Directory.Exists(resultsDir)) +{ + throw new DirectoryNotFoundException($"Directory not found '{resultsDir}'"); +} + +if (File.Exists(resultsPath)) +{ + File.Delete(resultsPath); +} + +var reports = Directory.GetFiles(resultsDir, searchPattern, SearchOption.TopDirectoryOnly).ToArray(); +if (!reports.Any()) +{ + throw new FileNotFoundException($"Reports not found '{searchPattern}'"); +} + +var combinedReport = JsonNode.Parse(File.ReadAllText(reports.First()))!; +var title = combinedReport["Title"]!; +var benchmarks = combinedReport["Benchmarks"]!.AsArray(); +// Rename title whilst keeping original timestamp +combinedReport["Title"] = $"{resultsFile}{title.GetValue()[^16..]}"; + +foreach (var report in reports.Skip(1)) +{ + var array = JsonNode.Parse(File.ReadAllText(report))!["Benchmarks"]!.AsArray(); + foreach (var benchmark in array) + { + // Double parse avoids "The node already has a parent" exception + benchmarks.Add(JsonNode.Parse(benchmark!.ToJsonString())!); + } +} + +File.WriteAllText(resultsPath, combinedReport.ToString()); diff --git a/src/Destructurama.Attributed.Tests/AttributedDestructuringTests.cs b/src/Destructurama.Attributed.Tests/AttributedDestructuringTests.cs new file mode 100644 index 0000000..e01ba39 --- /dev/null +++ b/src/Destructurama.Attributed.Tests/AttributedDestructuringTests.cs @@ -0,0 +1,86 @@ +using Destructurama.Attributed.Tests.Support; +using NUnit.Framework; +using Serilog; +using Serilog.Events; + +namespace Destructurama.Attributed.Tests; + +[TestFixture] +public class AttributedDestructuringTests +{ + [Test] + public void AttributesAreConsultedWhenDestructuring() + { + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new Customized + { + ImmutableScalar = new(), + MutableScalar = new(), + NotAScalar = new(), + Ignored = "Hello, there", + ScalarAnyway = new(), + AuthData = new() + { + Username = "This is a username", + Password = "This is a password" + } + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsInstanceOf(props["ImmutableScalar"].LiteralValue()); + Assert.AreEqual(new MutableScalar().ToString(), props["MutableScalar"].LiteralValue()); + Assert.IsInstanceOf(props["NotAScalar"]); + Assert.IsFalse(props.ContainsKey("Ignored")); + Assert.IsInstanceOf(props["ScalarAnyway"].LiteralValue()); + + var str = sv.ToString(); + Assert.That(str.Contains("This is a username")); + Assert.False(str.Contains("This is a password")); + } + + [LogAsScalar] + public class ImmutableScalar + { + } + + [LogAsScalar(isMutable: true)] + public class MutableScalar + { + } + + public class NotAScalar + { + } + + public class Customized + { + // ReSharper disable UnusedAutoPropertyAccessor.Global + public ImmutableScalar? ImmutableScalar { get; set; } + public MutableScalar? MutableScalar { get; set; } + public NotAScalar? NotAScalar { get; set; } + + [NotLogged] public string? Ignored { get; set; } + + [LogAsScalar] public NotAScalar? ScalarAnyway { get; set; } + + public UserAuthData? AuthData { get; set; } + } + + public class UserAuthData + { + public string? Username { get; set; } + + [NotLogged] public string? Password { get; set; } + } + +} diff --git a/test/Destructurama.Attributed.Tests/Destructurama.Attributed.Tests.csproj b/src/Destructurama.Attributed.Tests/Destructurama.Attributed.Tests.csproj similarity index 57% rename from test/Destructurama.Attributed.Tests/Destructurama.Attributed.Tests.csproj rename to src/Destructurama.Attributed.Tests/Destructurama.Attributed.Tests.csproj index b5abf50..449dca0 100644 --- a/test/Destructurama.Attributed.Tests/Destructurama.Attributed.Tests.csproj +++ b/src/Destructurama.Attributed.Tests/Destructurama.Attributed.Tests.csproj @@ -1,14 +1,20 @@ - + + - net5.0;net48 + net48;net6.0;net7.0;net8.0 + false + $(NoWarn);1591 - + + + + diff --git a/src/Destructurama.Attributed.Tests/IgnoreNullPropertiesTests.cs b/src/Destructurama.Attributed.Tests/IgnoreNullPropertiesTests.cs new file mode 100644 index 0000000..36c31d9 --- /dev/null +++ b/src/Destructurama.Attributed.Tests/IgnoreNullPropertiesTests.cs @@ -0,0 +1,518 @@ +using System.Collections; +using Destructurama.Attributed.Tests.Support; +using NUnit.Framework; +using Serilog; +using Serilog.Events; + +namespace Destructurama.Attributed.Tests; + +[TestFixture] +public class IgnoreNullPropertiesTests +{ + private struct NotLoggedIfNullStruct + { + public int Integer { get; set; } + + public int? NullableInteger { get; set; } + + public DateTime DateTime { get; set; } + + public DateTime? NullableDateTime { get; set; } + + public object? Object { get; set; } + } + + private class NotLoggedIfNull + { + public string? String { get; set; } + + public int Integer { get; set; } + + public int? NullableInteger { get; set; } + + public object? Object { get; set; } + + public object? IntegerAsObject { get; set; } + + public DateTime DateTime { get; set; } + + public DateTime? NullableDateTime { get; set; } + + public NotLoggedIfNullStruct Struct { get; set; } + + public NotLoggedIfNullStruct? NullableStruct { get; set; } + + public NotLoggedIfNullStruct StructPartiallyInitialized { get; set; } + } + + private class NotLoggedIfNullAttributed + { + [NotLoggedIfNull] + public string? String { get; set; } + + [NotLoggedIfNull] + public int Integer { get; set; } + + [NotLoggedIfNull] + public int? NullableInteger { get; set; } + + [NotLoggedIfNull] + public object? Object { get; set; } + + [NotLoggedIfNull] + public object? IntegerAsObject { get; set; } + + [NotLoggedIfNull] + public DateTime DateTime { get; set; } + + [NotLoggedIfNull] + public DateTime? NullableDateTime { get; set; } + + [NotLoggedIfNull] + public NotLoggedIfNullStruct Struct { get; set; } + + [NotLoggedIfNull] + public NotLoggedIfNullStruct? NullableStruct { get; set; } + + [NotLoggedIfNull] + public NotLoggedIfNullStruct StructPartiallyInitialized { get; set; } + } + + private class AttributedWithMask + { + [LogMasked(ShowFirst = 3)] + public string? String { get; set; } + + [LogMasked(ShowFirst = 3)] + public object? Object { get; set; } + } + + private class Dependency + { + public int Integer { get; set; } + + public int? NullableInteger { get; set; } + } + + private class CustomEnumerableDestructionIgnored : IEnumerable + { + public int Integer { get; set; } + + public Dependency? Dependency { get; set; } + + public IEnumerator GetEnumerator() + { + yield return 1; + } + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + } + + /// + /// At least one attribute from Destructurma.Attributed is enough to ignore all default properties on IEnumerable, + /// when IgnoreNullProperties is true. + /// + private class CustomEnumerableAttributed : IEnumerable + { + [NotLogged] + public bool Dummy { get; set; } + + public int Integer { get; set; } + + public int? NullableInteger { get; set; } + + public Dependency? Dependency { get; set; } + + public IEnumerator GetEnumerator() + { + yield return 1; + } + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + } + + [SetUp] + public void SetUp() + { + AttributedDestructuringPolicy.Clear(); + } + + [TearDown] + public void TearDown() + { + AttributedDestructuringPolicy.Clear(); + } + + [Test] + public void NotLoggedIfNull_Uninitialized() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new NotLoggedIfNull(); + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt!.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("Integer")); + Assert.IsTrue(props.ContainsKey("DateTime")); + Assert.IsTrue(props.ContainsKey("Struct")); + Assert.IsTrue(props.ContainsKey("StructPartiallyInitialized")); + + Assert.IsFalse(props.ContainsKey("String")); + Assert.IsFalse(props.ContainsKey("NullableInteger")); + Assert.IsFalse(props.ContainsKey("IntegerAsObject")); + Assert.IsFalse(props.ContainsKey("Object")); + Assert.IsFalse(props.ContainsKey("NullableDateTime")); + Assert.IsFalse(props.ContainsKey("NullableStruct")); + } + + [Test] + public void NotLoggedIfNull_Initialized() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var dateTime = DateTime.UtcNow; + var theStruct = new NotLoggedIfNullStruct + { + Integer = 20, + NullableInteger = 15, + DateTime = dateTime, + NullableDateTime = dateTime, + Object = "Bar", + }; + + var theStructPartiallyUnitialized = new NotLoggedIfNullStruct + { + Integer = 20, + NullableInteger = 15, + DateTime = dateTime, + NullableDateTime = dateTime, + Object = null, + }; + + var customized = new NotLoggedIfNull + { + String = "Foo", + Integer = 10, + NullableInteger = 5, + Object = "Bar", + IntegerAsObject = 0, + DateTime = dateTime, + NullableDateTime = dateTime, + Struct = theStruct, + NullableStruct = theStruct, + StructPartiallyInitialized = theStructPartiallyUnitialized, + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt!.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("String")); + Assert.IsTrue(props.ContainsKey("Integer")); + Assert.IsTrue(props.ContainsKey("NullableInteger")); + Assert.IsTrue(props.ContainsKey("Object")); + Assert.IsTrue(props.ContainsKey("IntegerAsObject")); + Assert.IsTrue(props.ContainsKey("DateTime")); + Assert.IsTrue(props.ContainsKey("NullableDateTime")); + Assert.IsTrue(props.ContainsKey("Struct")); + Assert.IsTrue(props.ContainsKey("NullableStruct")); + Assert.IsTrue(props.ContainsKey("StructPartiallyInitialized")); + + Assert.AreEqual("Foo", props["String"].LiteralValue()); + Assert.AreEqual(10, props["Integer"].LiteralValue()); + Assert.AreEqual(5, props["NullableInteger"].LiteralValue()); + Assert.AreEqual("Bar", props["Object"].LiteralValue()); + Assert.AreEqual(0, props["IntegerAsObject"].LiteralValue()); + Assert.AreEqual(dateTime, props["DateTime"].LiteralValue()); + Assert.AreEqual(dateTime, props["NullableDateTime"].LiteralValue()); + Assert.IsInstanceOf(props["Struct"]); + Assert.IsInstanceOf(props["NullableStruct"]); + Assert.IsInstanceOf(props["StructPartiallyInitialized"]); + + var structProps = ((StructureValue)props["Struct"]).Properties + .ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(structProps.ContainsKey("Integer")); + Assert.IsTrue(structProps.ContainsKey("NullableInteger")); + Assert.IsTrue(structProps.ContainsKey("DateTime")); + Assert.IsTrue(structProps.ContainsKey("NullableDateTime")); + Assert.IsTrue(structProps.ContainsKey("Object")); + Assert.AreEqual(20, structProps["Integer"].LiteralValue()); + Assert.AreEqual(15, structProps["NullableInteger"].LiteralValue()); + Assert.AreEqual(dateTime, structProps["DateTime"].LiteralValue()); + Assert.AreEqual(dateTime, structProps["NullableDateTime"].LiteralValue()); + Assert.AreEqual("Bar", structProps["Object"].LiteralValue()); + + var partiallyItitializedProps = ((StructureValue)props["StructPartiallyInitialized"]).Properties + .ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(partiallyItitializedProps.ContainsKey("Integer")); + Assert.IsTrue(partiallyItitializedProps.ContainsKey("NullableInteger")); + Assert.IsTrue(partiallyItitializedProps.ContainsKey("DateTime")); + Assert.IsTrue(partiallyItitializedProps.ContainsKey("NullableDateTime")); + Assert.IsFalse(partiallyItitializedProps.ContainsKey("Object")); + Assert.AreEqual(20, partiallyItitializedProps["Integer"].LiteralValue()); + Assert.AreEqual(15, partiallyItitializedProps["NullableInteger"].LiteralValue()); + Assert.AreEqual(dateTime, partiallyItitializedProps["DateTime"].LiteralValue()); + Assert.AreEqual(dateTime, partiallyItitializedProps["NullableDateTime"].LiteralValue()); + } + + [Test] + public void WithMask_NotLoggedIfNull_Uninitialized() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new AttributedWithMask(); + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt!.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsFalse(props.ContainsKey("String")); + Assert.IsFalse(props.ContainsKey("Object")); + } + + [Test] + public void WithMask_NotLoggedIfNull_Initialized() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var dateTime = new DateTime(2000, 1, 2, 3, 4, 5); + var customized = new AttributedWithMask + { + String = "Foo[Masked]", + Object = "Bar[Masked]", + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt!.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("String")); + Assert.IsTrue(props.ContainsKey("Object")); + + Assert.AreEqual("Foo***", props["String"].LiteralValue()); + Assert.AreEqual("Bar***", props["Object"].LiteralValue()); + } + + [Test] + public void EnumerableIgnored() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomEnumerableDestructionIgnored() + { + Integer = 0, + Dependency = new Dependency + { + Integer = 0, + } + }; + + log.Information("Here is {@Customized}", customized); + + var sv = evt!.Properties["Customized"]; + Assert.IsInstanceOf(sv); + } + + [Test] + public void EnumerableDestructedAsStruct() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomEnumerableAttributed + { + Integer = 0, + NullableInteger = null, + Dependency = new Dependency + { + Integer = 0, + }, + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt!.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("Integer")); + Assert.IsFalse(props.ContainsKey("NullableInteger")); + Assert.IsTrue(props.ContainsKey("Dependency")); + + var dependencyProps = ((StructureValue)props["Dependency"]).Properties + .ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(dependencyProps.ContainsKey("Integer")); + Assert.IsFalse(dependencyProps.ContainsKey("NullableInteger")); + } + + [Test] + public void NotLoggedIfNullAttribute_Uninitialized() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes(x => x.IgnoreNullProperties = false) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new NotLoggedIfNullAttributed(); + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt!.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("Integer")); + Assert.IsTrue(props.ContainsKey("DateTime")); + Assert.IsTrue(props.ContainsKey("Struct")); + Assert.IsTrue(props.ContainsKey("StructPartiallyInitialized")); + + Assert.IsFalse(props.ContainsKey("String")); + Assert.IsFalse(props.ContainsKey("NullableInteger")); + Assert.IsFalse(props.ContainsKey("IntegerAsObject")); + Assert.IsFalse(props.ContainsKey("Object")); + Assert.IsFalse(props.ContainsKey("NullableDateTime")); + Assert.IsFalse(props.ContainsKey("NullableStruct")); + } + + [Test] + public void NotLoggedIfNullAttribute_Initialized() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes(x => x.IgnoreNullProperties = false) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var dateTime = DateTime.UtcNow; + var theStruct = new NotLoggedIfNullStruct + { + Integer = 20, + NullableInteger = 15, + DateTime = dateTime, + NullableDateTime = dateTime, + Object = "Bar", + }; + + var theStructPartiallyUnitialized = new NotLoggedIfNullStruct + { + Integer = 20, + NullableInteger = 15, + DateTime = dateTime, + NullableDateTime = dateTime, + Object = null, + }; + + var customized = new NotLoggedIfNullAttributed + { + String = "Foo", + Integer = 10, + NullableInteger = 5, + Object = "Bar", + IntegerAsObject = 0, + DateTime = dateTime, + NullableDateTime = dateTime, + Struct = theStruct, + NullableStruct = theStruct, + StructPartiallyInitialized = theStructPartiallyUnitialized, + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt!.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("String")); + Assert.IsTrue(props.ContainsKey("Integer")); + Assert.IsTrue(props.ContainsKey("NullableInteger")); + Assert.IsTrue(props.ContainsKey("Object")); + Assert.IsTrue(props.ContainsKey("IntegerAsObject")); + Assert.IsTrue(props.ContainsKey("DateTime")); + Assert.IsTrue(props.ContainsKey("NullableDateTime")); + Assert.IsTrue(props.ContainsKey("Struct")); + Assert.IsTrue(props.ContainsKey("NullableStruct")); + Assert.IsTrue(props.ContainsKey("StructPartiallyInitialized")); + + Assert.AreEqual("Foo", props["String"].LiteralValue()); + Assert.AreEqual(10, props["Integer"].LiteralValue()); + Assert.AreEqual(5, props["NullableInteger"].LiteralValue()); + Assert.AreEqual("Bar", props["Object"].LiteralValue()); + Assert.AreEqual(0, props["IntegerAsObject"].LiteralValue()); + Assert.AreEqual(dateTime, props["DateTime"].LiteralValue()); + Assert.AreEqual(dateTime, props["NullableDateTime"].LiteralValue()); + Assert.IsInstanceOf(props["Struct"]); + Assert.IsInstanceOf(props["NullableStruct"]); + Assert.IsInstanceOf(props["StructPartiallyInitialized"]); + + var structProps = ((StructureValue)props["Struct"]).Properties + .ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(structProps.ContainsKey("Integer")); + Assert.IsTrue(structProps.ContainsKey("NullableInteger")); + Assert.IsTrue(structProps.ContainsKey("DateTime")); + Assert.IsTrue(structProps.ContainsKey("NullableDateTime")); + Assert.IsTrue(structProps.ContainsKey("Object")); + Assert.AreEqual(20, structProps["Integer"].LiteralValue()); + Assert.AreEqual(15, structProps["NullableInteger"].LiteralValue()); + Assert.AreEqual(dateTime, structProps["DateTime"].LiteralValue()); + Assert.AreEqual(dateTime, structProps["NullableDateTime"].LiteralValue()); + Assert.AreEqual("Bar", structProps["Object"].LiteralValue()); + + var partiallyItitializedProps = ((StructureValue)props["StructPartiallyInitialized"]).Properties + .ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(partiallyItitializedProps.ContainsKey("Integer")); + Assert.IsTrue(partiallyItitializedProps.ContainsKey("NullableInteger")); + Assert.IsTrue(partiallyItitializedProps.ContainsKey("DateTime")); + Assert.IsTrue(partiallyItitializedProps.ContainsKey("NullableDateTime")); + Assert.IsTrue(partiallyItitializedProps.ContainsKey("Object")); + Assert.AreEqual(20, partiallyItitializedProps["Integer"].LiteralValue()); + Assert.AreEqual(15, partiallyItitializedProps["NullableInteger"].LiteralValue()); + Assert.AreEqual(dateTime, partiallyItitializedProps["DateTime"].LiteralValue()); + Assert.AreEqual(dateTime, partiallyItitializedProps["NullableDateTime"].LiteralValue()); + + } + +} + + diff --git a/src/Destructurama.Attributed.Tests/LogWithNameAttributedTests.cs b/src/Destructurama.Attributed.Tests/LogWithNameAttributedTests.cs new file mode 100644 index 0000000..b3a0059 --- /dev/null +++ b/src/Destructurama.Attributed.Tests/LogWithNameAttributedTests.cs @@ -0,0 +1,43 @@ +using Destructurama.Attributed.Tests.Support; +using NUnit.Framework; +using Serilog; +using Serilog.Events; + +namespace Destructurama.Attributed.Tests; + +[TestFixture] +public class LogWithNameAttributedTests +{ + + [Test] + public void AttributesAreConsultedWhenDestructuring() + { + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var personalData = new PersonalData + { + Name = "John Doe" + }; + + log.Information("Here is {@PersonData}", personalData); + + var sv = (StructureValue)evt.Properties["PersonData"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + var literalValue = props["FullName"].LiteralValue(); + Assert.AreEqual("John Doe", literalValue); + } + + #region LogWithName + public class PersonalData + { + [LogWithName("FullName")] + public string? Name { get; set; } + } + #endregion +} diff --git a/src/Destructurama.Attributed.Tests/MaskedAttributeTests.cs b/src/Destructurama.Attributed.Tests/MaskedAttributeTests.cs new file mode 100644 index 0000000..6209247 --- /dev/null +++ b/src/Destructurama.Attributed.Tests/MaskedAttributeTests.cs @@ -0,0 +1,892 @@ +using Destructurama.Attributed.Tests.Support; +using NUnit.Framework; +using Serilog; +using Serilog.Events; + +namespace Destructurama.Attributed.Tests; + +#region CustomizedMaskedLogs + +public class CustomizedMaskedLogs +{ + /// + /// 123456789 results in "***" + /// + [LogMasked] + public string? DefaultMasked { get; set; } + + /// + /// [123456789,123456789,123456789] results in [***,***,***] + /// + [LogMasked] + public string[]? DefaultMaskedArray { get; set; } + + /// + /// 123456789 results in "*********" + /// + [LogMasked(PreserveLength = true)] + public string? DefaultMaskedPreserved { get; set; } + + /// + /// "" results in "***" + /// + [LogMasked] + public string? DefaultMaskedNotPreservedOnEmptyString { get; set; } + + /// + /// 123456789 results in "#" + /// + [LogMasked(Text = "_REMOVED_")] + public string? CustomMasked { get; set; } + + /// + /// 123456789 results in "#" + /// + [LogMasked(Text = "")] + public string? CustomMaskedWithEmptyString { get; set; } + + /// + /// 123456789 results in "#########" + /// + [LogMasked(Text = "#", PreserveLength = true)] + public string? CustomMaskedPreservedLength { get; set; } + + /// + /// 123456789 results in "123******" + /// + [LogMasked(ShowFirst = 3)] + public string? ShowFirstThreeThenDefaultMasked { get; set; } + + /// + /// 123456789 results in "123******" + /// + [LogMasked(ShowFirst = 3, PreserveLength = true)] + public string? ShowFirstThreeThenDefaultMaskedPreservedLength { get; set; } + + /// + /// 123456789 results in "***789" + /// + [LogMasked(ShowLast = 3)] + public string? ShowLastThreeThenDefaultMasked { get; set; } + + /// + /// 123456789 results in "******789" + /// + [LogMasked(ShowLast = 3, PreserveLength = true)] + public string? ShowLastThreeThenDefaultMaskedPreservedLength { get; set; } + + /// + /// 123456789 results in "123REMOVED" + /// + [LogMasked(Text = "_REMOVED_", ShowFirst = 3)] + public string? ShowFirstThreeThenCustomMask { get; set; } + + /// + /// 123456789 results in "123_REMOVED_" + /// + [LogMasked(Text = "_REMOVED_", ShowFirst = 3, PreserveLength = true)] + public string? ShowFirstThreeThenCustomMaskPreservedLengthIgnored { get; set; } + + /// + /// 123456789 results in "_REMOVED_789" + /// + [LogMasked(Text = "_REMOVED_", ShowLast = 3)] + public string? ShowLastThreeThenCustomMask { get; set; } + + /// + /// 123456789 results in "_REMOVED_789" + /// + [LogMasked(Text = "_REMOVED_", ShowLast = 3, PreserveLength = true)] + public string? ShowLastThreeThenCustomMaskPreservedLengthIgnored { get; set; } + + /// + /// 123456789 results in "123***789" + /// + [LogMasked(ShowFirst = 3, ShowLast = 3)] + public string? ShowFirstAndLastThreeAndDefaultMaskInTheMiddle { get; set; } + + /// + /// 123456789 results in "123***789" + /// + [LogMasked(ShowFirst = 3, ShowLast = 3, PreserveLength = true)] + public string? ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength { get; set; } + + /// + /// 123456789 results in "123_REMOVED_789" + /// + [LogMasked(Text = "_REMOVED_", ShowFirst = 3, ShowLast = 3)] + public string? ShowFirstAndLastThreeAndCustomMaskInTheMiddle { get; set; } + + /// + /// 123456789 results in "123_REMOVED_789". PreserveLength is ignored" + /// + [LogMasked(Text = "_REMOVED_", ShowFirst = 3, ShowLast = 3, PreserveLength = true)] + public string? ShowFirstAndLastThreeAndCustomMaskInTheMiddlePreservedLengthIgnored { get; set; } +} + +#endregion + +[TestFixture] +public class MaskedAttributeTests +{ + [Test] + public void LogMaskedAttribute_Replaces_Value_With_DefaultStars_Mask() + { + // [LogMasked] + // 123456789 -> "***" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + DefaultMasked = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("DefaultMasked")); + Assert.AreEqual("***", props["DefaultMasked"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Replaces_Array_Value_With_DefaultStars_Mask() + { + // [LogMasked] + // [123456789,123456789,123456789] results in [***,***,***] + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + DefaultMaskedArray = new[] { "123456789", "123456789", "123456789" } + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("DefaultMaskedArray")); + var seq = props["DefaultMaskedArray"] as SequenceValue; + foreach (var elem in seq!.Elements) + Assert.AreEqual("***", elem.LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Replaces_Value_With_DefaultStars_Mask_And_PreservedLength() + { + // [LogMasked] + // 123456789 -> "*********" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + DefaultMaskedPreserved = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("DefaultMaskedPreserved")); + Assert.AreEqual("*********", props["DefaultMaskedPreserved"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Replaces_Value_With_DefaultStars_Mask_And_Not_Preserve_Length_On_Empty_String() + { + // [LogMasked] + // "" -> "***" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + DefaultMaskedNotPreservedOnEmptyString = "" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("DefaultMaskedNotPreservedOnEmptyString")); + Assert.AreEqual("***", props["DefaultMaskedNotPreservedOnEmptyString"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Replaces_Value_With_Provided_Mask() + { + // [LogMasked(Text = "#")] + // 123456789 -> "_REMOVED_" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + CustomMasked = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("CustomMasked")); + Assert.AreEqual("_REMOVED_", props["CustomMasked"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Replaces_Value_With_Provided_Empty_Mask() + { + // [LogMasked(Text = "#")] + // 123456789 -> "_REMOVED_" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + CustomMaskedWithEmptyString = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("CustomMasked")); + Assert.AreEqual("", props["CustomMaskedWithEmptyString"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Replaces_Value_With_Provided_Mask_And_PreservedLength() + { + // [LogMasked(Text = "#")] + // 123456789 -> "#########" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + CustomMaskedPreservedLength = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("CustomMaskedPreservedLength")); + Assert.AreEqual("#########", props["CustomMaskedPreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_With_Custom_Mask() + { + // [LogMasked(Text = "REMOVED", ShowFirst = 3)] + // -> "123_REMOVED_" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstThreeThenCustomMask = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenCustomMask")); + Assert.AreEqual("123_REMOVED_", props["ShowFirstThreeThenCustomMask"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_With_Custom_Mask_PreservedLength_Ignored() + { + // [LogMasked(Text = "REMOVED", ShowFirst = 3,PreserveLength = true)] + // -> "123_REMOVED_" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstThreeThenCustomMaskPreservedLengthIgnored = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenCustomMaskPreservedLengthIgnored")); + Assert.AreEqual("123_REMOVED_", props["ShowFirstThreeThenCustomMaskPreservedLengthIgnored"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Replaces_Value_With_Default_StarMask() + { + // [LogMasked(ShowFirst = 3, ShowLast = 3)] + // -> "123***321" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstAndLastThreeAndDefaultMaskInTheMiddle = "12345678987654321" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndDefaultMaskInTheMiddle")); + Assert.AreEqual("123***321", props["ShowFirstAndLastThreeAndDefaultMaskInTheMiddle"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Replaces_Value_With_Default_StarMask_PreserveLength() + { + // [LogMasked(ShowFirst = 3, ShowLast = 3)] + // -> "123***********321" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength = "12345678987654321" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength")); + Assert.AreEqual("123***********321", props["ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Replaces_Value_With_Default_StarMask_Single_PreserveLength() + { + // [LogMasked(ShowFirst = 3, ShowLast = 3)] + // -> "123*456" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength = "123x456" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength")); + Assert.AreEqual("123*456", props["ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask() + { + // [LogMasked(Text = "REMOVED", ShowFirst = 3, ShowLast = 3)] + // 12345678987654321 -> 123_REMOVED_321 + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstAndLastThreeAndCustomMaskInTheMiddle = "12345678987654321" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddle")); + Assert.AreEqual("123_REMOVED_321", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddle"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_And_PreservedLength() + { + // [LogMasked(Text = "#", ShowFirst = 3, ShowLast = 3)] + // 12345678987654321 -> "123_REMOVED_321" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstAndLastThreeAndCustomMaskInTheMiddle = "12345678987654321" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddle")); + Assert.AreEqual("123_REMOVED_321", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddle"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_And_PreservedLength_Even_When_Input_Length_Is_Less_Than_ShowFirst() + { + // [LogMasked(Text = "#", ShowFirst = 3, ShowLast = 3)] + // 12 -> "12" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstAndLastThreeAndCustomMaskInTheMiddle = "12" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddle")); + Assert.AreEqual("12", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddle"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_And_PreservedLength_Even_When_Input_Length_Is_Less_Than_ShowFirst_Plus_ShowLast() + { + // [LogMasked(Text = "#", ShowFirst = 3, ShowLast = 3)] + // 1234 -> "1234" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstAndLastThreeAndCustomMaskInTheMiddle = "1234" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddle")); + Assert.AreEqual("1234", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddle"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask() + { + // [LogMasked(ShowLast = 3)] + // 123456789 -> "123***" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstThreeThenDefaultMasked = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMasked")); + Assert.AreEqual("123***", props["ShowFirstThreeThenDefaultMasked"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask() + { + // [LogMasked(Text = "_REMOVED_", ShowLast = 3)] + // 123456789 -> "_REMOVED_789" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowLastThreeThenCustomMask = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowLastThreeThenCustomMask")); + Assert.AreEqual("_REMOVED_789", props["ShowLastThreeThenCustomMask"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_PreserveLength_Ignored() + { + // [LogMasked(Text = "_REMOVED_", ShowLast = 3, PreserveLength = true)] + // 123456789 -> "_REMOVED_789" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowLastThreeThenCustomMaskPreservedLengthIgnored = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowLastThreeThenCustomMaskPreservedLengthIgnored")); + Assert.AreEqual("_REMOVED_789", props["ShowLastThreeThenCustomMaskPreservedLengthIgnored"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask() + { + // [LogMasked(ShowLast = 3)] + // 123456789 -> "***789" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowLastThreeThenDefaultMasked = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowLastThreeThenDefaultMasked")); + Assert.AreEqual("***789", props["ShowLastThreeThenDefaultMasked"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength() + { + // [LogMasked(ShowFirst = 3,PreserveLength = true))] + // -> "123******" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstThreeThenDefaultMaskedPreservedLength = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMaskedPreservedLength")); + Assert.AreEqual("123******", props["ShowFirstThreeThenDefaultMaskedPreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Empty_Input() + { + // [LogMasked(ShowFirst = 3,PreserveLength = true))] + // -> "" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstThreeThenDefaultMaskedPreservedLength = "" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMaskedPreservedLength")); + Assert.AreEqual("", props["ShowFirstThreeThenDefaultMaskedPreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Input_With_Same_Length_As_ShowFirst() + { + // [LogMasked(ShowFirst = 3,PreserveLength = true))] + // -> "123" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstThreeThenDefaultMaskedPreservedLength = "123" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMaskedPreservedLength")); + Assert.AreEqual("123", props["ShowFirstThreeThenDefaultMaskedPreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Input_Shorter_Than_ShowFirst() + { + // [LogMasked(ShowFirst = 3,PreserveLength = true))] + // -> "12" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstThreeThenDefaultMaskedPreservedLength = "12" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMaskedPreservedLength")); + Assert.AreEqual("12", props["ShowFirstThreeThenDefaultMaskedPreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength() + { + // [LogMasked(ShowLast = 3,PreserveLength = true))] + // -> "******789" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowLastThreeThenDefaultMaskedPreservedLength = "123456789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowLastThreeThenDefaultMaskedPreservedLength")); + Assert.AreEqual("******789", props["ShowLastThreeThenDefaultMaskedPreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Input_With_Same_Length_As_ShowLast() + { + // [LogMasked(ShowLast = 3,PreserveLength = true))] + // -> "123" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowLastThreeThenDefaultMaskedPreservedLength = "123" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowLastThreeThenDefaultMaskedPreservedLength")); + Assert.AreEqual("123", props["ShowLastThreeThenDefaultMaskedPreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Input_Shorter_Than_ShowLast() + { + // [LogMasked(ShowLast = 3,PreserveLength = true))] + // -> "12" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowLastThreeThenDefaultMaskedPreservedLength = "12" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowLastThreeThenDefaultMaskedPreservedLength")); + Assert.AreEqual("12", props["ShowLastThreeThenDefaultMaskedPreservedLength"].LiteralValue()); + } + + [Test] + public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_And_PreservedLength_That_Gives_Warning() + { + // [LogMasked(Text = "REMOVED", ShowFirst = 3, ShowLast = 3, PreserveLength = true)] + // 12345678987654321 -> 123_REMOVED_321 + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedMaskedLogs + { + ShowFirstAndLastThreeAndCustomMaskInTheMiddlePreservedLengthIgnored = "12345678987654321" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddlePreservedLengthIgnored")); + Assert.AreEqual("123_REMOVED_321", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddlePreservedLengthIgnored"].LiteralValue()); + } +} diff --git a/src/Destructurama.Attributed.Tests/NotLoggedIfDefaultAttributeTests.cs b/src/Destructurama.Attributed.Tests/NotLoggedIfDefaultAttributeTests.cs new file mode 100644 index 0000000..dba97e5 --- /dev/null +++ b/src/Destructurama.Attributed.Tests/NotLoggedIfDefaultAttributeTests.cs @@ -0,0 +1,210 @@ +using Destructurama.Attributed.Tests.Support; +using NUnit.Framework; +using Serilog; +using Serilog.Events; + +namespace Destructurama.Attributed.Tests; + +[TestFixture] +public class NotLoggedIfDefaultAttributeTests +{ + private struct NotLoggedIfDefaultStruct + { + public int Integer { get; set; } + + public DateTime DateTime { get; set; } + } + + private struct NotLoggedIfDefaultStructWithAttributes + { + [NotLoggedIfDefault] + public int Integer { get; set; } + + [NotLoggedIfDefault] + public DateTime DateTime { get; set; } + + public int IntegerLogged { get; set; } + + public DateTime DateTimeLogged { get; set; } + } + + private class NotLoggedIfDefaultCustomizedDefaultLogs + { + [NotLoggedIfDefault] + public string? String { get; set; } + + [NotLoggedIfDefault] + public int Integer { get; set; } + + [NotLoggedIfDefault] + public int? NullableInteger { get; set; } + + [NotLoggedIfDefault] + public object? Object { get; set; } + + [NotLoggedIfDefault] + public object? IntegerAsObject { get; set; } + + [NotLoggedIfDefault] + public DateTime DateTime { get; set; } + + [NotLoggedIfDefault] + public NotLoggedIfDefaultStruct Struct { get; set; } + + public NotLoggedIfDefaultStructWithAttributes StructWithAttributes { get; set; } + + public string? StringLogged { get; set; } + + public int IntegerLogged { get; set; } + + public int? NullableIntegerLogged { get; set; } + + public object? ObjectLogged { get; set; } + + public DateTime DateTimeLogged { get; set; } + + [LogAsScalar] + public NotLoggedIfDefaultStruct StructLogged { get; set; } + } + + + [Test] + public void NotLoggedIfDefault_Uninitialized() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new NotLoggedIfDefaultCustomizedDefaultLogs(); + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt!.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsFalse(props.ContainsKey("String")); + Assert.IsFalse(props.ContainsKey("Integer")); + Assert.IsFalse(props.ContainsKey("NullableInteger")); + Assert.IsFalse(props.ContainsKey("Object")); + Assert.IsFalse(props.ContainsKey("DateTime")); + Assert.IsFalse(props.ContainsKey("Struct")); + + Assert.IsTrue(props.ContainsKey("StringLogged")); + Assert.IsTrue(props.ContainsKey("IntegerLogged")); + Assert.IsTrue(props.ContainsKey("NullableIntegerLogged")); + Assert.IsTrue(props.ContainsKey("ObjectLogged")); + Assert.IsTrue(props.ContainsKey("DateTimeLogged")); + Assert.IsTrue(props.ContainsKey("StructLogged")); + + Assert.AreEqual(default(string), props["StringLogged"].LiteralValue()); + Assert.AreEqual(default(int), props["IntegerLogged"].LiteralValue()); + Assert.AreEqual(default(int?), props["NullableIntegerLogged"].LiteralValue()); + Assert.AreEqual(default, props["ObjectLogged"].LiteralValue()); + Assert.AreEqual(default(DateTime), props["DateTimeLogged"].LiteralValue()); + Assert.AreEqual(default(NotLoggedIfDefaultStruct), props["StructLogged"].LiteralValue()); + + Assert.IsTrue(props.ContainsKey("StructWithAttributes")); + Assert.IsTrue(props["StructWithAttributes"] is StructureValue); + + var structProps = ((StructureValue)props["StructWithAttributes"]).Properties + .ToDictionary(p => p.Name, p => p.Value); + + Assert.IsFalse(structProps.ContainsKey("Integer")); + Assert.IsFalse(structProps.ContainsKey("DateTime")); + Assert.IsTrue(structProps.ContainsKey("IntegerLogged")); + Assert.IsTrue(structProps.ContainsKey("DateTimeLogged")); + Assert.AreEqual(default(int), structProps["IntegerLogged"].LiteralValue()); + Assert.AreEqual(default(DateTime), structProps["DateTimeLogged"].LiteralValue()); + } + + [Test] + public void NotLoggedIfDefault_Initialized() + { + LogEvent? evt = null; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var dateTime = DateTime.UtcNow; + var theStruct = new NotLoggedIfDefaultStruct + { + Integer = 20, + DateTime = dateTime + }; + + var attributedStruct = new NotLoggedIfDefaultStructWithAttributes + { + Integer = 20, + DateTime = dateTime + }; + + var customized = new NotLoggedIfDefaultCustomizedDefaultLogs + { + String = "Foo", + Integer = 10, + NullableInteger = 5, + Object = "Bar", + DateTime = dateTime, + Struct = theStruct, + StructWithAttributes = attributedStruct, + IntegerAsObject = 0 + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt!.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("String")); + Assert.IsTrue(props.ContainsKey("Integer")); + Assert.IsTrue(props.ContainsKey("NullableInteger")); + Assert.IsTrue(props.ContainsKey("Object")); + Assert.IsTrue(props.ContainsKey("DateTime")); + Assert.IsTrue(props.ContainsKey("Struct")); + Assert.IsTrue(props.ContainsKey("IntegerAsObject")); + + Assert.IsTrue(props.ContainsKey("StringLogged")); + Assert.IsTrue(props.ContainsKey("IntegerLogged")); + Assert.IsTrue(props.ContainsKey("NullableIntegerLogged")); + Assert.IsTrue(props.ContainsKey("ObjectLogged")); + Assert.IsTrue(props.ContainsKey("DateTimeLogged")); + Assert.IsTrue(props.ContainsKey("StructLogged")); + + Assert.AreEqual("Foo", props["String"].LiteralValue()); + Assert.AreEqual(10, props["Integer"].LiteralValue()); + Assert.AreEqual(5, props["NullableInteger"].LiteralValue()); + Assert.AreEqual("Bar", props["Object"].LiteralValue()); + Assert.AreEqual(dateTime, props["DateTime"].LiteralValue()); + Assert.IsInstanceOf(props["Struct"]); + Assert.AreEqual(0, props["IntegerAsObject"].LiteralValue()); + + Assert.AreEqual(default(string), props["StringLogged"].LiteralValue()); + Assert.AreEqual(default(int), props["IntegerLogged"].LiteralValue()); + Assert.AreEqual(default(int?), props["NullableIntegerLogged"].LiteralValue()); + Assert.AreEqual(default, props["ObjectLogged"].LiteralValue()); + Assert.AreEqual(default(DateTime), props["DateTimeLogged"].LiteralValue()); + Assert.AreEqual(default(NotLoggedIfDefaultStruct), props["StructLogged"].LiteralValue()); + + Assert.IsTrue(props.ContainsKey("StructWithAttributes")); + Assert.IsTrue(props["StructWithAttributes"] is StructureValue); + + var structProps = ((StructureValue)props["StructWithAttributes"]).Properties + .ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(structProps.ContainsKey("Integer")); + Assert.IsTrue(structProps.ContainsKey("DateTime")); + Assert.IsTrue(structProps.ContainsKey("IntegerLogged")); + Assert.IsTrue(structProps.ContainsKey("DateTimeLogged")); + Assert.AreEqual(20, structProps["Integer"].LiteralValue()); + Assert.AreEqual(dateTime, structProps["DateTime"].LiteralValue()); + Assert.AreEqual(default(int), structProps["IntegerLogged"].LiteralValue()); + Assert.AreEqual(default(DateTime), structProps["DateTimeLogged"].LiteralValue()); + } +} + + diff --git a/src/Destructurama.Attributed.Tests/ReplacedAttributeTests.cs b/src/Destructurama.Attributed.Tests/ReplacedAttributeTests.cs new file mode 100644 index 0000000..aa2ba2e --- /dev/null +++ b/src/Destructurama.Attributed.Tests/ReplacedAttributeTests.cs @@ -0,0 +1,177 @@ +using Destructurama.Attributed.Tests.Support; +using NUnit.Framework; +using Serilog; +using Serilog.Events; + +namespace Destructurama.Attributed.Tests; + +public class CustomizedRegexLogs +{ + private const string REGEX_WITH_VERTICAL_BARS = @"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)"; + + /// + /// 123|456|789 results in "***|456|789" + /// + [LogReplaced(REGEX_WITH_VERTICAL_BARS, "***|$2|$3")] + public string? RegexReplaceFirst { get; set; } + + /// + /// 123|456|789 results in "123|***|789" + /// + [LogReplaced(REGEX_WITH_VERTICAL_BARS, "$1|***|$3")] + public string? RegexReplaceSecond { get; set; } + + /// + /// 123|456|789 results in "123|456|***" + /// + [LogReplaced(REGEX_WITH_VERTICAL_BARS, "$1|$2|***")] + public string? RegexReplaceThird { get; set; } + + /// + /// 123|456|789 results in "***|456|****" + /// + [LogReplaced(REGEX_WITH_VERTICAL_BARS, "***|$2|****")] + public string? RegexReplaceFirstThird { get; set; } +} + +[TestFixture] +public class ReplacedAttributeTests +{ + [Test] + public void LogReplacedAttribute_Replaces_First() + { + // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "***|$2|$3")] + // 123|456|789 -> "***|456|789" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedRegexLogs + { + RegexReplaceFirst = "123|456|789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("RegexReplaceFirst")); + Assert.AreEqual("***|456|789", props["RegexReplaceFirst"].LiteralValue()); + } + + [Test] + public void LogReplacedAttribute_Replaces_Second() + { + // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "$1|***|$3")] + // 123|456|789 -> "123|***|789" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedRegexLogs + { + RegexReplaceSecond = "123|456|789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("RegexReplaceSecond")); + Assert.AreEqual("123|***|789", props["RegexReplaceSecond"].LiteralValue()); + } + + [Test] + public void LogReplacedAttribute_Replaces_Third() + { + // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "$1|$2|***")] + // 123|456|789 -> "123|456|***" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedRegexLogs + { + RegexReplaceThird = "123|456|789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("RegexReplaceThird")); + Assert.AreEqual("123|456|***", props["RegexReplaceThird"].LiteralValue()); + } + + [Test] + public void LogReplacedAttribute_Replaces_FirstThird() + { + // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "***|$2|****")] + // 123|456|789 -> "***|456|****" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedRegexLogs + { + RegexReplaceFirstThird = "123|456|789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("RegexReplaceFirstThird")); + Assert.AreEqual("***|456|****", props["RegexReplaceFirstThird"].LiteralValue()); + } + + [Test] + public void LogReplacedAttribute_Replaces_First_And_Third() + { + // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "***|$2|$3")] + // 123|456|789 -> "***|456|789" + + LogEvent evt = null!; + + var log = new LoggerConfiguration() + .Destructure.UsingAttributes() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var customized = new CustomizedRegexLogs + { + RegexReplaceFirst = "123|456|789", + RegexReplaceThird = "123|456|789" + }; + + log.Information("Here is {@Customized}", customized); + + var sv = (StructureValue)evt.Properties["Customized"]; + var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); + + Assert.IsTrue(props.ContainsKey("RegexReplaceFirst")); + Assert.AreEqual("***|456|789", props["RegexReplaceFirst"].LiteralValue()); + Assert.IsTrue(props.ContainsKey("RegexReplaceThird")); + Assert.AreEqual("123|456|***", props["RegexReplaceThird"].LiteralValue()); + } +} diff --git a/src/Destructurama.Attributed.Tests/Snippets.cs b/src/Destructurama.Attributed.Tests/Snippets.cs new file mode 100644 index 0000000..dca377a --- /dev/null +++ b/src/Destructurama.Attributed.Tests/Snippets.cs @@ -0,0 +1,49 @@ +using NUnit.Framework; +using Serilog; + +namespace Destructurama.Attributed.Tests; + +#region WithRegex + +public class WithRegex +{ + private const string REGEX_WITH_VERTICAL_BARS = @"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)"; + + /// + /// 123|456|789 results in "***|456|789" + /// + [LogReplaced(REGEX_WITH_VERTICAL_BARS, "***|$2|$3")] + public string? RegexReplaceFirst { get; set; } + + /// + /// 123|456|789 results in "123|***|789" + /// + [LogReplaced(REGEX_WITH_VERTICAL_BARS, "$1|***|$3")] + public string? RegexReplaceSecond { get; set; } +} + +#endregion + +public class Snippets +{ + #region LoginCommand + public class LoginCommand + { + public string? Username { get; set; } + + [NotLogged] + public string? Password { get; set; } + } + #endregion + + private static readonly ILogger _log = Log.ForContext(); + + [Test] + public void LogCommand() + { + #region LogCommand + var command = new LoginCommand { Username = "logged", Password = "not logged" }; + _log.Information("Logging in {@Command}", command); + #endregion + } +} diff --git a/src/Destructurama.Attributed.Tests/Support/DelegatingSink.cs b/src/Destructurama.Attributed.Tests/Support/DelegatingSink.cs new file mode 100644 index 0000000..a7795ef --- /dev/null +++ b/src/Destructurama.Attributed.Tests/Support/DelegatingSink.cs @@ -0,0 +1,31 @@ +using Serilog; +using Serilog.Core; +using Serilog.Events; + +namespace Destructurama.Attributed.Tests.Support; + +public class DelegatingSink : ILogEventSink +{ + private readonly Action _write; + + public DelegatingSink(Action write) + { + _write = write ?? throw new ArgumentNullException("write"); + } + + public void Emit(LogEvent logEvent) + { + _write(logEvent); + } + + public static LogEvent GetLogEvent(Action writeAction) + { + LogEvent result = null!; + var l = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(le => result = le)) + .CreateLogger(); + + writeAction(l); + return result; + } +} diff --git a/src/Destructurama.Attributed.Tests/Support/Extensions.cs b/src/Destructurama.Attributed.Tests/Support/Extensions.cs new file mode 100644 index 0000000..dafb3ee --- /dev/null +++ b/src/Destructurama.Attributed.Tests/Support/Extensions.cs @@ -0,0 +1,9 @@ +using Serilog.Events; + +namespace Destructurama.Attributed.Tests.Support; + +public static class Extensions +{ + public static object? LiteralValue(this LogEventPropertyValue @this) => + ((ScalarValue)@this).Value; +} diff --git a/src/Destructurama.Attributed/Attributed/AttributedDestructuringPolicy.cs b/src/Destructurama.Attributed/Attributed/AttributedDestructuringPolicy.cs index 8e5b40c..e9ed789 100644 --- a/src/Destructurama.Attributed/Attributed/AttributedDestructuringPolicy.cs +++ b/src/Destructurama.Attributed/Attributed/AttributedDestructuringPolicy.cs @@ -12,133 +12,129 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reflection; using Destructurama.Util; using Serilog.Core; using Serilog.Debugging; using Serilog.Events; -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +internal class AttributedDestructuringPolicy : IDestructuringPolicy { - class AttributedDestructuringPolicy : IDestructuringPolicy + private static readonly ConcurrentDictionary _cache = new(); + private readonly AttributedDestructuringPolicyOptions _options; + + public AttributedDestructuringPolicy() { - readonly static ConcurrentDictionary _cache = new(); - private readonly AttributedDestructuringPolicyOptions _options; + _options = new AttributedDestructuringPolicyOptions(); + } - public AttributedDestructuringPolicy() - { - _options = new AttributedDestructuringPolicyOptions(); - } + public AttributedDestructuringPolicy(Action configure) + : this() + { + configure?.Invoke(_options); + } - public AttributedDestructuringPolicy(Action configure) - : this() - { - configure?.Invoke(_options); - } + public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventPropertyValue? result) + { + var cached = _cache.GetOrAdd(value.GetType(), CreateCacheEntry); + result = cached.DestructureFunc(value, propertyValueFactory); + return cached.CanDestructure; + } - public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventPropertyValue? result) + private CacheEntry CreateCacheEntry(Type type) + { + var ti = type.GetTypeInfo(); + var classDestructurer = ti.GetCustomAttribute(); + if (classDestructurer != null) + return new((o, f) => classDestructurer.CreateLogEventPropertyValue(o, f)); + + var properties = type.GetPropertiesRecursive().ToList(); + if (!_options.IgnoreNullProperties + && properties.All(pi => + pi.GetCustomAttribute() == null + && pi.GetCustomAttribute() == null)) { - var cached = _cache.GetOrAdd(value.GetType(), CreateCacheEntry); - result = cached.DestructureFunc(value, propertyValueFactory); - return cached.CanDestructure; + return CacheEntry.Ignore; } - private CacheEntry CreateCacheEntry(Type type) + var optionalIgnoreAttributes = properties + .Select(pi => new { pi, Attribute = pi.GetCustomAttribute() }) + .Where(o => o.Attribute != null) + .ToDictionary(o => o.pi, o => o.Attribute); + + var destructuringAttributes = properties + .Select(pi => new { pi, Attribute = pi.GetCustomAttribute() }) + .Where(o => o.Attribute != null) + .ToDictionary(o => o.pi, o => o.Attribute); + + if (_options.IgnoreNullProperties && !optionalIgnoreAttributes.Any() && !destructuringAttributes.Any()) { - var ti = type.GetTypeInfo(); - var classDestructurer = ti.GetCustomAttribute(); - if (classDestructurer != null) - return new((o, f) => classDestructurer.CreateLogEventPropertyValue(o, f)); - - var properties = type.GetPropertiesRecursive().ToList(); - if (!_options.IgnoreNullProperties - && properties.All(pi => - pi.GetCustomAttribute() == null - && pi.GetCustomAttribute() == null)) - { + if (typeof(IEnumerable).IsAssignableFrom(type)) return CacheEntry.Ignore; - } + } - var optionalIgnoreAttributes = properties - .Select(pi => new { pi, Attribute = pi.GetCustomAttribute() }) - .Where(o => o.Attribute != null) - .ToDictionary(o => o.pi, o => o.Attribute); + return new CacheEntry((o, f) => MakeStructure(o, properties, optionalIgnoreAttributes, destructuringAttributes, f, type)); + } - var destructuringAttributes = properties - .Select(pi => new { pi, Attribute = pi.GetCustomAttribute() }) - .Where(o => o.Attribute != null) - .ToDictionary(o => o.pi, o => o.Attribute); + private LogEventPropertyValue MakeStructure( + object o, + IEnumerable loggedProperties, + IDictionary optionalIgnoreAttributes, + IDictionary destructuringAttributes, + ILogEventPropertyValueFactory propertyValueFactory, + Type type) + { + var structureProperties = new List(); + foreach (var pi in loggedProperties) + { + var propValue = SafeGetPropValue(o, pi); - if (_options.IgnoreNullProperties && !optionalIgnoreAttributes.Any() && !destructuringAttributes.Any()) + if (optionalIgnoreAttributes.TryGetValue(pi, out var optionalIgnoreAttribute)) { - if (typeof(IEnumerable).IsAssignableFrom(type)) - return CacheEntry.Ignore; + if (optionalIgnoreAttribute.ShouldPropertyBeIgnored(pi.Name, propValue, pi.PropertyType)) + continue; } - return new CacheEntry((o, f) => MakeStructure(o, properties, optionalIgnoreAttributes, destructuringAttributes, f, type)); - } - - private LogEventPropertyValue MakeStructure( - object o, - IEnumerable loggedProperties, - IDictionary optionalIgnoreAttributes, - IDictionary destructuringAttributes, - ILogEventPropertyValueFactory propertyValueFactory, - Type type) - { - var structureProperties = new List(); - foreach (var pi in loggedProperties) + if (_options.IgnoreNullProperties) { - var propValue = SafeGetPropValue(o, pi); - - if (optionalIgnoreAttributes.TryGetValue(pi, out var optionalIgnoreAttribute)) - { - if (optionalIgnoreAttribute.ShouldPropertyBeIgnored(pi.Name, propValue, pi.PropertyType)) - continue; - } - - if (_options.IgnoreNullProperties) - { - if (NotLoggedIfNullAttribute.Instance.ShouldPropertyBeIgnored(pi.Name, propValue, pi.PropertyType)) - continue; - } - - if (destructuringAttributes.TryGetValue(pi, out var destructuringAttribute)) - { - if (destructuringAttribute.TryCreateLogEventProperty(pi.Name, propValue, propertyValueFactory, out var property)) - structureProperties.Add(property); - } - else - { - structureProperties.Add(new(pi.Name, propertyValueFactory.CreatePropertyValue(propValue, true))); - } + if (NotLoggedIfNullAttribute.Instance.ShouldPropertyBeIgnored(pi.Name, propValue, pi.PropertyType)) + continue; } - return new StructureValue(structureProperties, type.Name); - } - - static object SafeGetPropValue(object o, PropertyInfo pi) - { - try + if (destructuringAttributes.TryGetValue(pi, out var destructuringAttribute)) { - return pi.GetValue(o); + if (destructuringAttribute.TryCreateLogEventProperty(pi.Name, propValue, propertyValueFactory, out var property)) + structureProperties.Add(property); } - catch (TargetInvocationException ex) + else { - SelfLog.WriteLine("The property accessor {0} threw exception {1}", pi, ex); - return $"The property accessor threw an exception: {ex.InnerException!.GetType().Name}"; + structureProperties.Add(new(pi.Name, propertyValueFactory.CreatePropertyValue(propValue, true))); } } - internal static void Clear() + return new StructureValue(structureProperties, type.Name); + } + + private static object SafeGetPropValue(object o, PropertyInfo pi) + { + try + { + return pi.GetValue(o); + } + catch (TargetInvocationException ex) { - _cache.Clear(); + SelfLog.WriteLine("The property accessor {0} threw exception {1}", pi, ex); + return $"The property accessor threw an exception: {ex.InnerException!.GetType().Name}"; } } + + internal static void Clear() + { + _cache.Clear(); + } } diff --git a/src/Destructurama.Attributed/Attributed/AttributedDestructuringPolicyOptions.cs b/src/Destructurama.Attributed/Attributed/AttributedDestructuringPolicyOptions.cs index ff7f81c..9d0dcc9 100644 --- a/src/Destructurama.Attributed/Attributed/AttributedDestructuringPolicyOptions.cs +++ b/src/Destructurama.Attributed/Attributed/AttributedDestructuringPolicyOptions.cs @@ -1,15 +1,14 @@ -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +/// +/// Global destructuring options. +/// +public class AttributedDestructuringPolicyOptions { /// - /// Global destructuring options. + /// By setting IgnoreNullProperties to true no need to set [NotLoggedIfNull] for every logged property. + /// Custom types implementing IEnumerable, will be destructed as StructureValue and affected by IgnoreNullProperties + /// only in case at least one property (or the type itself) has Destructurama attribute applied. /// - public class AttributedDestructuringPolicyOptions - { - /// - /// By setting IgnoreNullProperties to true no need to set [NotLoggedIfNull] for every logged property. - /// Custom types implementing IEnumerable, will be destructed as StructureValue and affected by IgnoreNullProperties - /// only in case at least one property (or the type itself) has Destructurama attribute applied. - /// - public bool IgnoreNullProperties { get; set; } - } + public bool IgnoreNullProperties { get; set; } } diff --git a/src/Destructurama.Attributed/Attributed/IPropertyDestructuringAttribute.cs b/src/Destructurama.Attributed/Attributed/IPropertyDestructuringAttribute.cs index 1302bc5..292848e 100644 --- a/src/Destructurama.Attributed/Attributed/IPropertyDestructuringAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/IPropertyDestructuringAttribute.cs @@ -1,4 +1,4 @@ -// Copyright 2018 Destructurama Contributors, Serilog Contributors +// Copyright 2018 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,26 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +/// +/// Base interfaces for all s that override how a property is destructured. +/// +public interface IPropertyDestructuringAttribute { /// - /// Base interfaces for all s that override how a property is destructured. + /// Attempt to create a replacement for a property. /// - public interface IPropertyDestructuringAttribute - { - /// - /// Attempt to create a replacement for a property. - /// - /// The current property name. - /// The current property value - /// The current . - /// The to use as a replacement. - /// trueIf a replacement has been derived. - bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventProperty? property); - } -} \ No newline at end of file + /// The current property name. + /// The current property value + /// The current . + /// The to use as a replacement. + /// trueIf a replacement has been derived. + bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventProperty? property); +} diff --git a/src/Destructurama.Attributed/Attributed/IPropertyOptionalIgnoreAttribute.cs b/src/Destructurama.Attributed/Attributed/IPropertyOptionalIgnoreAttribute.cs index 3db9c66..c2e4bbe 100644 --- a/src/Destructurama.Attributed/Attributed/IPropertyOptionalIgnoreAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/IPropertyOptionalIgnoreAttribute.cs @@ -12,22 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; +namespace Destructurama.Attributed; -namespace Destructurama.Attributed +/// +/// Base interfaces for all s that determine should a property be ignored. +/// +public interface IPropertyOptionalIgnoreAttribute { /// - /// Base interfaces for all s that determine should a property be ignored. + /// Determine should a property be ignored /// - public interface IPropertyOptionalIgnoreAttribute - { - /// - /// Determine should a property be ignored - /// - /// The current property name - /// The current property value - /// The current property type - /// - bool ShouldPropertyBeIgnored(string name, object? value, Type type); - } + /// The current property name + /// The current property value + /// The current property type + /// + bool ShouldPropertyBeIgnored(string name, object? value, Type type); } diff --git a/src/Destructurama.Attributed/Attributed/ITypeDestructuringAttribute.cs b/src/Destructurama.Attributed/Attributed/ITypeDestructuringAttribute.cs index 40499bc..b3d86f9 100644 --- a/src/Destructurama.Attributed/Attributed/ITypeDestructuringAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/ITypeDestructuringAttribute.cs @@ -1,4 +1,4 @@ -// Copyright 2018 Destructurama Contributors, Serilog Contributors +// Copyright 2018 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,23 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using Serilog.Core; using Serilog.Events; -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +/// +/// Base interfaces for all s that override how a property type type is destructured. +/// +public interface ITypeDestructuringAttribute { /// - /// Base interfaces for all s that override how a property type type is destructured. + /// Attempt to create a replacement for a property. /// - public interface ITypeDestructuringAttribute - { - /// - /// Attempt to create a replacement for a property. - /// - /// The value of the property. - /// The current . - /// The new to use when logging the property. - LogEventPropertyValue CreateLogEventPropertyValue(object? value, ILogEventPropertyValueFactory propertyValueFactory); - } -} \ No newline at end of file + /// The value of the property. + /// The current . + /// The new to use when logging the property. + LogEventPropertyValue CreateLogEventPropertyValue(object? value, ILogEventPropertyValueFactory propertyValueFactory); +} diff --git a/src/Destructurama.Attributed/Attributed/LogAsScalarAttribute.cs b/src/Destructurama.Attributed/Attributed/LogAsScalarAttribute.cs index 118788c..5cfc010 100644 --- a/src/Destructurama.Attributed/Attributed/LogAsScalarAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/LogAsScalarAttribute.cs @@ -1,4 +1,4 @@ -// Copyright 2015 Destructurama Contributors, Serilog Contributors +// Copyright 2015 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,41 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using Serilog.Core; using Serilog.Events; -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +/// +/// Specified that the type or property it is applied to should never be +/// destructured; instead it should be logged as an atomic value. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] +public class LogAsScalarAttribute : Attribute, ITypeDestructuringAttribute, IPropertyDestructuringAttribute { + private readonly bool _isMutable; + /// - /// Specified that the type or property it is applied to should never be - /// destructured; instead it should be logged as an atomic value. + /// Construct a . /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] - public class LogAsScalarAttribute : Attribute, ITypeDestructuringAttribute, IPropertyDestructuringAttribute + /// Whether the scalar value should be converted into a string before + /// being passed down the (asynchronous) logging pipeline. For mutable types, specify + /// true, otherwise leave as false. + public LogAsScalarAttribute(bool isMutable = false) { - readonly bool _isMutable; - - /// - /// Construct a . - /// - /// Whether the scalar value should be converted into a string before - /// being passed down the (asynchronous) logging pipeline. For mutable types, specify - /// true, otherwise leave as false. - public LogAsScalarAttribute(bool isMutable = false) - { - _isMutable = isMutable; - } + _isMutable = isMutable; + } - /// - public LogEventPropertyValue CreateLogEventPropertyValue(object? value, ILogEventPropertyValueFactory propertyValueFactory) => - new ScalarValue(_isMutable ? value?.ToString() : value); + /// + public LogEventPropertyValue CreateLogEventPropertyValue(object? value, ILogEventPropertyValueFactory propertyValueFactory) => + new ScalarValue(_isMutable ? value?.ToString() : value); - /// - public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventProperty property) - { - property = new(name, CreateLogEventPropertyValue(value, propertyValueFactory)); - return true; - } + /// + public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventProperty property) + { + property = new(name, CreateLogEventPropertyValue(value, propertyValueFactory)); + return true; } } diff --git a/src/Destructurama.Attributed/Attributed/LogMaskedAttribute.cs b/src/Destructurama.Attributed/Attributed/LogMaskedAttribute.cs index 570dbb9..8676874 100644 --- a/src/Destructurama.Attributed/Attributed/LogMaskedAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/LogMaskedAttribute.cs @@ -1,4 +1,4 @@ -// Copyright 2015-2018 Destructurama Contributors, Serilog Contributors +// Copyright 2015-2018 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,117 +12,113 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.Collections.Generic; -using System.Linq; using Serilog.Core; using Serilog.Events; -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +/// +/// Apply to a property to apply a mask to the logged value. +/// +[AttributeUsage(AttributeTargets.Property)] +public class LogMaskedAttribute : Attribute, IPropertyDestructuringAttribute { + private const string DEFAULT_MASK = "***"; + /// - /// Apply to a property to apply a mask to the logged value. + /// If set, the property value will be set to this text. /// - [AttributeUsage(AttributeTargets.Property)] - public class LogMaskedAttribute : Attribute, IPropertyDestructuringAttribute - { - const string DefaultMask = "***"; - - /// - /// If set, the property value will be set to this text. - /// - public string Text { get; set; } = DefaultMask; - /// - /// Shows the first x characters in the property value. - /// - public int ShowFirst { get; set; } - /// - /// Shows the last x characters in the property value. - /// - public int ShowLast { get; set; } - /// - /// If set, it will swap out each character with the default value. Note that this - /// property will be ignored if has been set to custom value. - /// - public bool PreserveLength { get; set; } - - private bool IsDefaultMask() => Text == DefaultMask; - - private object FormatMaskedValue(string val) - { - if (string.IsNullOrEmpty(val)) - return PreserveLength ? val : Text; + public string Text { get; set; } = DEFAULT_MASK; + /// + /// Shows the first x characters in the property value. + /// + public int ShowFirst { get; set; } + /// + /// Shows the last x characters in the property value. + /// + public int ShowLast { get; set; } + /// + /// If set, it will swap out each character with the default value. Note that this + /// property will be ignored if has been set to custom value. + /// + public bool PreserveLength { get; set; } - if (ShowFirst == 0 && ShowLast == 0) - { - if (PreserveLength) - return new string(Text[0], val.Length); + private bool IsDefaultMask() => Text == DEFAULT_MASK; - return Text; - } + private object FormatMaskedValue(string val) + { + if (string.IsNullOrEmpty(val)) + return PreserveLength ? val : Text; - if (ShowFirst > 0 && ShowLast == 0) - { - var first = val.Substring(0, Math.Min(ShowFirst, val.Length)); + if (ShowFirst == 0 && ShowLast == 0) + { + if (PreserveLength) + return new string(Text[0], val.Length); - if (!PreserveLength || !IsDefaultMask()) - return first + Text; + return Text; + } - var mask = ""; - if (ShowFirst <= val.Length) - mask = new(Text[0], val.Length - ShowFirst); + if (ShowFirst > 0 && ShowLast == 0) + { + var first = val.Substring(0, Math.Min(ShowFirst, val.Length)); - return first + mask; + if (!PreserveLength || !IsDefaultMask()) + return first + Text; - } + var mask = ""; + if (ShowFirst <= val.Length) + mask = new(Text[0], val.Length - ShowFirst); - if (ShowFirst == 0 && ShowLast > 0) - { - var last = ShowLast > val.Length ? val : val.Substring(val.Length - ShowLast); + return first + mask; - if (!PreserveLength || !IsDefaultMask()) - return Text + last; + } - var mask = ""; - if (ShowLast <= val.Length) - mask = new(Text[0], val.Length - ShowLast); + if (ShowFirst == 0 && ShowLast > 0) + { + var last = ShowLast > val.Length ? val : val.Substring(val.Length - ShowLast); - return mask + last; - } + if (!PreserveLength || !IsDefaultMask()) + return Text + last; - if (ShowFirst > 0 && ShowLast > 0) - { - if (ShowFirst + ShowLast >= val.Length) - return val; + var mask = ""; + if (ShowLast <= val.Length) + mask = new(Text[0], val.Length - ShowLast); - var first = val.Substring(0, ShowFirst); - var last = val.Substring(val.Length - ShowLast); + return mask + last; + } - string? mask = null; - if (PreserveLength && IsDefaultMask()) - mask = new string(Text[0], val.Length - ShowFirst - ShowLast); + if (ShowFirst > 0 && ShowLast > 0) + { + if (ShowFirst + ShowLast >= val.Length) + return val; - return first + (mask ?? Text) + last; - } + var first = val.Substring(0, ShowFirst); + var last = val.Substring(val.Length - ShowLast); - return val; - } + string? mask = null; + if (PreserveLength && IsDefaultMask()) + mask = new string(Text[0], val.Length - ShowFirst - ShowLast); - /// - public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventProperty property) - { - property = new LogEventProperty(name, CreateValue(value)); - return true; + return first + (mask ?? Text) + last; } - private LogEventPropertyValue CreateValue(object? value) + return val; + } + + /// + public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventProperty property) + { + property = new LogEventProperty(name, CreateValue(value)); + return true; + } + + private LogEventPropertyValue CreateValue(object? value) + { + return value switch { - return value switch - { - IEnumerable strings => new SequenceValue(strings.Select(s => new ScalarValue(FormatMaskedValue(s)))), - string s => new ScalarValue(FormatMaskedValue(s)), - _ => new ScalarValue(null) - }; - } + IEnumerable strings => new SequenceValue(strings.Select(s => new ScalarValue(FormatMaskedValue(s)))), + string s => new ScalarValue(FormatMaskedValue(s)), + _ => new ScalarValue(null) + }; } } diff --git a/src/Destructurama.Attributed/Attributed/LogReplacedAttribute.cs b/src/Destructurama.Attributed/Attributed/LogReplacedAttribute.cs index 645a03f..3753754 100644 --- a/src/Destructurama.Attributed/Attributed/LogReplacedAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/LogReplacedAttribute.cs @@ -1,4 +1,4 @@ -// Copyright 2015-2020 Destructurama Contributors, Serilog Contributors +// Copyright 2015-2020 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,63 +12,61 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Serilog.Core; using Serilog.Events; -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +/// +/// Apply to a property to use a replace the current value. +/// +[AttributeUsage(AttributeTargets.Property)] +public class LogReplacedAttribute : Attribute, IPropertyDestructuringAttribute { + private readonly string _pattern; + private readonly string _replacement; + /// - /// Apply to a property to use a replace the current value. + /// The RegexOptions that will be applied. Defaults to /// - [AttributeUsage(AttributeTargets.Property)] - public class LogReplacedAttribute : Attribute, IPropertyDestructuringAttribute - { - readonly string _pattern; - readonly string _replacement; + public RegexOptions Options { get; set; } - /// - /// The RegexOptions that will be applied. Defaults to - /// - public RegexOptions Options { get; set; } + /// + /// A time-out interval to evaluate regular expression. Defaults to + /// + public TimeSpan Timeout { get; set; } = Regex.InfiniteMatchTimeout; - /// - /// A time-out interval to evaluate regular expression. Defaults to - /// - public TimeSpan Timeout { get; set; } = Regex.InfiniteMatchTimeout; + /// + /// Construct a . + /// + /// The pattern that should be applied on value. + /// The pattern that should be applied on value. + public LogReplacedAttribute(string pattern, string replacement) + { + _pattern = pattern; + _replacement = replacement; + } - /// - /// Construct a . - /// - /// The pattern that should be applied on value. - /// The pattern that should be applied on value. - public LogReplacedAttribute(string pattern, string replacement) + /// + public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventProperty? property) + { + if (value == null) { - _pattern = pattern; - _replacement = replacement; + property = new(name, new ScalarValue(value)); + return true; } - /// - public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventProperty? property) + if (value is string s) { - if (value == null) - { - property = new(name, new ScalarValue(value)); - return true; - } + var replacement = Regex.Replace(s, _pattern, _replacement, Options, Timeout); - if (value is string s) - { - var replacement = Regex.Replace(s, _pattern, _replacement, Options, Timeout); - - property = new(name, new ScalarValue(replacement)); - return true; - } - - property = null; - return false; + property = new(name, new ScalarValue(replacement)); + return true; } + + property = null; + return false; } } diff --git a/src/Destructurama.Attributed/Attributed/LogWithNameAttribute.cs b/src/Destructurama.Attributed/Attributed/LogWithNameAttribute.cs index 85fabf0..18cebed 100644 --- a/src/Destructurama.Attributed/Attributed/LogWithNameAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/LogWithNameAttribute.cs @@ -1,4 +1,4 @@ -// Copyright 2015-2018 Destructurama Contributors, Serilog Contributors +// Copyright 2015-2018 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,52 +12,50 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; -using System; -using System.Diagnostics.CodeAnalysis; -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +/// +/// Apply to a property to use a custom name when that property is logged. +/// +[AttributeUsage(AttributeTargets.Property)] +public class LogWithNameAttribute : Attribute, IPropertyDestructuringAttribute { + private readonly string _newName; + /// - /// Apply to a property to use a custom name when that property is logged. + /// Construct a . /// - [AttributeUsage(AttributeTargets.Property)] - public class LogWithNameAttribute : Attribute, IPropertyDestructuringAttribute + /// The new name to use when logging the target property. + public LogWithNameAttribute(string newName) { - private string _newName; + _newName = newName; + } - /// - /// Construct a . - /// - /// The new name to use when logging the target property. - public LogWithNameAttribute(string newName) + /// + public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventProperty? property) + { + var propValue = propertyValueFactory.CreatePropertyValue(value); + + LogEventPropertyValue? logEventPropVal = propValue switch { - _newName = newName; - } + ScalarValue scalar => new ScalarValue(scalar.Value), + DictionaryValue dictionary => new DictionaryValue(dictionary.Elements), + SequenceValue sequence => new SequenceValue(sequence.Elements), + StructureValue structure => new StructureValue(structure.Properties), + _ => null + }; - /// - public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventProperty? property) + if (logEventPropVal is null) { - var propValue = propertyValueFactory.CreatePropertyValue(value); - - LogEventPropertyValue? logEventPropVal = propValue switch - { - ScalarValue scalar => new ScalarValue(scalar.Value), - DictionaryValue dictionary => new DictionaryValue(dictionary.Elements), - SequenceValue sequence => new SequenceValue(sequence.Elements), - StructureValue structure => new StructureValue(structure.Properties), - _ => null - }; - - if (logEventPropVal is null) - { - property = null; - return false; - } - - property = new LogEventProperty(_newName, logEventPropVal); - return true; + property = null; + return false; } + + property = new LogEventProperty(_newName, logEventPropVal); + return true; } -} \ No newline at end of file +} diff --git a/src/Destructurama.Attributed/Attributed/NotLoggedAttribute.cs b/src/Destructurama.Attributed/Attributed/NotLoggedAttribute.cs index d0439b2..8a3f830 100644 --- a/src/Destructurama.Attributed/Attributed/NotLoggedAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/NotLoggedAttribute.cs @@ -1,4 +1,4 @@ -// Copyright 2015 Destructurama Contributors, Serilog Contributors +// Copyright 2015 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,24 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +/// +/// Specified that a property should not be included when destructuring an object for logging. +/// +[AttributeUsage(AttributeTargets.Property)] +public class NotLoggedAttribute : Attribute, IPropertyDestructuringAttribute { - /// - /// Specified that a property should not be included when destructuring an object for logging. - /// - [AttributeUsage(AttributeTargets.Property)] - public class NotLoggedAttribute : Attribute, IPropertyDestructuringAttribute + /// + public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventProperty? property) { - /// - public bool TryCreateLogEventProperty(string name, object? value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventProperty? property) - { - property = null; - return false; - } + property = null; + return false; } -} \ No newline at end of file +} diff --git a/src/Destructurama.Attributed/Attributed/NotLoggedIfDefaultAttribute.cs b/src/Destructurama.Attributed/Attributed/NotLoggedIfDefaultAttribute.cs index c944c36..71751cb 100644 --- a/src/Destructurama.Attributed/Attributed/NotLoggedIfDefaultAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/NotLoggedIfDefaultAttribute.cs @@ -1,76 +1,74 @@ // Copyright 2020 Destructurama Contributors, Serilog Contributors -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Collections.Concurrent; -namespace Destructurama.Attributed +namespace Destructurama.Attributed; + +internal abstract class CachedValue +{ + public abstract bool IsDefaultValue(object value); +} + +internal class CachedValue : CachedValue where T : notnull { - abstract class CachedValue + private T Value { get; set; } + + public CachedValue(T value) { - public abstract bool IsDefaultValue(object value); + Value = value; } - class CachedValue : CachedValue where T: notnull + public override bool IsDefaultValue(object value) { - T Value { get; set; } - - public CachedValue(T value) - { - Value = value; - } - - public override bool IsDefaultValue(object value) - { - return Value.Equals(value); - } + return Value.Equals(value); } +} - /// - /// Specified that a property with default value for its type should not be included when destructuring an object for logging. - /// - [AttributeUsage(AttributeTargets.Property)] - public class NotLoggedIfDefaultAttribute : Attribute, IPropertyOptionalIgnoreAttribute - { - readonly static ConcurrentDictionary _cache = new(); +/// +/// Specified that a property with default value for its type should not be included when destructuring an object for logging. +/// +[AttributeUsage(AttributeTargets.Property)] +public class NotLoggedIfDefaultAttribute : Attribute, IPropertyOptionalIgnoreAttribute +{ + private static readonly ConcurrentDictionary _cache = new(); - bool IPropertyOptionalIgnoreAttribute.ShouldPropertyBeIgnored(string name, object? value, Type type) + bool IPropertyOptionalIgnoreAttribute.ShouldPropertyBeIgnored(string name, object? value, Type type) + { + if (value != null) { - if (value != null) - { - if (type.IsValueType) + if (type.IsValueType) + { + if (!_cache.TryGetValue(type, out CachedValue cachedValue)) { - if (!_cache.TryGetValue(type, out CachedValue cachedValue)) - { - var cachedValueType = typeof(CachedValue<>).MakeGenericType(type); - var defaultValue = Activator.CreateInstance(type); - cachedValue = (CachedValue)Activator.CreateInstance(cachedValueType, defaultValue); - - _cache.TryAdd(type, cachedValue); - } + var cachedValueType = typeof(CachedValue<>).MakeGenericType(type); + var defaultValue = Activator.CreateInstance(type); + cachedValue = (CachedValue)Activator.CreateInstance(cachedValueType, defaultValue); - if (cachedValue.IsDefaultValue(value)) - { - return true; - } + _cache.TryAdd(type, cachedValue); } - return false; + if (cachedValue.IsDefaultValue(value)) + { + return true; + } } - return true; + return false; } + + return true; } } diff --git a/src/Destructurama.Attributed/Attributed/NotLoggedIfNullAttribute.cs b/src/Destructurama.Attributed/Attributed/NotLoggedIfNullAttribute.cs index 7e21e51..7cccd54 100644 --- a/src/Destructurama.Attributed/Attributed/NotLoggedIfNullAttribute.cs +++ b/src/Destructurama.Attributed/Attributed/NotLoggedIfNullAttribute.cs @@ -12,19 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; +namespace Destructurama.Attributed; -namespace Destructurama.Attributed +/// +/// Specified that a property with null value should not be included when destructuring an object for logging. +/// +[AttributeUsage(AttributeTargets.Property)] +public class NotLoggedIfNullAttribute : Attribute, IPropertyOptionalIgnoreAttribute { - /// - /// Specified that a property with null value should not be included when destructuring an object for logging. - /// - [AttributeUsage(AttributeTargets.Property)] - public class NotLoggedIfNullAttribute : Attribute, IPropertyOptionalIgnoreAttribute - { - internal static readonly IPropertyOptionalIgnoreAttribute Instance = new NotLoggedIfNullAttribute(); + internal static readonly IPropertyOptionalIgnoreAttribute Instance = new NotLoggedIfNullAttribute(); - bool IPropertyOptionalIgnoreAttribute.ShouldPropertyBeIgnored(string name, object? value, Type type) - => value == null; - } + bool IPropertyOptionalIgnoreAttribute.ShouldPropertyBeIgnored(string name, object? value, Type type) + => value == null; } diff --git a/src/Destructurama.Attributed/Destructurama.Attributed.csproj b/src/Destructurama.Attributed/Destructurama.Attributed.csproj index 0eb5fcf..67453a2 100644 --- a/src/Destructurama.Attributed/Destructurama.Attributed.csproj +++ b/src/Destructurama.Attributed/Destructurama.Attributed.csproj @@ -1,25 +1,16 @@ - + netstandard2.0 - 3.1.0 - Destructurama - True - Serilog Contributors Use attributes to control how complex types are logged to Serilog. - https://github.com/destructurama - Apache-2.0 - icon.png - serilog;attributed - true - true + Destructurama + true - - - + + diff --git a/src/Destructurama.Attributed/LoggerConfigurationAttributedExtensions.cs b/src/Destructurama.Attributed/LoggerConfigurationAttributedExtensions.cs index 6d9ba49..c460ebc 100644 --- a/src/Destructurama.Attributed/LoggerConfigurationAttributedExtensions.cs +++ b/src/Destructurama.Attributed/LoggerConfigurationAttributedExtensions.cs @@ -15,39 +15,34 @@ using Destructurama.Attributed; using Serilog; using Serilog.Configuration; -using System; using Serilog.Core; -using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Destructurama.Attributed.Tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100638a43140e8a1271c1453df1379e64b40b67a1f333864c1aef5ac318a0fa2008545c3d35a82ef005edf0de1ad1e1ea155722fe289df0e462f78c40a668cbc96d7be1d487faef5714a54bb4e57909c86b3924c2db6d55ccf59939b99eb0cab6e8a91429ba0ce630c08a319b323bddcbbd509f1afe4ae77a6cbb8b447f588febc3")] +namespace Destructurama; -namespace Destructurama +/// +/// Adds the Destructure.UsingAttributes() extension to . +/// +public static class LoggerConfigurationAppSettingsExtensions { /// - /// Adds the Destructure.UsingAttributes() extension to . + /// Adds a custom to enable manipulation of how objects + /// are logged to Serilog using attributes. /// - public static class LoggerConfigurationAppSettingsExtensions - { - /// - /// Adds a custom to enable manipulation of how objects - /// are logged to Serilog using attributes. - /// - /// The logger configuration to apply configuration to. - /// An object allowing configuration to continue. - public static LoggerConfiguration UsingAttributes(this LoggerDestructuringConfiguration configuration) => - configuration.With(); + /// The logger configuration to apply configuration to. + /// An object allowing configuration to continue. + public static LoggerConfiguration UsingAttributes(this LoggerDestructuringConfiguration configuration) => + configuration.With(); - /// - /// - /// The logger configuration to apply configuration to. - /// Configure Destructurama options - /// An object allowing configuration to continue. - public static LoggerConfiguration UsingAttributes(this LoggerDestructuringConfiguration configuration, - Action configure) - { - var policy = new AttributedDestructuringPolicy(configure); - return configuration.With(policy); - } + /// + /// + /// The logger configuration to apply configuration to. + /// Configure Destructurama options + /// An object allowing configuration to continue. + public static LoggerConfiguration UsingAttributes(this LoggerDestructuringConfiguration configuration, + Action configure) + { + var policy = new AttributedDestructuringPolicy(configure); + return configuration.With(policy); } } diff --git a/src/Destructurama.Attributed/Util/AttributeFinder.cs b/src/Destructurama.Attributed/Util/AttributeFinder.cs index 903e4d0..a0182d0 100644 --- a/src/Destructurama.Attributed/Util/AttributeFinder.cs +++ b/src/Destructurama.Attributed/Util/AttributeFinder.cs @@ -1,4 +1,4 @@ -// Copyright 2018 Destructurama Contributors, Serilog Contributors +// Copyright 2018 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,17 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Linq; using System.Reflection; -namespace Destructurama.Util +namespace Destructurama.Util; + +internal static class AttributeFinder { - static class AttributeFinder - { - public static T GetCustomAttribute(this TypeInfo typeInfo) => - typeInfo.GetCustomAttributes().OfType().FirstOrDefault(); + public static T GetCustomAttribute(this TypeInfo typeInfo) => + typeInfo.GetCustomAttributes().OfType().FirstOrDefault(); - public static T GetCustomAttribute(this PropertyInfo propertyInfo) => - propertyInfo.GetCustomAttributes().OfType().FirstOrDefault(); - } + public static T GetCustomAttribute(this PropertyInfo propertyInfo) => + propertyInfo.GetCustomAttributes().OfType().FirstOrDefault(); } diff --git a/src/Destructurama.Attributed/Util/CacheEntry.cs b/src/Destructurama.Attributed/Util/CacheEntry.cs index d79075b..e187b4d 100644 --- a/src/Destructurama.Attributed/Util/CacheEntry.cs +++ b/src/Destructurama.Attributed/Util/CacheEntry.cs @@ -1,4 +1,4 @@ -// Copyright 2015 Destructurama Contributors, Serilog Contributors +// Copyright 2015 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,30 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using Serilog.Core; using Serilog.Events; -namespace Destructurama.Util +namespace Destructurama.Util; + +internal struct CacheEntry { - struct CacheEntry + public CacheEntry(Func destructureFunc) { - public CacheEntry(Func destructureFunc) - { - CanDestructure = true; - DestructureFunc = destructureFunc ?? throw new ArgumentNullException(nameof(destructureFunc)); - } + CanDestructure = true; + DestructureFunc = destructureFunc ?? throw new ArgumentNullException(nameof(destructureFunc)); + } - CacheEntry(bool canDestructure, Func destructureFunc) - { - CanDestructure = canDestructure; - DestructureFunc = destructureFunc ?? throw new ArgumentNullException(nameof(destructureFunc)); - } + private CacheEntry(bool canDestructure, Func destructureFunc) + { + CanDestructure = canDestructure; + DestructureFunc = destructureFunc ?? throw new ArgumentNullException(nameof(destructureFunc)); + } - public bool CanDestructure { get; } + public bool CanDestructure { get; } - public Func DestructureFunc { get; } + public Func DestructureFunc { get; } - public static CacheEntry Ignore { get; } = new(false, (_, _) => null); - } -} \ No newline at end of file + public static CacheEntry Ignore { get; } = new(false, (_, _) => null); +} diff --git a/src/Destructurama.Attributed/Util/GetablePropertyFinder.cs b/src/Destructurama.Attributed/Util/GetablePropertyFinder.cs index 58efc1d..4e80a77 100644 --- a/src/Destructurama.Attributed/Util/GetablePropertyFinder.cs +++ b/src/Destructurama.Attributed/Util/GetablePropertyFinder.cs @@ -1,4 +1,4 @@ -// Copyright 2015 Destructurama Contributors, Serilog Contributors +// Copyright 2015 Destructurama Contributors, Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,38 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -namespace Destructurama.Util +namespace Destructurama.Util; + +internal static class GetablePropertyFinder { - static class GetablePropertyFinder + public static IEnumerable GetPropertiesRecursive(this Type type) { - public static IEnumerable GetPropertiesRecursive(this Type type) - { - var seenNames = new HashSet(); + var seenNames = new HashSet(); - var currentTypeInfo = type.GetTypeInfo(); - - while (currentTypeInfo.AsType() != typeof(object)) - { - var unseenProperties = currentTypeInfo.DeclaredProperties - .Where(p => p.CanRead && - p.GetMethod.IsPublic && - !p.GetMethod.IsStatic && - (p.Name != "Item" || p.GetIndexParameters().Length == 0) && - !seenNames.Contains(p.Name)); + var currentTypeInfo = type.GetTypeInfo(); - foreach (var propertyInfo in unseenProperties) - { - seenNames.Add(propertyInfo.Name); - yield return propertyInfo; - } + while (currentTypeInfo.AsType() != typeof(object)) + { + var unseenProperties = currentTypeInfo.DeclaredProperties + .Where(p => p.CanRead && + p.GetMethod.IsPublic && + !p.GetMethod.IsStatic && + (p.Name != "Item" || p.GetIndexParameters().Length == 0) && + !seenNames.Contains(p.Name)); - currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo(); + foreach (var propertyInfo in unseenProperties) + { + seenNames.Add(propertyInfo.Name); + yield return propertyInfo; } + + currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo(); } } -} \ No newline at end of file +} diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..6f5c415 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,38 @@ + + + + latest + true + true + true + $([System.DateTime]::Now.ToString(yyyy)) + Destructurama Contributors, Serilog Contributors + Copyright © Serilog Contributors 2017-$(CurrentYear) + Apache-2.0 + false + icon.png + git + embedded + true + true + + True + serilog;attributed + enable + README.md + true + enable + true + ../../assets/Destructurama.snk + + ../../../../../../assets/Destructurama.snk + + + + + + + + + + diff --git a/src/destructurama-attributed.sln b/src/destructurama-attributed.sln new file mode 100644 index 0000000..9430b87 --- /dev/null +++ b/src/destructurama-attributed.sln @@ -0,0 +1,79 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{446E3BA7-97CF-43CF-B708-78C51EB45FC9}" + ProjectSection(SolutionItems) = preProject + ..\.editorconfig = ..\.editorconfig + ..\.gitattributes = ..\.gitattributes + ..\.gitignore = ..\.gitignore + ..\CHANGES.md = ..\CHANGES.md + ..\assets\Destructurama.snk = ..\assets\Destructurama.snk + Directory.Build.props = Directory.Build.props + ..\assets\icon.png = ..\assets\icon.png + ..\LICENSE = ..\LICENSE + ..\mdsnippets.json = ..\mdsnippets.json + ..\README.md = ..\README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{36896219-827B-40F7-B74E-FA02C5454361}" + ProjectSection(SolutionItems) = preProject + ..\.github\codecov.yml = ..\.github\codecov.yml + ..\.github\dependabot.yml = ..\.github\dependabot.yml + ..\.github\labeler.yml = ..\.github\labeler.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{5E865875-D5D8-4308-86B2-8035BDDB7833}" + ProjectSection(SolutionItems) = preProject + ..\.github\ISSUE_TEMPLATE\bug_report.md = ..\.github\ISSUE_TEMPLATE\bug_report.md + ..\.github\ISSUE_TEMPLATE\feature_request.md = ..\.github\ISSUE_TEMPLATE\feature_request.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{14AB42BE-7411-4195-B734-EAB658BE1ED1}" + ProjectSection(SolutionItems) = preProject + ..\.github\workflows\benchmarks.yml = ..\.github\workflows\benchmarks.yml + ..\.github\workflows\codeql-analysis.yml = ..\.github\workflows\codeql-analysis.yml + ..\.github\workflows\label.yml = ..\.github\workflows\label.yml + ..\.github\workflows\publish-preview.yml = ..\.github\workflows\publish-preview.yml + ..\.github\workflows\publish-release.yml = ..\.github\workflows\publish-release.yml + ..\.github\workflows\stale.yml = ..\.github\workflows\stale.yml + ..\.github\workflows\test.yml = ..\.github\workflows\test.yml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Destructurama.Attributed", "Destructurama.Attributed\Destructurama.Attributed.csproj", "{1F52A6F8-BE51-4648-9EDB-ADEC23E1C1C8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Destructurama.Attributed.Tests", "Destructurama.Attributed.Tests\Destructurama.Attributed.Tests.csproj", "{293201F5-761A-47AC-B6B0-98734CA6B4A2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{27515D99-8216-423E-B12A-0150101A2C19}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1F52A6F8-BE51-4648-9EDB-ADEC23E1C1C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F52A6F8-BE51-4648-9EDB-ADEC23E1C1C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F52A6F8-BE51-4648-9EDB-ADEC23E1C1C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F52A6F8-BE51-4648-9EDB-ADEC23E1C1C8}.Release|Any CPU.Build.0 = Release|Any CPU + {293201F5-761A-47AC-B6B0-98734CA6B4A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {293201F5-761A-47AC-B6B0-98734CA6B4A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {293201F5-761A-47AC-B6B0-98734CA6B4A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {293201F5-761A-47AC-B6B0-98734CA6B4A2}.Release|Any CPU.Build.0 = Release|Any CPU + {27515D99-8216-423E-B12A-0150101A2C19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27515D99-8216-423E-B12A-0150101A2C19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27515D99-8216-423E-B12A-0150101A2C19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27515D99-8216-423E-B12A-0150101A2C19}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5E865875-D5D8-4308-86B2-8035BDDB7833} = {36896219-827B-40F7-B74E-FA02C5454361} + {14AB42BE-7411-4195-B734-EAB658BE1ED1} = {36896219-827B-40F7-B74E-FA02C5454361} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F09D8B7B-37C6-417E-B8DA-296735211A4E} + EndGlobalSection +EndGlobal diff --git a/test/Destructurama.Attributed.Tests/AttributedDestructuringTests.cs b/test/Destructurama.Attributed.Tests/AttributedDestructuringTests.cs deleted file mode 100644 index 900a413..0000000 --- a/test/Destructurama.Attributed.Tests/AttributedDestructuringTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Linq; -using Destructurama.Attributed.Tests.Support; -using NUnit.Framework; -using Serilog; -using Serilog.Events; - -namespace Destructurama.Attributed.Tests -{ - [TestFixture] - public class AttributedDestructuringTests - { - [Test] - public void AttributesAreConsultedWhenDestructuring() - { - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new Customized - { - ImmutableScalar = new(), - MutableScalar = new(), - NotAScalar = new(), - Ignored = "Hello, there", - ScalarAnyway = new(), - AuthData = new() - { - Username = "This is a username", - Password = "This is a password" - } - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsInstanceOf(props["ImmutableScalar"].LiteralValue()); - Assert.AreEqual(new MutableScalar().ToString(), props["MutableScalar"].LiteralValue()); - Assert.IsInstanceOf(props["NotAScalar"]); - Assert.IsFalse(props.ContainsKey("Ignored")); - Assert.IsInstanceOf(props["ScalarAnyway"].LiteralValue()); - - var str = sv.ToString(); - Assert.That(str.Contains("This is a username")); - Assert.False(str.Contains("This is a password")); - } - - [LogAsScalar] - public class ImmutableScalar - { - } - - [LogAsScalar(isMutable: true)] - public class MutableScalar - { - } - - public class NotAScalar - { - } - - public class Customized - { - // ReSharper disable UnusedAutoPropertyAccessor.Global - public ImmutableScalar? ImmutableScalar { get; set; } - public MutableScalar? MutableScalar { get; set; } - public NotAScalar? NotAScalar { get; set; } - - [NotLogged] public string? Ignored { get; set; } - - [LogAsScalar] public NotAScalar? ScalarAnyway { get; set; } - - public UserAuthData? AuthData { get; set; } - } - - public class UserAuthData - { - public string? Username { get; set; } - - [NotLogged] public string? Password { get; set; } - } - - } -} diff --git a/test/Destructurama.Attributed.Tests/IgnoreNullPropertiesTests.cs b/test/Destructurama.Attributed.Tests/IgnoreNullPropertiesTests.cs deleted file mode 100644 index 402497c..0000000 --- a/test/Destructurama.Attributed.Tests/IgnoreNullPropertiesTests.cs +++ /dev/null @@ -1,522 +0,0 @@ -using Destructurama.Attributed.Tests.Support; -using NUnit.Framework; -using Serilog; -using Serilog.Events; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Destructurama.Attributed.Tests -{ - [TestFixture] - public class IgnoreNullPropertiesTests - { - struct NotLoggedIfNullStruct - { - public int Integer { get; set; } - - public int? NullableInteger { get; set; } - - public DateTime DateTime { get; set; } - - public DateTime? NullableDateTime { get; set; } - - public object? Object { get; set; } - } - - class NotLoggedIfNull - { - public string? String { get; set; } - - public int Integer { get; set; } - - public int? NullableInteger { get; set; } - - public object? Object { get; set; } - - public object? IntegerAsObject { get; set; } - - public DateTime DateTime { get; set; } - - public DateTime? NullableDateTime { get; set; } - - public NotLoggedIfNullStruct Struct { get; set; } - - public NotLoggedIfNullStruct? NullableStruct { get; set; } - - public NotLoggedIfNullStruct StructPartiallyInitialized { get; set; } - } - - class NotLoggedIfNullAttributed - { - [NotLoggedIfNull] - public string? String { get; set; } - - [NotLoggedIfNull] - public int Integer { get; set; } - - [NotLoggedIfNull] - public int? NullableInteger { get; set; } - - [NotLoggedIfNull] - public object? Object { get; set; } - - [NotLoggedIfNull] - public object? IntegerAsObject { get; set; } - - [NotLoggedIfNull] - public DateTime DateTime { get; set; } - - [NotLoggedIfNull] - public DateTime? NullableDateTime { get; set; } - - [NotLoggedIfNull] - public NotLoggedIfNullStruct Struct { get; set; } - - [NotLoggedIfNull] - public NotLoggedIfNullStruct? NullableStruct { get; set; } - - [NotLoggedIfNull] - public NotLoggedIfNullStruct StructPartiallyInitialized { get; set; } - } - - class AttributedWithMask - { - [LogMasked(ShowFirst = 3)] - public string? String { get; set; } - - [LogMasked(ShowFirst = 3)] - public object? Object { get; set; } - } - - class Dependency - { - public int Integer { get; set; } - - public int? NullableInteger { get; set; } - } - - class CustomEnumerableDestructionIgnored : IEnumerable - { - public int Integer { get; set; } - - public Dependency? Dependency { get; set; } - - public IEnumerator GetEnumerator() - { - yield return 1; - } - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - } - - /// - /// At least one attribute from Destructurma.Attributed is enough to ignore all default properties on IEnumerable, - /// when IgnoreNullProperties is true. - /// - class CustomEnumerableAttributed : IEnumerable - { - [NotLogged] - public bool Dummy { get; set; } - - public int Integer { get; set; } - - public int? NullableInteger { get; set; } - - public Dependency? Dependency { get; set; } - - public IEnumerator GetEnumerator() - { - yield return 1; - } - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - } - - [SetUp] - public void SetUp() - { - AttributedDestructuringPolicy.Clear(); - } - - [TearDown] - public void TearDown() - { - AttributedDestructuringPolicy.Clear(); - } - - [Test] - public void NotLoggedIfNull_Uninitialized() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new NotLoggedIfNull(); - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt!.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("Integer")); - Assert.IsTrue(props.ContainsKey("DateTime")); - Assert.IsTrue(props.ContainsKey("Struct")); - Assert.IsTrue(props.ContainsKey("StructPartiallyInitialized")); - - Assert.IsFalse(props.ContainsKey("String")); - Assert.IsFalse(props.ContainsKey("NullableInteger")); - Assert.IsFalse(props.ContainsKey("IntegerAsObject")); - Assert.IsFalse(props.ContainsKey("Object")); - Assert.IsFalse(props.ContainsKey("NullableDateTime")); - Assert.IsFalse(props.ContainsKey("NullableStruct")); - } - - [Test] - public void NotLoggedIfNull_Initialized() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var dateTime = DateTime.UtcNow; - var theStruct = new NotLoggedIfNullStruct - { - Integer = 20, - NullableInteger = 15, - DateTime = dateTime, - NullableDateTime = dateTime, - Object = "Bar", - }; - - var theStructPartiallyUnitialized = new NotLoggedIfNullStruct - { - Integer = 20, - NullableInteger = 15, - DateTime = dateTime, - NullableDateTime = dateTime, - Object = null, - }; - - var customized = new NotLoggedIfNull - { - String = "Foo", - Integer = 10, - NullableInteger = 5, - Object = "Bar", - IntegerAsObject = 0, - DateTime = dateTime, - NullableDateTime = dateTime, - Struct = theStruct, - NullableStruct = theStruct, - StructPartiallyInitialized = theStructPartiallyUnitialized, - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt!.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("String")); - Assert.IsTrue(props.ContainsKey("Integer")); - Assert.IsTrue(props.ContainsKey("NullableInteger")); - Assert.IsTrue(props.ContainsKey("Object")); - Assert.IsTrue(props.ContainsKey("IntegerAsObject")); - Assert.IsTrue(props.ContainsKey("DateTime")); - Assert.IsTrue(props.ContainsKey("NullableDateTime")); - Assert.IsTrue(props.ContainsKey("Struct")); - Assert.IsTrue(props.ContainsKey("NullableStruct")); - Assert.IsTrue(props.ContainsKey("StructPartiallyInitialized")); - - Assert.AreEqual("Foo", props["String"].LiteralValue()); - Assert.AreEqual(10, props["Integer"].LiteralValue()); - Assert.AreEqual(5, props["NullableInteger"].LiteralValue()); - Assert.AreEqual("Bar", props["Object"].LiteralValue()); - Assert.AreEqual(0, props["IntegerAsObject"].LiteralValue()); - Assert.AreEqual(dateTime, props["DateTime"].LiteralValue()); - Assert.AreEqual(dateTime, props["NullableDateTime"].LiteralValue()); - Assert.IsInstanceOf(props["Struct"]); - Assert.IsInstanceOf(props["NullableStruct"]); - Assert.IsInstanceOf(props["StructPartiallyInitialized"]); - - var structProps = ((StructureValue)props["Struct"]).Properties - .ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(structProps.ContainsKey("Integer")); - Assert.IsTrue(structProps.ContainsKey("NullableInteger")); - Assert.IsTrue(structProps.ContainsKey("DateTime")); - Assert.IsTrue(structProps.ContainsKey("NullableDateTime")); - Assert.IsTrue(structProps.ContainsKey("Object")); - Assert.AreEqual(20, structProps["Integer"].LiteralValue()); - Assert.AreEqual(15, structProps["NullableInteger"].LiteralValue()); - Assert.AreEqual(dateTime, structProps["DateTime"].LiteralValue()); - Assert.AreEqual(dateTime, structProps["NullableDateTime"].LiteralValue()); - Assert.AreEqual("Bar", structProps["Object"].LiteralValue()); - - var partiallyItitializedProps = ((StructureValue)props["StructPartiallyInitialized"]).Properties - .ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(partiallyItitializedProps.ContainsKey("Integer")); - Assert.IsTrue(partiallyItitializedProps.ContainsKey("NullableInteger")); - Assert.IsTrue(partiallyItitializedProps.ContainsKey("DateTime")); - Assert.IsTrue(partiallyItitializedProps.ContainsKey("NullableDateTime")); - Assert.IsFalse(partiallyItitializedProps.ContainsKey("Object")); - Assert.AreEqual(20, partiallyItitializedProps["Integer"].LiteralValue()); - Assert.AreEqual(15, partiallyItitializedProps["NullableInteger"].LiteralValue()); - Assert.AreEqual(dateTime, partiallyItitializedProps["DateTime"].LiteralValue()); - Assert.AreEqual(dateTime, partiallyItitializedProps["NullableDateTime"].LiteralValue()); - } - - [Test] - public void WithMask_NotLoggedIfNull_Uninitialized() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new AttributedWithMask(); - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt!.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsFalse(props.ContainsKey("String")); - Assert.IsFalse(props.ContainsKey("Object")); - } - - [Test] - public void WithMask_NotLoggedIfNull_Initialized() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var dateTime = new DateTime(2000, 1, 2, 3, 4, 5); - var customized = new AttributedWithMask - { - String = "Foo[Masked]", - Object = "Bar[Masked]", - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt!.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("String")); - Assert.IsTrue(props.ContainsKey("Object")); - - Assert.AreEqual("Foo***", props["String"].LiteralValue()); - Assert.AreEqual("Bar***", props["Object"].LiteralValue()); - } - - [Test] - public void EnumerableIgnored() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomEnumerableDestructionIgnored() - { - Integer = 0, - Dependency = new Dependency - { - Integer = 0, - } - }; - - log.Information("Here is {@Customized}", customized); - - var sv = evt!.Properties["Customized"]; - Assert.IsInstanceOf(sv); - } - - [Test] - public void EnumerableDestructedAsStruct() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes(x => x.IgnoreNullProperties = true) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomEnumerableAttributed - { - Integer = 0, - NullableInteger = null, - Dependency = new Dependency - { - Integer = 0, - }, - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt!.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("Integer")); - Assert.IsFalse(props.ContainsKey("NullableInteger")); - Assert.IsTrue(props.ContainsKey("Dependency")); - - var dependencyProps = ((StructureValue)props["Dependency"]).Properties - .ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(dependencyProps.ContainsKey("Integer")); - Assert.IsFalse(dependencyProps.ContainsKey("NullableInteger")); - } - - [Test] - public void NotLoggedIfNullAttribute_Uninitialized() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes(x => x.IgnoreNullProperties = false) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new NotLoggedIfNullAttributed(); - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt!.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("Integer")); - Assert.IsTrue(props.ContainsKey("DateTime")); - Assert.IsTrue(props.ContainsKey("Struct")); - Assert.IsTrue(props.ContainsKey("StructPartiallyInitialized")); - - Assert.IsFalse(props.ContainsKey("String")); - Assert.IsFalse(props.ContainsKey("NullableInteger")); - Assert.IsFalse(props.ContainsKey("IntegerAsObject")); - Assert.IsFalse(props.ContainsKey("Object")); - Assert.IsFalse(props.ContainsKey("NullableDateTime")); - Assert.IsFalse(props.ContainsKey("NullableStruct")); - } - - [Test] - public void NotLoggedIfNullAttribute_Initialized() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes(x => x.IgnoreNullProperties = false) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var dateTime = DateTime.UtcNow; - var theStruct = new NotLoggedIfNullStruct - { - Integer = 20, - NullableInteger = 15, - DateTime = dateTime, - NullableDateTime = dateTime, - Object = "Bar", - }; - - var theStructPartiallyUnitialized = new NotLoggedIfNullStruct - { - Integer = 20, - NullableInteger = 15, - DateTime = dateTime, - NullableDateTime = dateTime, - Object = null, - }; - - var customized = new NotLoggedIfNullAttributed - { - String = "Foo", - Integer = 10, - NullableInteger = 5, - Object = "Bar", - IntegerAsObject = 0, - DateTime = dateTime, - NullableDateTime = dateTime, - Struct = theStruct, - NullableStruct = theStruct, - StructPartiallyInitialized = theStructPartiallyUnitialized, - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt!.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("String")); - Assert.IsTrue(props.ContainsKey("Integer")); - Assert.IsTrue(props.ContainsKey("NullableInteger")); - Assert.IsTrue(props.ContainsKey("Object")); - Assert.IsTrue(props.ContainsKey("IntegerAsObject")); - Assert.IsTrue(props.ContainsKey("DateTime")); - Assert.IsTrue(props.ContainsKey("NullableDateTime")); - Assert.IsTrue(props.ContainsKey("Struct")); - Assert.IsTrue(props.ContainsKey("NullableStruct")); - Assert.IsTrue(props.ContainsKey("StructPartiallyInitialized")); - - Assert.AreEqual("Foo", props["String"].LiteralValue()); - Assert.AreEqual(10, props["Integer"].LiteralValue()); - Assert.AreEqual(5, props["NullableInteger"].LiteralValue()); - Assert.AreEqual("Bar", props["Object"].LiteralValue()); - Assert.AreEqual(0, props["IntegerAsObject"].LiteralValue()); - Assert.AreEqual(dateTime, props["DateTime"].LiteralValue()); - Assert.AreEqual(dateTime, props["NullableDateTime"].LiteralValue()); - Assert.IsInstanceOf(props["Struct"]); - Assert.IsInstanceOf(props["NullableStruct"]); - Assert.IsInstanceOf(props["StructPartiallyInitialized"]); - - var structProps = ((StructureValue)props["Struct"]).Properties - .ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(structProps.ContainsKey("Integer")); - Assert.IsTrue(structProps.ContainsKey("NullableInteger")); - Assert.IsTrue(structProps.ContainsKey("DateTime")); - Assert.IsTrue(structProps.ContainsKey("NullableDateTime")); - Assert.IsTrue(structProps.ContainsKey("Object")); - Assert.AreEqual(20, structProps["Integer"].LiteralValue()); - Assert.AreEqual(15, structProps["NullableInteger"].LiteralValue()); - Assert.AreEqual(dateTime, structProps["DateTime"].LiteralValue()); - Assert.AreEqual(dateTime, structProps["NullableDateTime"].LiteralValue()); - Assert.AreEqual("Bar", structProps["Object"].LiteralValue()); - - var partiallyItitializedProps = ((StructureValue)props["StructPartiallyInitialized"]).Properties - .ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(partiallyItitializedProps.ContainsKey("Integer")); - Assert.IsTrue(partiallyItitializedProps.ContainsKey("NullableInteger")); - Assert.IsTrue(partiallyItitializedProps.ContainsKey("DateTime")); - Assert.IsTrue(partiallyItitializedProps.ContainsKey("NullableDateTime")); - Assert.IsTrue(partiallyItitializedProps.ContainsKey("Object")); - Assert.AreEqual(20, partiallyItitializedProps["Integer"].LiteralValue()); - Assert.AreEqual(15, partiallyItitializedProps["NullableInteger"].LiteralValue()); - Assert.AreEqual(dateTime, partiallyItitializedProps["DateTime"].LiteralValue()); - Assert.AreEqual(dateTime, partiallyItitializedProps["NullableDateTime"].LiteralValue()); - - } - - } -} - - diff --git a/test/Destructurama.Attributed.Tests/LogWithNameAttributedTests.cs b/test/Destructurama.Attributed.Tests/LogWithNameAttributedTests.cs deleted file mode 100644 index 60003a3..0000000 --- a/test/Destructurama.Attributed.Tests/LogWithNameAttributedTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Destructurama.Attributed.Tests.Support; -using NUnit.Framework; -using Serilog; -using Serilog.Events; -using System.Linq; - -namespace Destructurama.Attributed.Tests -{ - [TestFixture] - public class LogWithNameAttributedTests - { - - [Test] - public void AttributesAreConsultedWhenDestructuring() - { - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var personalData = new PersonalData - { - Name = "John Doe" - }; - - log.Information("Here is {@PersonData}", personalData); - - var sv = (StructureValue)evt.Properties["PersonData"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - var literalValue = props["FullName"].LiteralValue(); - Assert.AreEqual("John Doe", literalValue); - } - - #region LogWithName - public class PersonalData - { - [LogWithName("FullName")] - public string? Name { get; set; } - } - #endregion - } -} \ No newline at end of file diff --git a/test/Destructurama.Attributed.Tests/MaskedAttributeTests.cs b/test/Destructurama.Attributed.Tests/MaskedAttributeTests.cs deleted file mode 100644 index 1c35d5d..0000000 --- a/test/Destructurama.Attributed.Tests/MaskedAttributeTests.cs +++ /dev/null @@ -1,895 +0,0 @@ -using Destructurama.Attributed.Tests.Support; -using NUnit.Framework; -using Serilog; -using Serilog.Events; -using System; -using System.Linq; - -namespace Destructurama.Attributed.Tests -{ - #region CustomizedMaskedLogs - - public class CustomizedMaskedLogs - { - /// - /// 123456789 results in "***" - /// - [LogMasked] - public string? DefaultMasked { get; set; } - - /// - /// [123456789,123456789,123456789] results in [***,***,***] - /// - [LogMasked] - public string[]? DefaultMaskedArray { get; set; } - - /// - /// 123456789 results in "*********" - /// - [LogMasked(PreserveLength = true)] - public string? DefaultMaskedPreserved { get; set; } - - /// - /// "" results in "***" - /// - [LogMasked] - public string? DefaultMaskedNotPreservedOnEmptyString { get; set; } - - /// - /// 123456789 results in "#" - /// - [LogMasked(Text = "_REMOVED_")] - public string? CustomMasked { get; set; } - - /// - /// 123456789 results in "#" - /// - [LogMasked(Text = "")] - public string? CustomMaskedWithEmptyString { get; set; } - - /// - /// 123456789 results in "#########" - /// - [LogMasked(Text = "#", PreserveLength = true)] - public string? CustomMaskedPreservedLength { get; set; } - - /// - /// 123456789 results in "123******" - /// - [LogMasked(ShowFirst = 3)] - public string? ShowFirstThreeThenDefaultMasked { get; set; } - - /// - /// 123456789 results in "123******" - /// - [LogMasked(ShowFirst = 3, PreserveLength = true)] - public string? ShowFirstThreeThenDefaultMaskedPreservedLength { get; set; } - - /// - /// 123456789 results in "***789" - /// - [LogMasked(ShowLast = 3)] - public string? ShowLastThreeThenDefaultMasked { get; set; } - - /// - /// 123456789 results in "******789" - /// - [LogMasked(ShowLast = 3, PreserveLength = true)] - public string? ShowLastThreeThenDefaultMaskedPreservedLength { get; set; } - - /// - /// 123456789 results in "123REMOVED" - /// - [LogMasked(Text = "_REMOVED_", ShowFirst = 3)] - public string? ShowFirstThreeThenCustomMask { get; set; } - - /// - /// 123456789 results in "123_REMOVED_" - /// - [LogMasked(Text = "_REMOVED_", ShowFirst = 3, PreserveLength = true)] - public string? ShowFirstThreeThenCustomMaskPreservedLengthIgnored { get; set; } - - /// - /// 123456789 results in "_REMOVED_789" - /// - [LogMasked(Text = "_REMOVED_", ShowLast = 3)] - public string? ShowLastThreeThenCustomMask { get; set; } - - /// - /// 123456789 results in "_REMOVED_789" - /// - [LogMasked(Text = "_REMOVED_", ShowLast = 3, PreserveLength = true)] - public string? ShowLastThreeThenCustomMaskPreservedLengthIgnored { get; set; } - - /// - /// 123456789 results in "123***789" - /// - [LogMasked(ShowFirst = 3, ShowLast = 3)] - public string? ShowFirstAndLastThreeAndDefaultMaskInTheMiddle { get; set; } - - /// - /// 123456789 results in "123***789" - /// - [LogMasked(ShowFirst = 3, ShowLast = 3, PreserveLength = true)] - public string? ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength { get; set; } - - /// - /// 123456789 results in "123_REMOVED_789" - /// - [LogMasked(Text = "_REMOVED_", ShowFirst = 3, ShowLast = 3)] - public string? ShowFirstAndLastThreeAndCustomMaskInTheMiddle { get; set; } - - /// - /// 123456789 results in "123_REMOVED_789". PreserveLength is ignored" - /// - [LogMasked(Text = "_REMOVED_", ShowFirst = 3, ShowLast = 3, PreserveLength = true)] - public string? ShowFirstAndLastThreeAndCustomMaskInTheMiddlePreservedLengthIgnored { get; set; } - } - - #endregion - - [TestFixture] - public class MaskedAttributeTests - { - [Test] - public void LogMaskedAttribute_Replaces_Value_With_DefaultStars_Mask() - { - // [LogMasked] - // 123456789 -> "***" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - DefaultMasked = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("DefaultMasked")); - Assert.AreEqual("***", props["DefaultMasked"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Replaces_Array_Value_With_DefaultStars_Mask() - { - // [LogMasked] - // [123456789,123456789,123456789] results in [***,***,***] - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - DefaultMaskedArray = new[] { "123456789", "123456789", "123456789" } - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("DefaultMaskedArray")); - var seq = props["DefaultMaskedArray"] as SequenceValue; - foreach (var elem in seq!.Elements) - Assert.AreEqual("***", elem.LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Replaces_Value_With_DefaultStars_Mask_And_PreservedLength() - { - // [LogMasked] - // 123456789 -> "*********" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - DefaultMaskedPreserved = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("DefaultMaskedPreserved")); - Assert.AreEqual("*********", props["DefaultMaskedPreserved"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Replaces_Value_With_DefaultStars_Mask_And_Not_Preserve_Length_On_Empty_String() - { - // [LogMasked] - // "" -> "***" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - DefaultMaskedNotPreservedOnEmptyString = "" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("DefaultMaskedNotPreservedOnEmptyString")); - Assert.AreEqual("***", props["DefaultMaskedNotPreservedOnEmptyString"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Replaces_Value_With_Provided_Mask() - { - // [LogMasked(Text = "#")] - // 123456789 -> "_REMOVED_" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - CustomMasked = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("CustomMasked")); - Assert.AreEqual("_REMOVED_", props["CustomMasked"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Replaces_Value_With_Provided_Empty_Mask() - { - // [LogMasked(Text = "#")] - // 123456789 -> "_REMOVED_" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - CustomMaskedWithEmptyString = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("CustomMasked")); - Assert.AreEqual("", props["CustomMaskedWithEmptyString"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Replaces_Value_With_Provided_Mask_And_PreservedLength() - { - // [LogMasked(Text = "#")] - // 123456789 -> "#########" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - CustomMaskedPreservedLength = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("CustomMaskedPreservedLength")); - Assert.AreEqual("#########", props["CustomMaskedPreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_With_Custom_Mask() - { - // [LogMasked(Text = "REMOVED", ShowFirst = 3)] - // -> "123_REMOVED_" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstThreeThenCustomMask = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenCustomMask")); - Assert.AreEqual("123_REMOVED_", props["ShowFirstThreeThenCustomMask"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_With_Custom_Mask_PreservedLength_Ignored() - { - // [LogMasked(Text = "REMOVED", ShowFirst = 3,PreserveLength = true)] - // -> "123_REMOVED_" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstThreeThenCustomMaskPreservedLengthIgnored = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenCustomMaskPreservedLengthIgnored")); - Assert.AreEqual("123_REMOVED_", props["ShowFirstThreeThenCustomMaskPreservedLengthIgnored"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Replaces_Value_With_Default_StarMask() - { - // [LogMasked(ShowFirst = 3, ShowLast = 3)] - // -> "123***321" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstAndLastThreeAndDefaultMaskInTheMiddle = "12345678987654321" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndDefaultMaskInTheMiddle")); - Assert.AreEqual("123***321", props["ShowFirstAndLastThreeAndDefaultMaskInTheMiddle"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Replaces_Value_With_Default_StarMask_PreserveLength() - { - // [LogMasked(ShowFirst = 3, ShowLast = 3)] - // -> "123***********321" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength = "12345678987654321" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength")); - Assert.AreEqual("123***********321", props["ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Replaces_Value_With_Default_StarMask_Single_PreserveLength() - { - // [LogMasked(ShowFirst = 3, ShowLast = 3)] - // -> "123*456" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength = "123x456" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength")); - Assert.AreEqual("123*456", props["ShowFirstAndLastThreeAndDefaultMaskInTheMiddlePreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask() - { - // [LogMasked(Text = "REMOVED", ShowFirst = 3, ShowLast = 3)] - // 12345678987654321 -> 123_REMOVED_321 - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstAndLastThreeAndCustomMaskInTheMiddle = "12345678987654321" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddle")); - Assert.AreEqual("123_REMOVED_321", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddle"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_And_PreservedLength() - { - // [LogMasked(Text = "#", ShowFirst = 3, ShowLast = 3)] - // 12345678987654321 -> "123_REMOVED_321" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstAndLastThreeAndCustomMaskInTheMiddle = "12345678987654321" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddle")); - Assert.AreEqual("123_REMOVED_321", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddle"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_And_PreservedLength_Even_When_Input_Length_Is_Less_Than_ShowFirst() - { - // [LogMasked(Text = "#", ShowFirst = 3, ShowLast = 3)] - // 12 -> "12" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstAndLastThreeAndCustomMaskInTheMiddle = "12" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddle")); - Assert.AreEqual("12", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddle"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_And_PreservedLength_Even_When_Input_Length_Is_Less_Than_ShowFirst_Plus_ShowLast() - { - // [LogMasked(Text = "#", ShowFirst = 3, ShowLast = 3)] - // 1234 -> "1234" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstAndLastThreeAndCustomMaskInTheMiddle = "1234" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddle")); - Assert.AreEqual("1234", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddle"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask() - { - // [LogMasked(ShowLast = 3)] - // 123456789 -> "123***" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstThreeThenDefaultMasked = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMasked")); - Assert.AreEqual("123***", props["ShowFirstThreeThenDefaultMasked"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask() - { - // [LogMasked(Text = "_REMOVED_", ShowLast = 3)] - // 123456789 -> "_REMOVED_789" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowLastThreeThenCustomMask = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowLastThreeThenCustomMask")); - Assert.AreEqual("_REMOVED_789", props["ShowLastThreeThenCustomMask"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_PreserveLength_Ignored() - { - // [LogMasked(Text = "_REMOVED_", ShowLast = 3, PreserveLength = true)] - // 123456789 -> "_REMOVED_789" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowLastThreeThenCustomMaskPreservedLengthIgnored = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowLastThreeThenCustomMaskPreservedLengthIgnored")); - Assert.AreEqual("_REMOVED_789", props["ShowLastThreeThenCustomMaskPreservedLengthIgnored"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask() - { - // [LogMasked(ShowLast = 3)] - // 123456789 -> "***789" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowLastThreeThenDefaultMasked = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowLastThreeThenDefaultMasked")); - Assert.AreEqual("***789", props["ShowLastThreeThenDefaultMasked"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength() - { - // [LogMasked(ShowFirst = 3,PreserveLength = true))] - // -> "123******" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstThreeThenDefaultMaskedPreservedLength = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMaskedPreservedLength")); - Assert.AreEqual("123******", props["ShowFirstThreeThenDefaultMaskedPreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Empty_Input() - { - // [LogMasked(ShowFirst = 3,PreserveLength = true))] - // -> "" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstThreeThenDefaultMaskedPreservedLength = "" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMaskedPreservedLength")); - Assert.AreEqual("", props["ShowFirstThreeThenDefaultMaskedPreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Input_With_Same_Length_As_ShowFirst() - { - // [LogMasked(ShowFirst = 3,PreserveLength = true))] - // -> "123" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstThreeThenDefaultMaskedPreservedLength = "123" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMaskedPreservedLength")); - Assert.AreEqual("123", props["ShowFirstThreeThenDefaultMaskedPreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Input_Shorter_Than_ShowFirst() - { - // [LogMasked(ShowFirst = 3,PreserveLength = true))] - // -> "12" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstThreeThenDefaultMaskedPreservedLength = "12" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstThreeThenDefaultMaskedPreservedLength")); - Assert.AreEqual("12", props["ShowFirstThreeThenDefaultMaskedPreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength() - { - // [LogMasked(ShowLast = 3,PreserveLength = true))] - // -> "******789" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowLastThreeThenDefaultMaskedPreservedLength = "123456789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowLastThreeThenDefaultMaskedPreservedLength")); - Assert.AreEqual("******789", props["ShowLastThreeThenDefaultMaskedPreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Input_With_Same_Length_As_ShowLast() - { - // [LogMasked(ShowLast = 3,PreserveLength = true))] - // -> "123" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowLastThreeThenDefaultMaskedPreservedLength = "123" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowLastThreeThenDefaultMaskedPreservedLength")); - Assert.AreEqual("123", props["ShowLastThreeThenDefaultMaskedPreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_Last_NChars_Then_Replaces_All_Other_Chars_With_Default_StarMask_And_PreservedLength_Even_For_An_Input_Shorter_Than_ShowLast() - { - // [LogMasked(ShowLast = 3,PreserveLength = true))] - // -> "12" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowLastThreeThenDefaultMaskedPreservedLength = "12" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowLastThreeThenDefaultMaskedPreservedLength")); - Assert.AreEqual("12", props["ShowLastThreeThenDefaultMaskedPreservedLength"].LiteralValue()); - } - - [Test] - public void LogMaskedAttribute_Shows_First_NChars_And_Last_NChars_Then_Replaces_All_Other_Chars_With_Custom_Mask_And_PreservedLength_That_Gives_Warning() - { - // [LogMasked(Text = "REMOVED", ShowFirst = 3, ShowLast = 3, PreserveLength = true)] - // 12345678987654321 -> 123_REMOVED_321 - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedMaskedLogs - { - ShowFirstAndLastThreeAndCustomMaskInTheMiddlePreservedLengthIgnored = "12345678987654321" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("ShowFirstAndLastThreeAndCustomMaskInTheMiddlePreservedLengthIgnored")); - Assert.AreEqual("123_REMOVED_321", props["ShowFirstAndLastThreeAndCustomMaskInTheMiddlePreservedLengthIgnored"].LiteralValue()); - } - } -} diff --git a/test/Destructurama.Attributed.Tests/NotLoggedIfDefaultAttributeTests.cs b/test/Destructurama.Attributed.Tests/NotLoggedIfDefaultAttributeTests.cs deleted file mode 100644 index 74bb5de..0000000 --- a/test/Destructurama.Attributed.Tests/NotLoggedIfDefaultAttributeTests.cs +++ /dev/null @@ -1,213 +0,0 @@ -using Destructurama.Attributed.Tests.Support; -using NUnit.Framework; -using Serilog; -using Serilog.Events; -using System; -using System.Linq; - -namespace Destructurama.Attributed.Tests -{ - [TestFixture] - public class NotLoggedIfDefaultAttributeTests - { - struct NotLoggedIfDefaultStruct - { - public int Integer { get; set; } - - public DateTime DateTime { get; set; } - } - - struct NotLoggedIfDefaultStructWithAttributes - { - [NotLoggedIfDefault] - public int Integer { get; set; } - - [NotLoggedIfDefault] - public DateTime DateTime { get; set; } - - public int IntegerLogged { get; set; } - - public DateTime DateTimeLogged { get; set; } - } - - class NotLoggedIfDefaultCustomizedDefaultLogs - { - [NotLoggedIfDefault] - public string? String { get; set; } - - [NotLoggedIfDefault] - public int Integer { get; set; } - - [NotLoggedIfDefault] - public int? NullableInteger { get; set; } - - [NotLoggedIfDefault] - public object? Object { get; set; } - - [NotLoggedIfDefault] - public object? IntegerAsObject { get; set; } - - [NotLoggedIfDefault] - public DateTime DateTime { get; set; } - - [NotLoggedIfDefault] - public NotLoggedIfDefaultStruct Struct { get; set; } - - public NotLoggedIfDefaultStructWithAttributes StructWithAttributes { get; set; } - - public string? StringLogged { get; set; } - - public int IntegerLogged { get; set; } - - public int? NullableIntegerLogged { get; set; } - - public object? ObjectLogged { get; set; } - - public DateTime DateTimeLogged { get; set; } - - [LogAsScalar] - public NotLoggedIfDefaultStruct StructLogged { get; set; } - } - - - [Test] - public void NotLoggedIfDefault_Uninitialized() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new NotLoggedIfDefaultCustomizedDefaultLogs(); - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt!.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsFalse(props.ContainsKey("String")); - Assert.IsFalse(props.ContainsKey("Integer")); - Assert.IsFalse(props.ContainsKey("NullableInteger")); - Assert.IsFalse(props.ContainsKey("Object")); - Assert.IsFalse(props.ContainsKey("DateTime")); - Assert.IsFalse(props.ContainsKey("Struct")); - - Assert.IsTrue(props.ContainsKey("StringLogged")); - Assert.IsTrue(props.ContainsKey("IntegerLogged")); - Assert.IsTrue(props.ContainsKey("NullableIntegerLogged")); - Assert.IsTrue(props.ContainsKey("ObjectLogged")); - Assert.IsTrue(props.ContainsKey("DateTimeLogged")); - Assert.IsTrue(props.ContainsKey("StructLogged")); - - Assert.AreEqual(default(string), props["StringLogged"].LiteralValue()); - Assert.AreEqual(default(int), props["IntegerLogged"].LiteralValue()); - Assert.AreEqual(default(int?), props["NullableIntegerLogged"].LiteralValue()); - Assert.AreEqual(default, props["ObjectLogged"].LiteralValue()); - Assert.AreEqual(default(DateTime), props["DateTimeLogged"].LiteralValue()); - Assert.AreEqual(default(NotLoggedIfDefaultStruct), props["StructLogged"].LiteralValue()); - - Assert.IsTrue(props.ContainsKey("StructWithAttributes")); - Assert.IsTrue(props["StructWithAttributes"] is StructureValue); - - var structProps = ((StructureValue)props["StructWithAttributes"]).Properties - .ToDictionary(p => p.Name, p => p.Value); - - Assert.IsFalse(structProps.ContainsKey("Integer")); - Assert.IsFalse(structProps.ContainsKey("DateTime")); - Assert.IsTrue(structProps.ContainsKey("IntegerLogged")); - Assert.IsTrue(structProps.ContainsKey("DateTimeLogged")); - Assert.AreEqual(default(int), structProps["IntegerLogged"].LiteralValue()); - Assert.AreEqual(default(DateTime), structProps["DateTimeLogged"].LiteralValue()); - } - - [Test] - public void NotLoggedIfDefault_Initialized() - { - LogEvent? evt = null; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var dateTime = DateTime.UtcNow; - var theStruct = new NotLoggedIfDefaultStruct - { - Integer = 20, - DateTime = dateTime - }; - - var attributedStruct = new NotLoggedIfDefaultStructWithAttributes - { - Integer = 20, - DateTime = dateTime - }; - - var customized = new NotLoggedIfDefaultCustomizedDefaultLogs - { - String = "Foo", - Integer = 10, - NullableInteger = 5, - Object = "Bar", - DateTime = dateTime, - Struct = theStruct, - StructWithAttributes = attributedStruct, - IntegerAsObject = 0 - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt!.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("String")); - Assert.IsTrue(props.ContainsKey("Integer")); - Assert.IsTrue(props.ContainsKey("NullableInteger")); - Assert.IsTrue(props.ContainsKey("Object")); - Assert.IsTrue(props.ContainsKey("DateTime")); - Assert.IsTrue(props.ContainsKey("Struct")); - Assert.IsTrue(props.ContainsKey("IntegerAsObject")); - - Assert.IsTrue(props.ContainsKey("StringLogged")); - Assert.IsTrue(props.ContainsKey("IntegerLogged")); - Assert.IsTrue(props.ContainsKey("NullableIntegerLogged")); - Assert.IsTrue(props.ContainsKey("ObjectLogged")); - Assert.IsTrue(props.ContainsKey("DateTimeLogged")); - Assert.IsTrue(props.ContainsKey("StructLogged")); - - Assert.AreEqual("Foo", props["String"].LiteralValue()); - Assert.AreEqual(10, props["Integer"].LiteralValue()); - Assert.AreEqual(5, props["NullableInteger"].LiteralValue()); - Assert.AreEqual("Bar", props["Object"].LiteralValue()); - Assert.AreEqual(dateTime, props["DateTime"].LiteralValue()); - Assert.IsInstanceOf(props["Struct"]); - Assert.AreEqual(0, props["IntegerAsObject"].LiteralValue()); - - Assert.AreEqual(default(string), props["StringLogged"].LiteralValue()); - Assert.AreEqual(default(int), props["IntegerLogged"].LiteralValue()); - Assert.AreEqual(default(int?), props["NullableIntegerLogged"].LiteralValue()); - Assert.AreEqual(default, props["ObjectLogged"].LiteralValue()); - Assert.AreEqual(default(DateTime), props["DateTimeLogged"].LiteralValue()); - Assert.AreEqual(default(NotLoggedIfDefaultStruct), props["StructLogged"].LiteralValue()); - - Assert.IsTrue(props.ContainsKey("StructWithAttributes")); - Assert.IsTrue(props["StructWithAttributes"] is StructureValue); - - var structProps = ((StructureValue)props["StructWithAttributes"]).Properties - .ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(structProps.ContainsKey("Integer")); - Assert.IsTrue(structProps.ContainsKey("DateTime")); - Assert.IsTrue(structProps.ContainsKey("IntegerLogged")); - Assert.IsTrue(structProps.ContainsKey("DateTimeLogged")); - Assert.AreEqual(20, structProps["Integer"].LiteralValue()); - Assert.AreEqual(dateTime, structProps["DateTime"].LiteralValue()); - Assert.AreEqual(default(int), structProps["IntegerLogged"].LiteralValue()); - Assert.AreEqual(default(DateTime), structProps["DateTimeLogged"].LiteralValue()); - } - } -} - - diff --git a/test/Destructurama.Attributed.Tests/ReplacedAttributeTests.cs b/test/Destructurama.Attributed.Tests/ReplacedAttributeTests.cs deleted file mode 100644 index 9b3c446..0000000 --- a/test/Destructurama.Attributed.Tests/ReplacedAttributeTests.cs +++ /dev/null @@ -1,179 +0,0 @@ -using Destructurama.Attributed.Tests.Support; -using NUnit.Framework; -using Serilog; -using Serilog.Events; -using System.Linq; - -namespace Destructurama.Attributed.Tests -{ - public class CustomizedRegexLogs - { - const string RegexWithVerticalBars = @"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)"; - - /// - /// 123|456|789 results in "***|456|789" - /// - [LogReplaced(RegexWithVerticalBars, "***|$2|$3")] - public string? RegexReplaceFirst { get; set; } - - /// - /// 123|456|789 results in "123|***|789" - /// - [LogReplaced(RegexWithVerticalBars, "$1|***|$3")] - public string? RegexReplaceSecond { get; set; } - - /// - /// 123|456|789 results in "123|456|***" - /// - [LogReplaced(RegexWithVerticalBars, "$1|$2|***")] - public string? RegexReplaceThird { get; set; } - - /// - /// 123|456|789 results in "***|456|****" - /// - [LogReplaced(RegexWithVerticalBars, "***|$2|****")] - public string? RegexReplaceFirstThird { get; set; } - } - - [TestFixture] - public class ReplacedAttributeTests - { - [Test] - public void LogReplacedAttribute_Replaces_First() - { - // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "***|$2|$3")] - // 123|456|789 -> "***|456|789" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedRegexLogs - { - RegexReplaceFirst = "123|456|789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("RegexReplaceFirst")); - Assert.AreEqual("***|456|789", props["RegexReplaceFirst"].LiteralValue()); - } - - [Test] - public void LogReplacedAttribute_Replaces_Second() - { - // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "$1|***|$3")] - // 123|456|789 -> "123|***|789" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedRegexLogs - { - RegexReplaceSecond = "123|456|789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("RegexReplaceSecond")); - Assert.AreEqual("123|***|789", props["RegexReplaceSecond"].LiteralValue()); - } - - [Test] - public void LogReplacedAttribute_Replaces_Third() - { - // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "$1|$2|***")] - // 123|456|789 -> "123|456|***" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedRegexLogs - { - RegexReplaceThird = "123|456|789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("RegexReplaceThird")); - Assert.AreEqual("123|456|***", props["RegexReplaceThird"].LiteralValue()); - } - - [Test] - public void LogReplacedAttribute_Replaces_FirstThird() - { - // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "***|$2|****")] - // 123|456|789 -> "***|456|****" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedRegexLogs - { - RegexReplaceFirstThird = "123|456|789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("RegexReplaceFirstThird")); - Assert.AreEqual("***|456|****", props["RegexReplaceFirstThird"].LiteralValue()); - } - - [Test] - public void LogReplacedAttribute_Replaces_First_And_Third() - { - // [LogReplaced(@"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)", "***|$2|$3")] - // 123|456|789 -> "***|456|789" - - LogEvent evt = null!; - - var log = new LoggerConfiguration() - .Destructure.UsingAttributes() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - var customized = new CustomizedRegexLogs - { - RegexReplaceFirst = "123|456|789", - RegexReplaceThird = "123|456|789" - }; - - log.Information("Here is {@Customized}", customized); - - var sv = (StructureValue)evt.Properties["Customized"]; - var props = sv.Properties.ToDictionary(p => p.Name, p => p.Value); - - Assert.IsTrue(props.ContainsKey("RegexReplaceFirst")); - Assert.AreEqual("***|456|789", props["RegexReplaceFirst"].LiteralValue()); - Assert.IsTrue(props.ContainsKey("RegexReplaceThird")); - Assert.AreEqual("123|456|***", props["RegexReplaceThird"].LiteralValue()); - } - } -} \ No newline at end of file diff --git a/test/Destructurama.Attributed.Tests/Snippets.cs b/test/Destructurama.Attributed.Tests/Snippets.cs deleted file mode 100644 index ed4f215..0000000 --- a/test/Destructurama.Attributed.Tests/Snippets.cs +++ /dev/null @@ -1,50 +0,0 @@ -using NUnit.Framework; -using Serilog; - -namespace Destructurama.Attributed.Tests -{ - #region WithRegex - - public class WithRegex - { - const string RegexWithVerticalBars = @"([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)"; - - /// - /// 123|456|789 results in "***|456|789" - /// - [LogReplaced(RegexWithVerticalBars, "***|$2|$3")] - public string? RegexReplaceFirst { get; set; } - - /// - /// 123|456|789 results in "123|***|789" - /// - [LogReplaced(RegexWithVerticalBars, "$1|***|$3")] - public string? RegexReplaceSecond { get; set; } - } - - #endregion - - public class Snippets - { - #region LoginCommand - public class LoginCommand - { - public string? Username { get; set; } - - [NotLogged] - public string? Password { get; set; } - } - #endregion - - static ILogger log = Log.ForContext(); - - [Test] - public void LogCommand() - { - #region LogCommand - var command = new LoginCommand { Username = "logged", Password = "not logged" }; - log.Information("Logging in {@Command}", command); - #endregion - } - } -} \ No newline at end of file diff --git a/test/Destructurama.Attributed.Tests/Support/DelegatingSink.cs b/test/Destructurama.Attributed.Tests/Support/DelegatingSink.cs deleted file mode 100644 index ed9fe15..0000000 --- a/test/Destructurama.Attributed.Tests/Support/DelegatingSink.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Serilog; -using Serilog.Core; -using Serilog.Events; - -namespace Destructurama.Attributed.Tests.Support -{ - public class DelegatingSink : ILogEventSink - { - readonly Action _write; - - public DelegatingSink(Action write) - { - _write = write ?? throw new ArgumentNullException("write"); - } - - public void Emit(LogEvent logEvent) - { - _write(logEvent); - } - - public static LogEvent GetLogEvent(Action writeAction) - { - LogEvent result = null!; - var l = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(le => result = le)) - .CreateLogger(); - - writeAction(l); - return result; - } - } -} diff --git a/test/Destructurama.Attributed.Tests/Support/Extensions.cs b/test/Destructurama.Attributed.Tests/Support/Extensions.cs deleted file mode 100644 index 93397c9..0000000 --- a/test/Destructurama.Attributed.Tests/Support/Extensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Serilog.Events; - -namespace Destructurama.Attributed.Tests.Support -{ - public static class Extensions - { - public static object? LiteralValue(this LogEventPropertyValue @this) => - ((ScalarValue)@this).Value; - } -}