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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ Read ./ai-plans/AGENTS.md for details on how to write plans.
## Here is Your Space

If you encounter something worth noting while you are working on this code base, write it down here in this section. Once you are finished, I will discuss it with you, and we can decide where to put your notes.

- `MetadataValueReconstructor` preserves `OperationCanceledException` through its exception filters, but this is not behaviorally testable through the generator's public surface: a pre-cancelled token is intercepted by Roslyn before reconstruction, and none of the whitelisted framework evaluations can throw cancellation. The contract therefore rests on the filters' structure unless an accepted evaluation gains a reachable cancellation path.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,9 @@ app.MapPut("/api/movieRatings", AddMovieRating)
);
```

The generator analyzes top-level `context.Check(...).Rule(...)` chains and produces response schemas and examples when metadata arguments are compile-time constants (e.g. `HasLengthIn(10, 1000)` or `IsInRange(1, 5)`).
The generator analyzes top-level `context.Check(...).Rule(...)` chains and produces response schemas and examples from compile-time constants (for example, `HasLengthIn(10, 1000)` or `IsInRange(1, 5)`). It also reconstructs deterministic `DateTime`, `DateTimeOffset`, `TimeSpan`, `DateOnly`, `TimeOnly`, `Guid`, and `Uri` boundaries written with supported constructors, factories, well-known static values, or `static readonly` fields declared in the validator's file. Reconstructed message values and example metadata use the same canonical text.

Runtime-computed or unsupported boundary expressions still generate valid schemas, but the affected example omits its message and metadata and reports `LPRSG0015` at the argument. `DateTimeKind.Local`, culture-sensitive parsing, arithmetic or chained calls, and invalid constructor or factory arguments follow this degraded path. A `static readonly` field declared in another file or assembly is deliberately not followed and reports `LPRSG0016`; write the value inline or move its declaration into the validator's file when the complete example is required.

Use `[PortableValidationOpenApiErrorHint]` to annotate codes the generator cannot infer (for example, from `Must(...)`, `Custom(...)`, or child validators):

Expand Down
192 changes: 192 additions & 0 deletions ai-plans/0057-0-openapi-examples-for-non-constant-boundaries.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Globalization;
using System.Text;

namespace Light.PortableResults.Validation.OpenApi.SourceGeneration;

/// <summary>
/// Renders C# string literals and escaped characters with deterministic escaping for generated source.
/// </summary>
internal static class CSharpLiterals
{
public static string ToStringLiteral(string value)
{
var builder = new StringBuilder(value.Length + 2).Append('"');
foreach (var c in value)
{
builder.Append(EscapeChar(c));
}

return builder.Append('"').ToString();
}

public static string EscapeChar(char value) =>
value switch
{
'\\' => @"\\",
'"' => "\\\"",
'\'' => "\\'",
'\0' => "\\0",
'\a' => "\\a",
'\b' => "\\b",
'\f' => "\\f",
'\n' => "\\n",
'\r' => "\\r",
'\t' => "\\t",
'\v' => "\\v",
_ => char.IsControl(value) ?
"\\u" + ((int) value).ToString("x4", CultureInfo.InvariantCulture) :
value.ToString()
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,24 @@ public static class DiagnosticDescriptors
isEnabledByDefault: true,
helpLinkUri: HelpLinkBase
);

public static readonly DiagnosticDescriptor MetadataValueCannotBeReconstructed = new (
"LPRSG0015",
"Validation metadata value cannot be reconstructed",
"Validation rule '{0}' metadata argument '{1}' cannot be reconstructed for an OpenAPI error example",
Category,
DiagnosticSeverity.Info,
isEnabledByDefault: true,
helpLinkUri: HelpLinkBase
);

public static readonly DiagnosticDescriptor MultiFileMetadataFieldUnsupported = new (
"LPRSG0016",
"Multi-file validation metadata field resolution is unsupported",
"Validation rule '{0}' metadata argument '{1}' references a static readonly field that requires unsupported multi-file resolution; declare it in the validator's file or write it inline",
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: HelpLinkBase
);
}
Loading
Loading