Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion docs/wiki/AspNetCore-Integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ then let the mapper derive the status code, title, detail, and formatted message
```csharp
using ZodSharp.AspNetCore;

public static class ConcurrentErrorType
public static partial class ConcurrentErrorType
{
[ErrorType]
public static readonly ErrorType SaveFailed = new(
Code: "aggregate_save_failed",
Description: "The aggregate could not be saved.",
Expand Down Expand Up @@ -126,6 +127,39 @@ throw new ZodException([
]);
```

### Generated `Create` / `Throw` helpers

Marking the field with `[ErrorType]` and the containing class `partial` lets the bundled source
generator turn each field into strongly typed static helpers. For the field above it generates
`ConcurrentErrorType.CreateSaveFailed(...)` and `ConcurrentErrorType.ThrowSaveFailed(...)` with one
named parameter per entry in `Parameters`:

```csharp
// Returns a ValidationError with the code, the formatted message, and the named parameters.
var error = ConcurrentErrorType.CreateSaveFailed("agg-123", "Invoice");

// Throws a ZodException carrying the same ValidationError.
ConcurrentErrorType.ThrowSaveFailed("agg-123", "Invoice");

// The path and structured issue metadata can be populated too:
var error = ConcurrentErrorType.CreateSaveFailed(
"agg-123",
"Invoice",
path: ["order", "items", "[0]"],
origin: "collection",
minimum: 1,
maximum: 10,
inclusive: true);
```

The generated `Create` builds the `parameters` dictionary and sets the message from
`ErrorType.FormatMessage`, so `error.Message` already reads
`Aggregate 'agg-123' (of type Invoice) failed to save` and mapping through the registry produces the
`409 Conflict` response described below. The analyzers `ZODSASP001`/`ZODSASP002`/`ZODSASP003`
(bundled with the package) warn when a `MessageFormat` placeholder is not declared in `Parameters`,
when an `[ErrorType]` field's containing class is not `partial`, or when the field is not
`static readonly`.

Produces a `409 Conflict` `HttpValidationProblemDetails` with:

```json
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zodsharp",
"version": "2.0.0-prerelease.10",
"version": "2.0.0-prerelease.11",
"private": true,
"license": "MIT",
"author": {
Expand Down
1 change: 1 addition & 0 deletions purview-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"lib/net8.0/Purview.ZodSharp.AspNetCore.dll",
"lib/net8.0/Purview.ZodSharp.AspNetCore.xml",
"analyzers/dotnet/cs/Purview.ZodSharp.AspNetCore.Analyzers.dll",
"analyzers/dotnet/cs/Purview.ZodSharp.AspNetCore.SourceGenerators.dll",
"README.md",
"purview-logo-light.png"
],
Expand Down
2 changes: 2 additions & 0 deletions src/ZodSharp.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<Folder Name="/tests/">
<Project Path="tests/AspNetCore.UnitTests/AspNetCore.UnitTests.csproj" />
<Project Path="tests/AspNetCore.Analyzers.UnitTests/AspNetCore.Analyzers.UnitTests.csproj" />
<Project Path="tests/AspNetCore.SourceGenerators.UnitTests/AspNetCore.SourceGenerators.UnitTests.csproj" />
<Project Path="tests/NewtonsoftJson.UnitTests/NewtonsoftJson.UnitTests.csproj" />
<Project Path="tests/SourceGenerators.UnitTests/SourceGenerators.UnitTests.csproj" />
<Project Path="tests/SystemTextJson.UnitTests/SystemTextJson.UnitTests.csproj" />
Expand All @@ -22,6 +23,7 @@
<Folder Name="/src/">
<Project Path="src/AspNetCore/AspNetCore.csproj" />
<Project Path="src/AspNetCore.Analyzers/AspNetCore.Analyzers.csproj" />
<Project Path="src/AspNetCore.SourceGenerators/AspNetCore.SourceGenerators.csproj" />
<Project Path="src/NewtonsoftJson/NewtonsoftJson.csproj" />
<Project Path="src/SourceGenerators/SourceGenerators.csproj" />
<Project Path="src/SystemTextJson/SystemTextJson.csproj" />
Expand Down
4 changes: 3 additions & 1 deletion src/src/AspNetCore.Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@

Rule ID | Category | Severity | Notes
--------|----------|----------|------
ZODSASP001 | ZodSharp.AspNetCore | Warning | MessageFormat placeholder is not declared in Parameters
ZODSASP001 | ZodSharp.AspNetCore | Warning | MessageFormat placeholder is not declared in Parameters
ZODSASP002 | ZodSharp.AspNetCore | Warning | Error type containing type must be partial
ZODSASP003 | ZodSharp.AspNetCore | Warning | ErrorType field must be static readonly
38 changes: 38 additions & 0 deletions src/src/AspNetCore.Analyzers/DiagnosticLibrary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.CodeAnalysis;

namespace ZodSharp.AspNetCore.Analyzers;

/// <summary>
/// Diagnostic descriptors reported by the ZodSharp.AspNetCore analyzers.
/// </summary>
static class DiagnosticLibrary
{
const string Category = "ZodSharp.AspNetCore";

public static readonly DiagnosticDescriptor MessageFormatPlaceholderNotDeclared = new(
id: "ZODSASP001",
title: "MessageFormat placeholder is not declared in Parameters",
messageFormat: "MessageFormat placeholder '{0}' is not declared in ErrorType.Parameters",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true
);

public static readonly DiagnosticDescriptor ErrorTypeContainingTypeNotPartial = new(
id: "ZODSASP002",
title: "Error type containing type must be partial",
messageFormat: "The containing type '{0}' must be declared 'partial' so that Create/Throw methods can be generated",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true
);

public static readonly DiagnosticDescriptor ErrorTypeFieldInvalid = new(
id: "ZODSASP003",
title: "ErrorType field must be static readonly",
messageFormat: "The ErrorType field '{0}' must be declared 'static readonly' for Create/Throw methods to be generated",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true
);
}
Loading
Loading