Skip to content

Commit a96802f

Browse files
committed
feat: Use source generators
1 parent 5771caa commit a96802f

45 files changed

Lines changed: 1169 additions & 757 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,4 +483,10 @@ dotnet_diagnostic.SYSLIB1045.severity = suggestion
483483
dotnet_diagnostic.CA2007.severity = none
484484

485485
# CA1711: Identifiers should not have incorrect suffix
486-
dotnet_diagnostic.CA1711.severity = none
486+
dotnet_diagnostic.CA1711.severity = none
487+
488+
# Prefer primary constructor
489+
dotnet_diagnostic.IDE0290.severity = none
490+
491+
# prefer new C# 12 collection initialzier
492+
dotnet_diagnostic.IDE0028.severity = none

Directory.Build.props

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<TargetFramework>net6.0</TargetFramework>
4-
<LangVersion>10.0</LangVersion>
3+
<LangVersion>Latest</LangVersion>
54
<Nullable>enable</Nullable>
65
<ImplicitUsings>enable</ImplicitUsings>
76
<Authors>Sultanov Shamil</Authors>
@@ -12,7 +11,7 @@
1211
<PropertyGroup Label="Analysis">
1312
<AnalysisLevel>latest</AnalysisLevel>
1413
<AnalysisMode>All</AnalysisMode>
15-
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
14+
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
1615
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
1716
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
1817
<EnableNETAnalyzers>true</EnableNETAnalyzers>
@@ -24,6 +23,7 @@
2423
<IncludeSymbols>true</IncludeSymbols>
2524
<SymbolPackageFormat>symbols.nupkg</SymbolPackageFormat>
2625
<DocumentationFile>bin\$(Configuration)\$(MSBuildProjectName).xml</DocumentationFile>
26+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
2727
</PropertyGroup>
2828

2929
</Project>

README.md

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,44 @@ to add an error description, enrich logs with error code, context data, collect
88

99
### What is an error code?
1010

11-
An error code is a number (or a combination of letters and numbers) that corresponds to a specific problem in the operation of the program.
12-
Example of error code: SSTV.10321, where SSTV is the prefix of app or [bounded context](https://martinfowler.com/bliki/BoundedContext.html).
11+
An error code is a number (or a combination of letters and numbers) that corresponds to a specific problem in the
12+
operation of the program.
13+
Example of error code: SSTV10321, where SSTV is the prefix of app
14+
or [bounded context](https://martinfowler.com/bliki/BoundedContext.html).
15+
1316
### Purpose
1417

1518
Applications often encounter exceptions and developers usually spend too little time to do exceptions properly.
16-
Exceptions can help us to notify about error, stop processing and avoid potentially corrupting data, provide some context to it for further research and fix.
17-
If we designate all the code with unique error codes, it will be faster to determine the root of the problem when it arises.
18-
When user occurs some error, we can show error code to him, and provide link to the helpful wiki page or user documentation for this error code, Using so user can fix the problem by himself or share it to the technical support.
19-
Technical support can look his own wiki, find article by error code, read the solution or recommendations how to solve the problem or avoid this concrete error, and maybe do automatical scripts and help to user. Error codes can save a lot of time in investigation problems.
19+
Exceptions can help us to notify about error, stop processing and avoid potentially corrupting data, provide some
20+
context to it for further research and fix.
21+
If we designate all the code with unique error codes, it will be faster to determine the root of the problem when it
22+
arises.
23+
When user occurs some error, we can show error code to him, and provide link to the helpful wiki page or user
24+
documentation for this error code, Using so user can fix the problem by himself or share it to the technical support.
25+
Technical support can look his own wiki, find article by error code, read the solution or recommendations how to solve
26+
the problem or avoid this concrete error, and maybe do automatical scripts and help to user. Error codes can save a lot
27+
of time in investigation problems.
2028

2129
### Getting started
2230

2331
This repository contains three NuGet packages:
2432

25-
| Package | Version | Description |
26-
|-------------------------------------------------------|---------|----------------------------------------------------------------------------|
27-
| [Sstv.DomainExceptions](./Sstv.DomainExceptions/README.md) | [![NuGet version](https://img.shields.io/nuget/v/Sstv.DomainExceptions.svg?style=flat-square)](https://www.nuget.org/packages/Sstv.DomainExceptions) | Core lib with no dependencies, that can be referenced in your domain layer |
28-
| [Sstv.DomainExceptions.Extensions.DependencyInjection](./Sstv.DomainExceptions.Extensions.DependencyInjection/README.md) | [![NuGet version](https://img.shields.io/nuget/v/Sstv.DomainExceptions.Extensions.DependencyInjection.svg?style=flat-square)](https://www.nuget.org/packages/Sstv.DomainExceptions.Extensions.DependencyInjection) | Dependency injection integration lib, for configuring at composition root |
29-
| [Sstv.DomainExceptions.Extensions.SerilogEnricher](./Sstv.DomainExceptions.Extensions.SerilogEnricher/README.md) | [![NuGet version](https://img.shields.io/nuget/v/Sstv.DomainExceptions.Extensions.SerilogEnricher.svg?style=flat-square)](https://www.nuget.org/packages/Sstv.DomainExceptions.Extensions.SerilogEnricher) | Serilog integration lib |
33+
| Package | Version | Description |
34+
|--------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|
35+
| [Sstv.DomainExceptions](./Sstv.DomainExceptions/README.md) | [![NuGet version](https://img.shields.io/nuget/v/Sstv.DomainExceptions.svg?style=flat-square)](https://www.nuget.org/packages/Sstv.DomainExceptions) | Core lib with no dependencies, that can be referenced in your domain layer |
36+
| [Sstv.DomainExceptions.Extensions.DependencyInjection](./Sstv.DomainExceptions.Extensions.DependencyInjection/README.md) | [![NuGet version](https://img.shields.io/nuget/v/Sstv.DomainExceptions.Extensions.DependencyInjection.svg?style=flat-square)](https://www.nuget.org/packages/Sstv.DomainExceptions.Extensions.DependencyInjection) | Dependency injection integration lib, for configuring at composition root |
37+
| [Sstv.DomainExceptions.Extensions.SerilogEnricher](./Sstv.DomainExceptions.Extensions.SerilogEnricher/README.md) | [![NuGet version](https://img.shields.io/nuget/v/Sstv.DomainExceptions.Extensions.SerilogEnricher.svg?style=flat-square)](https://www.nuget.org/packages/Sstv.DomainExceptions.Extensions.SerilogEnricher) | Serilog integration lib |
3038

3139
How to install and use, you can read at theirs readme files.
3240

3341
For usage example, you can look the sample [here](./Sstv.Host).
3442

3543
Sample also use [Hellang.Middleware.ProblemDetails](https://www.nuget.org/packages/Hellang.Middleware.ProblemDetails/),
36-
that very easy to automatically convert response into [ProblemDetails](https://datatracker.ietf.org/doc/html/rfc7807) format.
37-
We can switch to [IExceptionHandler](https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-5/#iexceptionhandler) for the same purpose,
38-
when .NET 8 ships in November 2023.
44+
that very easy to automatically convert response into [ProblemDetails](https://datatracker.ietf.org/doc/html/rfc7807)
45+
format.
46+
Also, if you are using .NET 8, then can
47+
try [IExceptionHandler](https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-5/#iexceptionhandler)
48+
for the same purpose.
3949

4050
### Contribute
4151

Sstv.DomainExceptions.Extensions.DependencyInjection/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
## [Unreleased]
1616

17+
## [2.0.0] - 2024-02-11
18+
19+
- Source gen release
20+
1721
## [1.0.0] - 2023-10-04
1822

1923
### Added

Sstv.DomainExceptions.Extensions.DependencyInjection/DomainExceptionBuilder.cs

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
using Microsoft.Extensions.DependencyInjection;
33
using Microsoft.Extensions.DependencyInjection.Extensions;
44
using Sstv.DomainExceptions.DebugViewer;
5-
using System.Diagnostics;
65

76
namespace Sstv.DomainExceptions.Extensions.DependencyInjection;
87

98
/// <summary>
109
/// Builder of domain exception.
1110
/// </summary>
12-
[DebuggerDisplay("ErrorCodesDescriptionSource = {ErrorCodesSourceAddedType.Name}")]
1311
public class DomainExceptionBuilder
1412
{
1513
/// <summary>
@@ -23,9 +21,9 @@ public class DomainExceptionBuilder
2321
public Action<IServiceProvider, DomainExceptionSettings>? ConfigureSettings { get; set; }
2422

2523
/// <summary>
26-
/// Error codes source type that should be used.
24+
/// Is manually added error codes source?
2725
/// </summary>
28-
internal Type? ErrorCodesSourceAddedType { get; set; }
26+
internal bool ErrorCodesSourceManuallyAdded { get; set; }
2927

3028
/// <summary>
3129
/// Initiates new instance <see cref="DomainExceptionBuilder"/>.
@@ -45,10 +43,21 @@ public DomainExceptionBuilder(IServiceCollection services)
4543
public DomainExceptionBuilder WithErrorCodesDescriptionSource<TErrorCodesDescriptionSource>()
4644
where TErrorCodesDescriptionSource : class, IErrorCodesDescriptionSource
4745
{
48-
EnsureErrorCodesDescriptionSourceNotAddedYet();
49-
Services.RemoveAll(typeof(IErrorCodesDescriptionSource));
5046
Services.AddSingleton<IErrorCodesDescriptionSource, TErrorCodesDescriptionSource>();
51-
ErrorCodesSourceAddedType = typeof(TErrorCodesDescriptionSource);
47+
ErrorCodesSourceManuallyAdded = true;
48+
49+
return this;
50+
}
51+
52+
/// <summary>
53+
/// Adds custom error code description source.
54+
/// </summary>
55+
/// <typeparam name="TErrorCodesDescriptionSource">Custom error codes description source.</typeparam>
56+
public DomainExceptionBuilder WithErrorCodesDescriptionSource<TErrorCodesDescriptionSource>(TErrorCodesDescriptionSource instance)
57+
where TErrorCodesDescriptionSource : class, IErrorCodesDescriptionSource
58+
{
59+
Services.AddSingleton<IErrorCodesDescriptionSource>(instance);
60+
ErrorCodesSourceManuallyAdded = true;
5261

5362
return this;
5463
}
@@ -63,12 +72,10 @@ public DomainExceptionBuilder WithErrorCodesDescriptionFromConfiguration(
6372
{
6473
ArgumentNullException.ThrowIfNull(sectionName);
6574

66-
EnsureErrorCodesDescriptionSourceNotAddedYet();
67-
68-
ErrorCodesDescriptionFromConfigurationSource.SectionName = sectionName;
69-
Services.RemoveAll(typeof(IErrorCodesDescriptionSource));
70-
Services.AddSingleton<IErrorCodesDescriptionSource, ErrorCodesDescriptionFromConfigurationSource>();
71-
ErrorCodesSourceAddedType = typeof(ErrorCodesDescriptionFromConfigurationSource);
75+
Services.AddSingleton<IErrorCodesDescriptionSource>(sp =>
76+
new ErrorCodesDescriptionFromConfigurationSource(sp.GetRequiredService<IConfiguration>().GetSection(sectionName))
77+
);
78+
ErrorCodesSourceManuallyAdded = true;
7279

7380
return this;
7481
}
@@ -78,18 +85,15 @@ public DomainExceptionBuilder WithErrorCodesDescriptionFromConfiguration(
7885
/// </summary>
7986
/// <param name="errorDescriptions">Dictionary of errors.</param>
8087
public DomainExceptionBuilder WithErrorCodesDescriptionFromMemory(
81-
IDictionary<string, ErrorDescription> errorDescriptions
88+
IReadOnlyDictionary<string, ErrorDescription> errorDescriptions
8289
)
8390
{
8491
ArgumentNullException.ThrowIfNull(errorDescriptions);
8592

86-
EnsureErrorCodesDescriptionSourceNotAddedYet();
87-
88-
Services.RemoveAll(typeof(IErrorCodesDescriptionSource));
8993
Services.AddSingleton<IErrorCodesDescriptionSource>(
9094
new ErrorCodesDescriptionInMemorySource(errorDescriptions)
9195
);
92-
ErrorCodesSourceAddedType = typeof(ErrorCodesDescriptionInMemorySource);
96+
ErrorCodesSourceManuallyAdded = true;
9397

9498
return this;
9599
}
@@ -103,25 +107,9 @@ internal void Build()
103107
Services.AddHostedService<InitHostedService>(sp => new InitHostedService(sp, ConfigureSettings));
104108

105109
// default registration
106-
if (ErrorCodesSourceAddedType is null)
110+
if (!ErrorCodesSourceManuallyAdded)
107111
{
108112
WithErrorCodesDescriptionFromConfiguration();
109113
}
110114
}
111-
112-
/// <summary>
113-
/// Throw if already registered error codes description source.
114-
/// </summary>
115-
/// <exception cref="InvalidOperationException">
116-
/// If error codes description source already registered.
117-
/// </exception>
118-
private void EnsureErrorCodesDescriptionSourceNotAddedYet()
119-
{
120-
if (ErrorCodesSourceAddedType is not null)
121-
{
122-
throw new InvalidOperationException(
123-
$"IErrorCodesDescriptionSource already added with implementation type {ErrorCodesSourceAddedType.FullName}"
124-
);
125-
}
126-
}
127115
}

Sstv.DomainExceptions.Extensions.DependencyInjection/ErrorCodesDescriptionFromConfigurationSource.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@ public sealed class ErrorCodesDescriptionFromConfigurationSource : IErrorCodesDe
1212
/// <summary>
1313
/// Section with error codes.
1414
/// </summary>
15-
private readonly IConfigurationSection _configuration;
16-
17-
/// <summary>
18-
/// Section name with error codes.
19-
/// </summary>
20-
internal static string? SectionName { get; set; }
15+
private readonly IConfiguration _configuration;
2116

2217
/// <summary>
2318
/// Initialize new instance of <see cref="ErrorCodesDescriptionFromConfigurationSource"/>.
@@ -30,7 +25,7 @@ public ErrorCodesDescriptionFromConfigurationSource(IConfiguration configuration
3025
{
3126
ArgumentNullException.ThrowIfNull(configuration);
3227

33-
_configuration = configuration.GetSection(SectionName ?? "DomainExceptionSettings:ErrorCodes");
28+
_configuration = configuration;
3429
}
3530

3631
/// <summary>

Sstv.DomainExceptions/ErrorCodesMeter.cs renamed to Sstv.DomainExceptions.Extensions.DependencyInjection/ErrorCodesMeter.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Diagnostics;
22
using System.Diagnostics.Metrics;
33

4-
namespace Sstv.DomainExceptions;
4+
namespace Sstv.DomainExceptions.Extensions.DependencyInjection;
55

66
/// <summary>
77
/// Errors metric collector.
@@ -24,9 +24,9 @@ public static class ErrorCodesMeter
2424
private static readonly Counter<long> _counter = _meter.CreateCounter<long>(name: "error.codes", description: "Error codes count");
2525

2626
/// <summary>
27-
/// Counts exception.
27+
/// Counts error codes.
2828
/// </summary>
29-
/// <param name="domainException">An exception.</param>
29+
/// <param name="domainException">Exception.</param>
3030
public static void Measure(DomainException? domainException)
3131
{
3232
if (!_counter.Enabled)
@@ -47,4 +47,29 @@ public static void Measure(DomainException? domainException)
4747

4848
_counter.Add(1, tagList);
4949
}
50+
51+
/// <summary>
52+
/// Counts error codes.
53+
/// </summary>
54+
/// <param name="errorDescription">An error description.</param>
55+
public static void Measure(ErrorDescription? errorDescription)
56+
{
57+
if (!_counter.Enabled)
58+
{
59+
return;
60+
}
61+
62+
if (errorDescription is null)
63+
{
64+
return;
65+
}
66+
67+
var tagList = new TagList
68+
{
69+
{ "code", errorDescription.ErrorCode },
70+
{ "message", errorDescription.Description }
71+
};
72+
73+
_counter.Add(1, tagList);
74+
}
5075
}

Sstv.DomainExceptions.Extensions.DependencyInjection/InitHostedService.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,29 @@ internal sealed class InitHostedService : BackgroundService
1515
/// <param name="configure">User provided configure action.</param>
1616
public InitHostedService(IServiceProvider sp, Action<IServiceProvider, DomainExceptionSettings>? configure)
1717
{
18-
configure?.Invoke(sp, DomainExceptionSettings.Instance);
18+
var instance = DomainExceptionSettings.Instance;
19+
configure?.Invoke(sp, instance);
20+
21+
if (instance.CollectErrorCodesMetricAutomatically)
22+
{
23+
instance.OnExceptionCreated = ErrorCodesMeter.Measure;
24+
}
1925

2026
DomainExceptionSettings.ErrorDescriptionSourceGetter =
21-
new Lazy<IErrorCodesDescriptionSource?>(() => sp.GetService<IErrorCodesDescriptionSource>());
27+
new Lazy<IErrorCodesDescriptionSource?>(() =>
28+
{
29+
var dict = new Dictionary<string, ErrorDescription>();
30+
31+
foreach (var s in sp.GetServices<IErrorCodesDescriptionSource>())
32+
{
33+
foreach (var errorDescription in s.Enumerate())
34+
{
35+
dict.TryAdd(errorDescription.ErrorCode, errorDescription);
36+
}
37+
}
38+
39+
return new ErrorCodesDescriptionInMemorySource(dict);
40+
});
2241
}
2342
protected override Task ExecuteAsync(CancellationToken stoppingToken)
2443
{

Sstv.DomainExceptions.Extensions.DependencyInjection/README.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ This library brings to Sstv.DomainExceptions additional capabilities to register
1212
You can install using Nuget Package Manager:
1313

1414
```bash
15-
Install-Package Sstv.DomainExceptions.Extensions.DependencyInjection -Version 1.0.0
15+
Install-Package Sstv.DomainExceptions.Extensions.DependencyInjection -Version 2.0.0
1616
```
1717

1818
via the .NET CLI:
1919

2020
```bash
21-
dotnet add package Sstv.DomainExceptions.Extensions.DependencyInjection --version 1.0.0
21+
dotnet add package Sstv.DomainExceptions.Extensions.DependencyInjection --version 2.0.0
2222
```
2323

2424
or you can add package reference manually:
2525

2626
```xml
27-
<PackageReference Include="Sstv.DomainExceptions.Extensions.DependencyInjection" Version="1.0.0" />
27+
<PackageReference Include="Sstv.DomainExceptions.Extensions.DependencyInjection" Version="2.0.0" />
2828
```
2929

3030
## How to use?
@@ -79,6 +79,9 @@ services.AddDomainException(bulder =>
7979
});
8080
```
8181

82+
All this sources internally merged and can be used by any DomainException class
83+
84+
8285
Below example of appsettings.json, if you choose `WithErrorCodesDescriptionFromConfiguration` with default configuration section
8386
`DomainExceptionSettings:ErrorCodes`:
8487

@@ -181,17 +184,11 @@ output example:
181184
{
182185
  "errorCodes": [
183186
    {
184-
      "exceptionType": "Sstv.Host.MyGenericException",
185-
      "assemblyName": "Sstv.Host, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
186-
      "errorCodesEnumType": "Sstv.Host.DomainErrorCodesEnum",
187187
      "code": "SSTV.10001",
188188
      "helpLink": "https://help.myproject.ru/error-codes/not-enough-money",
189189
      "message": "You have not enough money"
190190
    },
191191
    {
192-
      "exceptionType": "Sstv.Host.MyGenericException",
193-
      "assemblyName": "Sstv.Host, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
194-
      "errorCodesEnumType": "Sstv.Host.DomainErrorCodesEnum",
195192
      "code": "DIF.10003",
196193
      "helpLink": "https://help.myproject.ru/error-codes/DIF.10003",
197194
      "message": "Obsolete error code in enum",

0 commit comments

Comments
 (0)