Skip to content

refactor: split oversized files under 500-LOC threshold#137

Merged
ANcpLua merged 12 commits into
mainfrom
refactor/split-oversized-files
May 16, 2026
Merged

refactor: split oversized files under 500-LOC threshold#137
ANcpLua merged 12 commits into
mainfrom
refactor/split-oversized-files

Conversation

@ANcpLua
Copy link
Copy Markdown
Owner

@ANcpLua ANcpLua commented May 16, 2026

Summary

Brings every .cs source/test file in the repo under a 500-LOC threshold by splitting along natural responsibility seams. Verified dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed, 0 failed) at every step.

Threshold

No explicit file-length setting exists in .editorconfig, Directory.Build.props, .globalconfig, ruleset, or per-repo CLAUDE.md. SonarAnalyzer S104 defaults to 1000 lines and is not configured. Per the task spec, this PR uses 500 LOC for .cs — aggressive but matches the repo's existing partial-class / one-type-per-file conventions.

Before / After (baseline → final)

File Before After (largest sibling) Strategy
Core/ErrorOrEndpointGenerator.ParameterBinding.cs 672 295 New .Meta + .Classifiers partials
tests/.../ParameterBindingTests.cs 660 249 4 classifier-domain test classes
Core/ErrorOrEndpointGenerator.Emitter.cs 596 251 New .Versioning + .Invoker + .ErrorHandling partials
Models/EndpointModels.cs 580 183 8 per-domain Models/ files (file deleted)
Core/ErrorOrEndpointGenerator.Extractor.cs 580 274 New .ErrorInference + .InterfaceDetection partials
tests/.../ErrorOrTests.cs 565 283 3 cohesive test classes (file deleted)
tests/.../MiddlewareEmissionTests.cs 563 198 3 middleware-domain test classes (file deleted)
Core/ErrorOrEndpointGenerator.Initialize.cs 543 314 New .Attributes + .EndpointFlow partials
Emitters/BindingCodeEmitter.cs 535 174 Made partial, split into 4 per-source files
Analyzers/ErrorOrEndpointAnalyzer.cs 521 269 Made partial, split into 3 files

Plus three commits inherited from the earlier branch state: OpenApiTransformerGenerator.cs (810→1), DiagnosticTests.cs (split into 5), and an earlier endpoint generator internals split (the original 4 .Emitter.* / .Extractor.Metadata partials).

Result: zero .cs files over 500 LOC remain (find . -name '*.cs' -not -path '*/bin/*' -not -path '*/obj/*' -not -path '*/Generated/*' -exec wc -l {} + | awk '\$1 > 500' returns empty).

Public-API changes

None. Every split preserved the public surface:

  • ErrorOrEndpointGenerator (the IIncrementalGenerator) — still public sealed partial class, same Initialize(...) entry point.
  • ErrorOrEndpointAnalyzer (the DiagnosticAnalyzer) — became public sealed partial class, same SupportedDiagnostics array + Initialize override.
  • BindingCodeEmitter (consumed via BindingCodeEmitter.EmitX(...) from Emitter.Invoker.cs and elsewhere) — became internal static partial class. No call sites changed.
  • All Models/EndpointModels.cs types — internal readonly record struct / internal enum. Namespace unchanged (ErrorOr.Generators, note: deliberately not ErrorOrX.Generators).
  • AttributeNameMatcher — relocated from ParameterBinding.cs to ParameterBinding.Meta.cs where it's actually used by ExtractKeyFromKeyedServiceAttribute (in TypeAnalysis.cs). It's private, no external surface.

Logic bugs fixed

None. This PR is mechanical refactoring; no behavior changes. Two minor opportunities were noted but not taken because they exceed scope:

  • BindingCodeEmitter.GetTryParseExpression is a hand-rolled 40-case switch on type FQN. Table-driven rewrite would be cleaner but is a separate refactor.
  • Emitter.cs's EmitMappings builds a ~110-line literal C# scaffold via StringBuilder.AppendLine. A raw string literal or template would be more readable but is also a separate refactor.

Duplication killed

AttributeNameMatcher is now co-located with its sole consumer (ExtractKeyFromKeyedServiceAttribute in ParameterBinding.TypeAnalysis.cs calls into the same partial class). Previously it sat at the bottom of the unrelated dispatcher file.

Files left untouched + why

  • Generated/ files and *.g.cs — explicitly excluded per task rules.
  • Polyfills.cs (under 500 LOC but the .editorconfig carries existing suppressions that document its intentional namespace-folder mismatch).
  • Any file that didn't cross the 500-LOC threshold (e.g., RouteValidator.cs at 467, Descriptors.cs exactly at 500).
  • The four existing ParameterBinding.TypeAnalysis.cs (348 LOC), Extractor.Metadata.cs (429), and OpenApiTransformerGenerator.Extractor.cs (388) sub-partials — already under threshold from the prior agent's work.

Test plan

  • dotnet build ErrorOrX.slnx — clean (0 warnings, 0 errors)
  • dotnet test --solution ErrorOrX.slnx — 443 passed, 0 failed
  • Per-file build+test gate after each commit (12 commits, 12 verifications)
  • CI: pending GitHub Actions run on this PR

🤖 Generated with Claude Code

ANcpLua and others added 12 commits May 16, 2026 18:28
DiagnosticTests.cs was 956 lines covering every EOE diagnostic. Split into
five focused classes grouped by validation concern:

- BindingTypeValidationTests (EOE010-014, 016-017)
- RouteBodyValidationTests (EOE003, 005-006, 015, 018-021)
- UnionTypeAndFactoryTests (EOE022-024)
- JsonAotValidationTests (EOE007, 025, 039, 041)
- NamingAndValidCaseTests (EOE033 + valid cases)

No test methods were renamed or modified.
…ze/Extractor/Emitter partials

The generator was 810 lines in a single sealed class. Mirror the partial
pattern already used by ErrorOrEndpointGenerator (Core/*.cs):

- OpenApiTransformerGenerator.cs - pipeline init + helpers
- Core/OpenApiTransformerGenerator.Extractor.cs - metadata extraction + XML parsing
- Core/OpenApiTransformerGenerator.Emitter.cs - transformer code emission

No logic changes.
Splits the 580-line grab-bag at Models/EndpointModels.cs into cohesive
per-domain files, each under 200 lines. Same namespace (ErrorOr.Generators),
zero behavior changes — these are all record structs / enums used as
incremental-generator cache keys.

Before:
  Models/EndpointModels.cs                  580 LOC

After:
  Models/EndpointParameters.cs              175 LOC  (enums + EndpointParameter + ParameterMeta + ParameterBindingResult)
  Models/EndpointDescriptor.cs              183 LOC  (EndpointDescriptor + Metadata + Success + UnionTypeResult)
  Models/MethodAnalysis.cs                   64 LOC  (MethodAnalysis + ReturnTypeInfo + ErrorInferenceInfo + SseInfo)
  Models/MiddlewareInfo.cs                   25 LOC
  Models/RouteModels.cs                      39 LOC
  Models/VersioningModels.cs                 31 LOC
  Models/OpenApiModels.cs                    40 LOC
  Models/ValidationModels.cs                 30 LOC

Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…partials

Splits the 672-line ErrorOrEndpointGenerator.ParameterBinding partial
into three cohesive partials, each well under 300 lines. AttributeNameMatcher
was misplaced — it is only consumed by ExtractKeyFromKeyedServiceAttribute
in TypeAnalysis.cs and by the meta-builder, so it now lives next to its
caller in the new Meta partial.

Before:
  Core/ErrorOrEndpointGenerator.ParameterBinding.cs                672 LOC
  Core/ErrorOrEndpointGenerator.ParameterBinding.TypeAnalysis.cs   348 LOC

After:
  Core/ErrorOrEndpointGenerator.ParameterBinding.cs                243 LOC
    (entry + BuildEndpointParameters + ClassifyParameter dispatcher
     + InferParameterSource + ParameterSuccess + ParameterClassificationResult)
  Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs           163 LOC
    (CreateParameterMeta + BuildFlags + DetectSpecialKind
     + DetermineBoundName + AttributeNameMatcher)
  Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs    295 LOC
    (ClassifyFromRoute/ImplicitRoute/FromQuery/FromHeader/FromForm
     + ClassifyFormDtoParameter + ClassifyAsParameters)
  Core/ErrorOrEndpointGenerator.ParameterBinding.TypeAnalysis.cs   348 LOC (unchanged)

Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ain classes

Splits the 660-line ParameterBindingTests.cs along its existing #region
boundaries into four cohesive test classes that mirror the production-side
split (ParameterBinding.{cs, Meta, Classifiers, TypeAnalysis}).

Before:
  tests/ErrorOrX.Generators.Tests/ParameterBindingTests.cs       660 LOC (25 tests)

After:
  tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs        249 LOC
    (smart inference: HTTP+complex→body, service detection, EOE021)
  tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs       170 LOC
    (route name-match, query primitives, primitive arrays, TryParse, Guid)
  tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs 151 LOC
    ([FromBody], [FromServices], [FromKeyedServices], [FromRoute],
     [FromQuery], [FromHeader])
  tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs      73 LOC
    (HttpContext, CancellationToken, Stream)

Verified: dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ndling partials

Splits the 596-line ErrorOrEndpointGenerator.Emitter.cs along its three
existing concerns into sub-partials, matching the convention already
established by Emitter.JsonContext.cs / Emitter.Options.cs / Emitter.Support.cs.

Before:
  Core/ErrorOrEndpointGenerator.Emitter.cs                 596 LOC

After:
  Core/ErrorOrEndpointGenerator.Emitter.cs                 190 LOC
    (EmitEndpoints orchestration + EmitMappings literal scaffold + EmitMapCall)
  Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs       94 LOC
    (ComputeGlobalVersionSet + EmitVersionSet + EmitVersioningCalls + VersionSetContext)
  Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs         251 LOC
    (EmitInvoker, body code, wrapper/core method emission, bind helpers,
     form content-type guard, InvokerContext)
  Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs   108 LOC
    (Union-type dispatch, ProblemDetails building, validation handling,
     Location-header for Created+Id, ErrorType switch)

The existing partials (JsonContext, Options, Support) are unchanged.

Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Detection partials

Splits the 580-line ErrorOrEndpointGenerator.Extractor partial along its
three distinct extraction phases. Sits alongside the existing
Extractor.Metadata.cs partial so the four files now mirror the four
extraction concerns of the generator pipeline.

Before:
  Core/ErrorOrEndpointGenerator.Extractor.cs           580 LOC
  Core/ErrorOrEndpointGenerator.Extractor.Metadata.cs  429 LOC (unchanged)

After:
  Core/ErrorOrEndpointGenerator.Extractor.cs                       203 LOC
    (ExtractErrorOrReturnType + DetectIdProperty + TryUnwrap{AsyncEnumerable,SseItem}
     + IsErrorOrType + ExtractProducesErrorAttributes + HasAcceptedResponseAttribute)
  Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs        274 LOC
    (InferErrorTypesFromMethod + GetMethodBody + CollectErrorTypes(Recursive)
     + ProcessNode + TryHandleErrorFactoryInvocation + TryGetReferencedSymbol
     + GetBodyToScan + ExtractCustomErrorInfo + IsErrorFactoryInvocation
     + ToSortedErrorArray)
  Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs    138 LOC
    (TryDetectUndocumentedInterfaceCall + TryExtractReturnsErrorAttributes
     + MapEnumValueToName + ReturnsErrorOr)
  Core/ErrorOrEndpointGenerator.Extractor.Metadata.cs              429 LOC (unchanged)

Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… partials

Splits the 543-line ErrorOrEndpointGenerator.Initialize partial along its
three responsibilities: pipeline wiring, attribute emission, and per-attribute
endpoint analysis.

Before:
  Core/ErrorOrEndpointGenerator.Initialize.cs                543 LOC

After:
  Core/ErrorOrEndpointGenerator.Initialize.cs                133 LOC
    (Initialize method, EmitMappingsAndRunAnalysis, route/version reports,
     option parsers, EmitContext, Helpers)
  Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs     127 LOC
    (EmitAttributes — marker-attribute literal-source emission via
     PostInitializationOutput)
  Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs   314 LOC
    (CombineHttpMethodProviders, CreateEndpointProvider, AnalyzeEndpointFlow,
     ProcessAttributeFlow, ExtractHttpMethodAndPattern, TrackingNames)

Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits the 535-line BindingCodeEmitter static class into four partials,
one per binding-source family. Class becomes 'internal static partial class'
to preserve the existing BindingCodeEmitter.EmitX(...) call sites.

Before:
  Emitters/BindingCodeEmitter.cs               535 LOC

After:
  Emitters/BindingCodeEmitter.cs               113 LOC
    (dispatcher + Route + Service / KeyedService / HttpContext / CancellationToken
     / Stream / PipeReader / FormFile / FormFiles / FormCollection)
  Emitters/BindingCodeEmitter.Query.cs         174 LOC
    (Query + Header — share scalar/collection pattern, plus BindAsync)
  Emitters/BindingCodeEmitter.Body.cs          142 LOC
    (Body Allow/Disallow + Form + AsParameters expansion)
  Emitters/BindingCodeEmitter.Parsing.cs       148 LOC
    (BuildArgumentExpression + GetTryParseExpression + EmitValidationDictBuilder)

Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation/BodyAndValidation partials

Splits the 521-line ErrorOrEndpointAnalyzer sealed class into three partials.
The class becomes 'sealed partial class' (still DiagnosticAnalyzer) with the
same Initialize/SupportedDiagnostics surface.

Before:
  Analyzers/ErrorOrEndpointAnalyzer.cs                       521 LOC

After:
  Analyzers/ErrorOrEndpointAnalyzer.cs                       269 LOC
    (DiagnosticAnalyzer surface, Initialize, AnalyzeMethod / AnalyzeEndpoint
     top-level loop, GetEndpointAttributes, IsValidReturnType, IsErrorOr,
     ExtractRouteParametersWithConstraints)
  Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs       186 LOC
    (EOE005 pattern syntax + EOE020 constraint vs CLR-type mismatch:
     ValidateRoutePattern + ValidateConstraintTypes/SingleRouteConstraint
     + ValidateCatchAllConstraint/TypedConstraint + IsFormatOnlyConstraint
     + DoesTypeMatchConstraint + Type-name helpers)
  Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs     104 LOC
    (EOE006 multi-body counting via ErrorOrContext, EOE009 accepted-on-readonly,
     EOE039 DataAnnotations reflection check + InheritsFrom)

Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…omain classes

Splits the 563-line MiddlewareEmissionTests.cs along its #region boundaries
into three cohesive test classes. Keeps the critical security-regression tests
(wrapper-does-not-drop-Authorize) co-located with metadata in the third class.

Before:
  tests/ErrorOrX.Generators.Tests/MiddlewareEmissionTests.cs       563 LOC (22 tests)

After:
  tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs        198 LOC
    (Combined-middleware smoke test + 7 [Authorize]/[AllowAnonymous] tests)
  tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs 176 LOC
    ([EnableRateLimiting]/[DisableRateLimiting] override + [OutputCache] variants)
  tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs       180 LOC
    ([EnableCors]/[DisableCors] + .WithName/.WithTags + Security_*_NotLost_InWrapper)

Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ining classes

Splits the 565-line ErrorOrTests.cs (the misnamed grab-bag in the test project)
into three cohesive classes matching the existing ErrorOr.X.cs naming
convention used by every other file in the directory.

Before:
  tests/ErrorOrX.Tests/ErrorOr/ErrorOrTests.cs               565 LOC (30 tests)

After:
  tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs        283 LOC
    (Error.Metadata + IsError/IsSuccess + Value access + Errors access
     + Implicit conversion + Error.Failure/Validation/NotFound/Conflict/
     Unauthorized/Forbidden/Unexpected ErrorType assignment)
  tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs  98 LOC
    (Match transformation + Switch side-effect)
  tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs      159 LOC
    (Then / ThenDo + sync Else + FailIf — async variants stay in
     ErrorOr.ElseAsyncTests.cs and ErrorOr.FailIfAsyncTests.cs)

Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

Walkthrough

Large-scale refactoring partitioning ErrorOrEndpointAnalyzer and ErrorOrEndpointGenerator monolithic classes into focused partial files, introducing comprehensive internal data model types, and reorganizing test coverage from monolithic diagnostic-only test files into focused test classes per feature/binding source. No functional generation logic changed; this is architectural reorganization with new model definitions.

Changes

Analyzer Infrastructure Refactoring

Layer / File(s) Summary
Body-Source Counting and Validation Attribute Detection
src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs
New partial analyzer logic counting body-source buckets from [FromBody]/[FromForm] attributes and ErrorOrContext-recognized body types; detects ValidationAttribute inheritance and reports ValidationUsesReflection; includes base-type inheritance traversal helper.
Route Constraint and Pattern Validation
src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs
New partial analyzer logic validating route constraint type compatibility (catch-all string requirement, typed constraint resolution via RouteValidator.ConstraintToTypes), nullable unwrapping, and route pattern syntax (brace matching, empty param names, duplicates).
Analyzer Main File Partial Declaration
src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs
Converted ErrorOrEndpointAnalyzer to partial class; updated documentation; removed helper method implementations now in other partials (validation, route constraint, body-source counting, type normalization).

Generator Initialization and Extraction

Layer / File(s) Summary
Error Type Inference from Method Bodies
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs
New extractor partial recursively walking method syntax to collect Error.X() factory invocations and Error.Custom(code, ...) codes; detects same-assembly symbol references for recursive traversal; deduplicates custom errors by code; emits UnknownErrorFactory diagnostics.
Interface/Abstract Call Detection and ReturnsError Extraction
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs
New extractor partial detecting invocations of interface/abstract/virtual methods returning ErrorOr<T>; extracts [ReturnsError] metadata including ErrorType enum mapping to string names; emits UndocumentedInterfaceCall diagnostic when neither [ReturnsError] nor endpoint [ProducesError] present.
Extractor Main File Return-Type Extraction
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
Updated extractor partial to focus on return-type extraction; removed large error-inference implementation moved to ErrorInference partial; preserved async/SSE shape unwrapping and success-kind/ID property detection.
Endpoint Flow Discovery and Attribute Wiring
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs
New partial creating incremental syntax providers per HTTP method and ErrorOrEndpointAttribute; validates method eligibility via SemanticGuard; constructs EndpointDescriptor by binding route parameters, validating patterns/constraints, extracting metadata and middleware configuration.
Post-Initialization Attribute Source Emission
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs
New partial emitting compile-time attribute type definitions (ErrorOrEndpointAttribute, HTTP method markers, ProducesError/AcceptedResponse/ReturnsError/RouteGroup helpers) into consumer's ErrorOr namespace via AddSource.
Initialize Main File Core Wiring
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cs
Updated initialize partial to contain only entry-point wiring and emission/analysis stages; removed endpoint discovery/provider combination/attribute emission/flow implementation moved to partials; simplified class focus.

Parameter Binding Classification and Metadata Extraction

Layer / File(s) Summary
Binding Source Classifiers (Route, Query, Header, Form, AsParameters)
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs
New partial implementing per-source classifiers validating binding compatibility (primitive support, TryParse custom bindings, collection shapes); emits specific diagnostics per source mismatch; includes recursive DTO handling for complex form parameters and [AsParameters] validation (nullability, constructor, nested forbidding).
Parameter Metadata Extraction and Flag Building
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs
New partial extracting parameter metadata: binding flags from attributes and nullability rules, special parameter kind detection (HttpContext, CancellationToken, Stream, form files), bound name resolution from binding attributes, keyed-services extraction, collection item typing, custom binding and validation property collection.
Parameter Binding Entry Point
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
Updated parameter-binding partial to act as entry point and classification dispatcher; updated documentation; removed parameter meta construction and per-binding classifier implementations moved to other partials.

Code Emission: Versioning, Invoker, and Error Handling

Layer / File(s) Summary
API Versioning Emission
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs
New emitter partial computing global version set from all endpoints (deduplicated and sorted), emitting HasApiVersion/HasDeprecatedApiVersion/ReportApiVersions().Build() calls; per-endpoint emits IsApiVersionNeutral() or WithApiVersionSet(versionSet)/MapToApiVersion(...) chaining.
Per-Endpoint Invoker and Core Method Emission
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs
New emitter partial generating typed wrapper/core methods, parameter binding code dispatch, optional BCL Validator.TryValidateObject validation, handler invocation (sync/async), error handling for SSE/union-based/minimal IsError/Errors/Value patterns; includes bind-failure helpers returning ValidationProblem or BadRequest<ProblemDetails> and 415 form content-type guard.
Error Handling and Success Response Emission
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs
New emitter partial generating first-error-based control flow: early ValidationProblem return for Validation typed errors, ProblemDetails construction from error code/description/status mapping, error-type switch returning typed result factories; includes success-path Location header generation for POST 201 responses with ID properties.
Emitter Main File Documentation Update
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs
Updated emitter partial documentation enumerating versioning/invoker/error-handling partials and existing JSON context/options/support partials; no generation logic changes.

Binding Code Generation: Body, Query, Header, and Parsing

Layer / File(s) Summary
Body and Form Binding Code Generation
src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cs
New partial emitting JSON body binding with empty-body handling (allow/disallow via nullability), ContentLength/content-type checks, ReadFromJsonAsync with JsonException mapping; form binding for composite DTOs (child binding + constructor invocation) and scalar parameters (field extraction + try-parse).
Query and Header Parameter Binding Code Generation
src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs
New partial emitting query/header bindings for collections (string filtering, per-item parsing, array vs. list materialization) and scalars (presence via TryGetQueryValue/Headers.TryGetValue, parsing, required/nullable handling, defaults).
Parsing Expression Generation and Validation Dict Building
src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs
New partial providing type-specific TryParse/TryParseWithFormat expression strings (numeric, Guid, Uri, date/time with culture/format-provider), argument expression building per source/nullability, and validation error dictionary aggregation pattern.
Binding Code Emitter Main File Partial Declaration
src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs
Converted BindingCodeEmitter to internal static partial class with documentation; no implementation changes.

OpenAPI Transformer: Metadata Extraction and Emission

Layer / File(s) Summary
OpenAPI Metadata Extraction from Endpoints and Types
src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cs
New extractor partial extracting endpoint HTTP method/pattern, XML doc summary/remarks/param tags, parameter definitions (binding attributes, primitives, required/optional semantics), filtering framework types, mapping CLR types to OpenAPI schema type/format, and type-level XML metadata.
OpenAPI Document/Operation/Schema Transformer Emission
src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs
New emitter partial generating per-tag IOpenApiDocumentTransformer for document.Tags population, IOpenApiOperationTransformer with frozen dictionaries for operation/parameter metadata and handler parameter definitions, IOpenApiSchemaTransformer with type-to-description mapping, and GeneratedOpenApiExtensions DI registration.
OpenAPI Transformer Main File Partial Declaration
src/ErrorOrX.Generators/OpenApiTransformerGenerator.cs
Converted OpenApiTransformerGenerator to public sealed partial class; removed large XML parsing/extraction/emission implementations now in partials; retained incremental generator initialization wiring.

Internal Data Models

Layer / File(s) Summary
Endpoint Descriptor and Response Classification Models
src/ErrorOrX.Generators/Models/EndpointDescriptor.cs
New file introducing EndpointDescriptor (HTTP routing, handler identity, parameters, error/SSE/versioning/middleware), SuccessKind/SuccessResponseInfo for typed responses, UnionTypeResult for union-type compatibility, and helper properties for HTTP method, body/form/binding/validation detection.
Parameter Binding and Analysis Models
src/ErrorOrX.Generators/Models/EndpointParameters.cs
New file introducing EndpointParameter record struct for analyzed parameter details, ParameterMeta with computed flag properties, ParameterBindingResult for analysis validity, and enums for binding sources, special parameter kinds, custom binding methods, empty-body behavior.
Method Analysis and Error Inference Models
src/ErrorOrX.Generators/Models/MethodAnalysis.cs
New file introducing MethodAnalysis aggregating return-type analysis, error inference, middleware; ErrorOrReturnTypeInfo with async/SSE detection; CustomErrorInfo, ProducesErrorInfo, SseInfo, ErrorInferenceInfo for error metadata aggregation.
Middleware and Versioning Configuration Models
src/ErrorOrX.Generators/Models/MiddlewareInfo.cs, src/ErrorOrX.Generators/Models/VersioningModels.cs
New files introducing MiddlewareInfo for authorization/rate-limiting/caching/CORS flags and policies; ApiVersionInfo and VersioningInfo for version configuration with computed HasVersioning and EffectiveVersions.
OpenAPI, Route, and Validation Metadata Models
src/ErrorOrX.Generators/Models/OpenApiModels.cs, src/ErrorOrX.Generators/Models/RouteModels.cs, src/ErrorOrX.Generators/Models/ValidationModels.cs
New files introducing immutable record structs for OpenAPI endpoint/parameter/schema/context, route parameter/constraint/group metadata, and validation attribute/property/type descriptors supporting generator metadata extraction.

Test Reorganization

Layer / File(s) Summary
Parameter Binding Type Validation Tests
tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs
New test class covering EOE010–EOE017 (invalid [FromRoute]/[FromQuery]/[FromHeader] types, invalid [AsParameters] usage) via inline source examples.
JSON AOT Validation Tests
tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs
New test class covering EOE007, EOE025, EOE039, EOE041 (missing JSON context types, CamelCase policy, DataAnnotations validation, ProblemDetails inclusion).
Parameter Binding Inference and Special Cases
tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs, tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs, tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs, tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs
New focused test classes covering smart body/service/route/query inference, explicit binding attributes, route parameter matching, custom TryParse, GUIDs, collections, nullability, and special types (HttpContext, CancellationToken, Stream).
Route and Body Validation Tests
tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs
New test class covering EOE003–EOE021 (route parameter binding, patterns, multiple body sources, anonymous/private/generic return types, constraint mismatches, complex type ambiguity).
Middleware Emission Tests
tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs, tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs, tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs
New focused test classes covering [Authorize], [AllowAnonymous], [EnableCors]/[DisableCors], [EnableRateLimiting]/[DisableRateLimiting], [OutputCache] with endpoint naming, tags, and security preservation.
Union Type, Error Factory, and Naming Tests
tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs, tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs
New test classes covering EOE022–EOE024 (unknown factories, too many result types, undocumented interface calls) and EOE033 (method naming PascalCase validation).
ErrorOr Unit Tests
tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs, tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs, tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
New test classes covering ErrorOr<T> chaining (Then, ThenDo, Else, FailIf) and pattern matching (Match, Switch); reorganized access tests (state, value/error access, implicit conversions, ErrorType assignment).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

  • ANcpLua/ErrorOrX#32: Introduces ValidationResolverEmitter and ErrorOrContext validatable-property infrastructure supporting DataAnnotations without reflection—this PR builds on that foundation by emitting validation calls and detection logic across multiple partials.

Suggested labels

area:generated, area:test

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/split-oversized-files
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch refactor/split-oversized-files
  • 🛠️ architecture hardening
  • 🛠️ security pass
  • 🛠️ test gap closure
  • 🛠️ docs and changelog alignment
  • 🛠️ performance and allocation pass

@codacy-production
Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 135 complexity · 2 duplication

Metric Results
Complexity 135
Duplication 2

View in Codacy

AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes.

Copy link
Copy Markdown

@codacy-production codacy-production Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This refactor successfully achieves the 500-LOC threshold across the codebase, but it introduces several high-severity functional bugs that must be resolved. Key concerns involve the analyzer logic for request body identification and a failure to correctly resolve binding attribute names when using positional arguments.

Furthermore, the review identified a critical gap in the acceptance criteria: the 'OpenAPI transformer generation' test scenarios are missing, leaving the refactored emitter and extractor logic unverified. Given the identified type mismatches and logic errors in these files, this PR is not yet ready for merging despite being up to Codacy standards.

About this PR

  • The refactoring of the OpenApiTransformerGenerator lacks explicit test scenarios in the current diff. Given that logic errors were found in both the Extractor and Emitter components of this generator, existing test suites may not be granular enough to verify the new file structure.

Test suggestions

  • Verify route pattern and route constraint validation diagnostics (EOE005, EOE003, EOE020)
  • Verify parameter binding logic for implicit vs explicit sources (Route, Query, Header, Service, Body)
  • Verify middleware emission for security-critical attributes (Authorize, AllowAnonymous, CORS)
  • Verify JSON AOT safety diagnostics (EOE007, EOE025, EOE041)
  • Verify error inference from method body walking (Error.X() vs Error.Custom())
  • Verify OpenAPI transformer generation for tags, operations, and schemas
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verify OpenAPI transformer generation for tags, operations, and schemas

TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback

code.AppendLine(" {");
// OpenApiDocument.Tags setter auto-wraps with OpenApiTagComparer.Instance
// which handles deduplication by Name - no manual .Any() check needed
code.AppendLine(" document.Tags ??= new HashSet<OpenApiTag>();");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

The Tags property is an IList<OpenApiTag>. Initialize it as a List<OpenApiTag> and check if a tag with the same name already exists before adding it to avoid duplicates and type mismatch errors.

return false;
}

private static (string? Location, string? Name) GetExplicitBinding(ISymbol param)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

The bound name should be resolved from ConstructorArguments if it is not present in NamedArguments to support attributes like [FromRoute("id")].

/// </summary>
public sealed partial class ErrorOrEndpointAnalyzer
{
private static int CountBodySources(IMethodSymbol method)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

The body source counting logic should account for implicit DTO parameters (complex types not bound elsewhere) and must verify that parameters of type Stream or IFormFile are not explicitly bound to other sources like [FromServices] or [FromRoute] before counting them as body sources.

{
// Error.Custom(int type, string code, string description, Dictionary<string, object>? metadata = null)
// The 'code' parameter (second arg) is what we want for deduplication
var args = invocation.ArgumentList.Arguments;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM RISK

Suggestion: The extraction of the 'code' argument using index 1 is fragile. It assumes the error code is always the second argument. Consider using the semantic model to correctly identify the 'code' parameter regardless of its position at the call site.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs (2)

27-35: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Inferred body parameters bypass the multi-body guard.

BindParameters only counts explicit body/form/stream flags, but InferParameterSource can still classify multiple complex parameters as ParameterSource.Body on POST/PUT/PATCH. That allows an invalid multi-body endpoint through without EOE006/EOE008.

Suggested fix (validate body-source categories after classification)
 private static ParameterBindingResult BuildEndpointParameters(
     ParameterMeta[] metas,
     ImmutableArray<IParameterSymbol> parameters,
     ImmutableHashSet<string> routeParameters,
     ISymbol method,
     ImmutableArray<DiagnosticInfo>.Builder diagnostics,
     ErrorOrContext context,
     HttpVerb httpVerb)
 {
     var builder = ImmutableArray.CreateBuilder<EndpointParameter>(metas.Length);
     var isValid = true;
+    var bodyParameters = 0;
+    var hasForm = false;
+    var hasStream = false;

     for (var i = 0; i < metas.Length; i++)
     {
         var result = ClassifyParameter(in metas[i], parameters[i], routeParameters, method, diagnostics, context,
             httpVerb);
         if (result.IsError)
         {
             isValid = false;
             continue;
         }

         builder.Add(result.Parameter);
+        switch (result.Parameter.Source)
+        {
+            case ParameterSource.Body:
+                bodyParameters++;
+                break;
+            case ParameterSource.Form:
+            case ParameterSource.FormFile:
+            case ParameterSource.FormFiles:
+            case ParameterSource.FormCollection:
+                hasForm = true;
+                break;
+            case ParameterSource.Stream:
+            case ParameterSource.PipeReader:
+                hasStream = true;
+                break;
+        }
     }

+    if (bodyParameters > 1 || (bodyParameters > 0 ? 1 : 0) + (hasForm ? 1 : 0) + (hasStream ? 1 : 0) > 1)
+    {
+        diagnostics.Add(DiagnosticInfo.Create(
+            Descriptors.MultipleBodySources, method.Locations.FirstOrDefault() ?? Location.None, method.Name));
+        return ParameterBindingResult.Invalid;
+    }
+
     return isValid
         ? new ParameterBindingResult(IsValid: true, builder.ToImmutable().AsEquatableArray())
         : ParameterBindingResult.Invalid;
 }

As per coding guidelines, “Binding validation must reject multiple [FromBody] parameters (EOE006, EOE008) and detect ambiguous bindings (EOE021).”

Also applies to: 173-194


196-205: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

EOE021 branch should fail classification instead of silently rebinding to service.

After emitting Descriptors.AmbiguousParameterBinding, the method currently returns a successful ParameterSource.Service classification. This hides an invalid binding behind generated service injection.

Suggested fix
             diagnostics.Add(DiagnosticInfo.Create(
                 Descriptors.AmbiguousParameterBinding,
                 method.Locations.FirstOrDefault() ?? Location.None,
                 meta.Name,
                 meta.TypeFqn,
                 httpVerb.ToHttpString()));

-            return ParameterSuccess(in meta, ParameterSource.Service);
+            return ParameterClassificationResult.Error;

As per coding guidelines, “Complex types in GET/DELETE endpoints without explicit [FromQuery] binding must emit EOE021 diagnostic error.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs`
around lines 196 - 205, After emitting Descriptors.AmbiguousParameterBinding you
must not return a successful binding; replace the current return of
ParameterSuccess(in meta, ParameterSource.Service) with a failure classification
for this parameter so the caller sees an invalid binding (i.e., return the
method's existing "parameter failure" result—use the same failure helper your
generator provides such as ParameterFailure/ParameterError or the equivalent
factory used elsewhere—to mark the parameter as failed due to EOE021). Ensure
you reference the same meta value and emit the EOE021 diagnostic already added
via diagnostics.Add(Descriptors.AmbiguousParameterBinding, ...).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs`:
- Around line 69-85: The current loop only checks param.GetAttributes() so
EOE039 misses ValidationAttributes on DTO properties; update the logic around
the parameter inspection (the foreach over method.Parameters and the
InheritsFrom(...) check against validationAttributeType used when reporting
Descriptors.ValidationUsesReflection) to also traverse the parameter type's
reachable properties and their attributes (starting from the parameter's
ITypeSymbol/INamedTypeSymbol), using a visited set to protect against cycles,
and treat any property attribute that InheritsFrom(validationAttributeType) as
equivalent to a parameter attribute so the analyzer reports the EOE039
diagnostic (use param.Locations.FirstOrDefault() or
method.Locations.FirstOrDefault() as before) once per parameter.

In
`@src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs`:
- Around line 107-131: The validator currently unwraps and compares types by FQN
strings in ValidateConstraintTypes/DoesTypeMatchConstraint (using
TypeNamesMatch), which can misrepresent semantics; update the call-site in
AnalyzeEndpoint to pass the method symbol (or extend RouteMethodParameterInfo to
include the resolved ITypeSymbol for each parameter) so validation can resolve
parameter types via ErrorOrContext.UnwrapNullable(ITypeSymbol) and perform
comparisons with ITypeSymbol.Equals (or SymbolEqualityComparer) instead of
string equality; adjust ValidateConstraintTypes and DoesTypeMatchConstraint to
accept ITypeSymbol(s) and use Roslyn type equality for constraint matching.

In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs`:
- Around line 54-67: The current gating uses hasValidation which only checks
ep.ErrorInference.InferredErrorTypeNames, so endpoints that declare a Validation
error but don't infer it are missing the 400 ValidationProblem path; update the
condition to consider declared/contract error types as well (e.g., check
ep.DeclaredErrorTypeNames or the collection that represents the endpoint's
declared errors in addition to ep.ErrorInference.InferredErrorTypeNames) so that
hasValidation becomes true if either inferred OR declared error type names
contain ErrorMapping.Validation; apply the same change to the other similar
guards around lines 89-106; keep the existing generation blocks
(BindingCodeEmitter.EmitValidationDictBuilder, the Type comparison to
WellKnownTypes.Fqn.ErrorType.Validation, and the ctx.WrapReturn call) unchanged
so only the presence check is broadened.

In `@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs`:
- Around line 151-167: The generated code currently dereferences {paramName}!
when calling {WellKnownTypes.Fqn.Validator}.TryValidateObject which will throw
on null; add a null-check for each validation target (from validationParams /
paramName) before calling TryValidateObject: if the parameter is null, build the
same validationDict indicating a required/null error for that parameter and
return the appropriate ValidationProblem (use the same isAsync-aware returnExpr
pattern used below), otherwise proceed to call TryValidateObject with the
non-null instance; ensure you reference the same
BindingCodeEmitter.EmitValidationDictBuilder/validationDict and the isAsync
return branches so behavior and return type remain consistent.

In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs`:
- Around line 195-204: The GetBodyToScan method currently falls back to
returning the original syntax node for unknown kinds, causing full-subtree
scans; change the default branch in GetBodyToScan to return null instead of
syntax so callers skip unsupported constructs (i.e., replace "_ => syntax" with
"_ => null"), and update any callers to handle a null return if they don't
already; alternatively, if keeping the catch-all is intentional, add a comment
above GetBodyToScan explaining that the default intentionally returns the full
node for unknown syntax kinds and why.
- Around line 35-36: The conditional checking refs for emptiness is redundant:
remove the "|| refs.Length is 0" portion and keep only the IsDefaultOrEmpty
check so the early-return uses "if (refs.IsDefaultOrEmpty) return null;"; update
the code where refs is assigned from method.DeclaringSyntaxReferences (in
ErrorOrEndpointGenerator.Extractor.ErrorInference) to rely solely on
refs.IsDefaultOrEmpty to determine emptiness.
- Around line 124-133: The recursion uses a single semanticModel bound to the
handler's SyntaxTree which fails for syntax nodes from other files; update the
logic around GetBodyToScan and CollectErrorTypesRecursive so you obtain the
correct semantic model per body by calling
Compilation.GetSemanticModel(bodyToScan.SyntaxTree) and use that semantic model
for all subsequent symbol/constant lookups; change the recursive signature of
CollectErrorTypesRecursive (and any callers) to accept the Compilation instead
of a fixed SemanticModel, and inside each recursion call fetch the per-tree
SemanticModel before calling GetSymbolInfo or GetConstantValue to ensure
cross-file helper methods, interface calls, and constant folding are detected
correctly.

In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs`:
- Around line 90-107: The ReturnsErrorAttribute first constructor
(ReturnsErrorAttribute(global::ErrorOr.ErrorType errorType, string errorCode))
never initializes StatusCode; explicitly set StatusCode = null in that
constructor to mirror the second constructor's behavior and avoid analyzer
warnings (touch the ReturnsErrorAttribute constructors and the
StatusCode/ErrorType properties to ensure symmetry).

In `@src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs`:
- Around line 221-240: The code appends parameter definitions from ParameterDefs
into operation.Parameters without checking for existing parameters, causing
duplicate (Name, In) entries; update the block that handles ParameterDefs for a
given operationId to first ensure operation.Parameters is initialized, then for
each param tuple from ParameterDefs check operation.Parameters for an existing
OpenApiParameter with the same Name and In (match on Name and In), if found
merge/update its Description/Schema/Required as appropriate, otherwise add a new
OpenApiParameter; refer to ParameterDefs, operation.Parameters, operationId and
OpenApiParameter when making this change.

In `@src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cs`:
- Around line 353-387: The ExtractTypeMetadata method currently accepts generic
type declarations and later emits invalid typeof(...) for open generics; update
ExtractTypeMetadata to skip types with generic arity by checking the
INamedTypeSymbol's Arity (symbol.Arity > 0) and returning null for those symbols
(add the check after obtaining the symbol and before producing the
TypeMetadataInfo), so only non-generic types proceed to
GetReflectionFullName/ConvertToTypeofExpression.
- Around line 150-153: The code currently makes decisions by passing
param.Type.ToDisplayString() into
IsSkippedParameterType/IsPrimitiveType/GetOpenApiSchema and manually stripping
"?" which is brittle; instead pass the ITypeSymbol (param.Type) directly, call
ErrorOrContext.UnwrapNullable() on that symbol before inspection, update
IsSkippedParameterType, IsPrimitiveType, and GetOpenApiSchema to accept and
operate on ITypeSymbol (not strings), and replace string checks with
SymbolEqualityComparer.Default.Equals(...) against known type symbols; remove
the manual EndsWithOrdinal("?") nullable stripping and apply these symbol-based
changes to all call sites of those helpers within the extractor.

In `@src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cs`:
- Around line 31-35: The code currently treats ctx.Request.ContentLength being
null as "no body", which breaks for chunked requests; replace that
ContentLength-based check with the ASP.NET Core request-body detection feature:
acquire IHttpRequestBodyDetectionFeature via
ctx.Request.HttpContext.Features.Get<IHttpRequestBodyDetectionFeature>() and use
its HasBody boolean to decide whether to parse or set {paramName} = default;
update both places that check ctx.Request.ContentLength (including the
occurrences around the if/else and the similar block at lines 55-56) and ensure
you handle a null feature reference by treating it conservatively (e.g., assume
HasBody == true unless explicitly false).

In `@src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs`:
- Around line 130-134: The header-collection emitter currently skips failed
parses (in the code path built by BindingCodeEmitter.Query.cs using
GetTryParseExpression and adding to {paramName}List), causing silent data loss;
change the emitted branch for non-string itemType so that when
GetTryParseExpression returns false you do not ignore the item but instead fail
the bind (e.g., set a parse failure flag / add a model error / return a failed
BindResult) so header binding mirrors query collection behavior; update the
branch around the generated parsedItem handling to produce a bind failure on
parse error rather than simply not adding to {paramName}List.
- Around line 14-15: EmitBindAsyncBinding currently always emits a
single-argument call but needs to emit a two-argument call when
param.CustomBinding == CustomBindingMethod.BindAsyncWithParam; update
EmitBindAsyncBinding to accept (or obtain from the existing param) the handler
method's ParameterInfo reflection metadata and, when
CustomBindingMethod.BindAsyncWithParam is detected, emit
"{baseType}.BindAsync(ctx, <ParameterInfoExpr>)" instead of
"{baseType}.BindAsync(ctx)"; make sure the emitter uses the same param/paramName
context passed from the dispatcher so the correct ParameterInfo for the handler
parameter is used and keep the existing bindFailFn handling.

In `@src/ErrorOrX.Generators/Models/VersioningModels.cs`:
- Line 23: The HasVersioning getter currently checks only SupportedVersions and
IsVersionNeutral and therefore ignores MappedVersions; update HasVersioning so
it also treats a non-empty MappedVersions as evidence of versioning by including
a check for MappedVersions.IsDefaultOrEmpty (i.e., return true if
SupportedVersions is non-empty OR MappedVersions is non-empty OR
IsVersionNeutral), touching the HasVersioning property and referencing
SupportedVersions, MappedVersions, and IsVersionNeutral.

In `@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs`:
- Around line 105-125: The test Authorize_With_Roles_Emits_Roles is too
permissive because generated.Should().Match("*.RequireAuthorization*Admin*")
only asserts a loose substring; update the assertion to verify observable auth
configuration containing explicit role information (e.g. assert the generated
string contains either RequireAuthorization with an AuthorizeAttribute
specifying Roles = "Admin,Manager" or RequireAuthorization/RequireRole calls
that include both "Admin" and "Manager" exactly), using a precise
string.Contains or a tighter regex match that ensures both role names appear in
the same authorization call; apply the same tightening to the other related
test(s) around lines 177-197 so they assert role/scheme metadata is present
rather than just RequireAuthorization appearing.
- Around line 152-174: The test AllowAnonymous_Overrides_Authorize only asserts
the presence of ".AllowAnonymous()" but must also assert the absence of
authorization emission; update the test (in
MiddlewareEmissionAuthorizationTests.cs, inside the
AllowAnonymous_Overrides_Authorize method after obtaining generated from
ErrorOrEndpointMappings.cs via RunAsync) to assert that the generated content
does NOT contain any ".RequireAuthorization" call (e.g., use
Should().NotContain(".RequireAuthorization") or equivalent negative assertion)
so the test fails if RequireAuthorization is still emitted.

In `@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs`:
- Around line 56-76: The test DisableCors_Emits_DisableCors currently only
checks for a generic "Cors" token which matches both enable and disable paths;
update the assertion to validate the exact disable semantics by asserting the
generated API contains the explicit disable marker emitted by the generator
(e.g. the attribute or mapping representing disable CORS) rather than a
wildcard. Replace generated.Should().Match("*Cors*") in
DisableCors_Emits_DisableCors with a precise check on the generated string (for
example generated.Should().Contain("[DisableCors]") or the exact snippet the
generator emits into ErrorOrEndpointMappings.cs) and optionally assert that any
enable-CORS marker is not present to ensure correct disable behavior.

In
`@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs`:
- Around line 133-175: The two tests OutputCache_With_Duration_Emits_Duration
and OutputCache_With_VaryByQueryKeys_Emits_VaryBy currently only assert that
some CacheOutput call exists, so update each test to assert the emitted code
preserves the specific configuration: inspect the generated string (variable
generated from the file with HintName "ErrorOrEndpointMappings.cs") for the
concrete duration or vary-by-query representation (e.g., ensure the generated
text contains the duration value/setting such as "60" or a policy builder call
that sets Duration = 60 for OutputCache_With_Duration_Emits_Duration, and ensure
it contains the VaryByQueryKeys list or an equivalent builder call for
OutputCache_With_VaryByQueryKeys_Emits_VaryBy); modify the assertions on the
generated variable in those two test methods to look for those specific tokens
rather than just "*.CacheOutput*".

In `@tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs`:
- Around line 203-224: The test Complex_Type_With_Explicit_FromQuery_NoWarning
is asserting that diagnostic "EOE011" is produced, so rename the test method to
reflect that a warning is expected (for example
Complex_Type_With_Explicit_FromQuery_EmitsEOE011 or
Complex_Type_With_Explicit_FromQuery_ReportsEOE011) to match the assertion on
result.Diagnostics.Should().ContainSingle(d => d.Id == "EOE011"); update only
the method name of the test (the method signature on the Fact) so callers like
the test runner and references such as RunAsync and result.Diagnostics remain
unchanged.

In `@tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs`:
- Around line 27-30: The test currently asserts the emitter's temporary
parameter name by checking for the exact string "global::Api.GetInfo(p0)";
change this to assert observable behavior instead (e.g., that the generated
output contains a call to "global::Api.GetInfo(" or otherwise validates that
GetInfo is invoked) so the test no longer depends on emitter-local names; update
the assertion that references "global::Api.GetInfo(p0)" in
ParameterBindingSpecialTypesTests to use a name-agnostic check while keeping the
existing check for the HttpContext binding ("= ctx;") intact.

In `@tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs`:
- Around line 97-114: The test
EOE024_Interface_Call_With_ProducesError_No_Diagnostic is exercising the
generator fallback by placing [ProducesError(404, "NotFound")] on the handler
instead of the interface; move the attribute to the interface method so the
generator's interface metadata extraction is exercised: add [ProducesError(404,
"NotFound")] to ITodoService.GetById and remove it from TodoApi.GetById,
ensuring the generator path that invokes TryExtractReturnsErrorAttributes in
InterfaceDetection.cs is covered.

---

Outside diff comments:
In `@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs`:
- Around line 196-205: After emitting Descriptors.AmbiguousParameterBinding you
must not return a successful binding; replace the current return of
ParameterSuccess(in meta, ParameterSource.Service) with a failure classification
for this parameter so the caller sees an invalid binding (i.e., return the
method's existing "parameter failure" result—use the same failure helper your
generator provides such as ParameterFailure/ParameterError or the equivalent
factory used elsewhere—to mark the parameter as failed due to EOE021). Ensure
you reference the same meta value and emit the EOE021 diagnostic already added
via diagnostics.Add(Descriptors.AmbiguousParameterBinding, ...).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 42ff8503-1b0d-4a5b-8607-27b9f70a9941

📥 Commits

Reviewing files that changed from the base of the PR and between df2a107 and 518fe8b.

📒 Files selected for processing (89)
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs
  • src/ErrorOrX.Generators/Models/EndpointDescriptor.cs
  • src/ErrorOrX.Generators/Models/EndpointModels.cs
  • src/ErrorOrX.Generators/Models/EndpointParameters.cs
  • src/ErrorOrX.Generators/Models/MethodAnalysis.cs
  • src/ErrorOrX.Generators/Models/MiddlewareInfo.cs
  • src/ErrorOrX.Generators/Models/OpenApiModels.cs
  • src/ErrorOrX.Generators/Models/RouteModels.cs
  • src/ErrorOrX.Generators/Models/ValidationModels.cs
  • src/ErrorOrX.Generators/Models/VersioningModels.cs
  • src/ErrorOrX.Generators/OpenApiTransformerGenerator.cs
  • tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/DiagnosticTests.cs
  • tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionTests.cs
  • tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingTests.cs
  • tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE010_Invalid_FromRoute_Type_Complex.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE011_Invalid_FromQuery_Type_Complex.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE012_Invalid_AsParameters_Type_Primitive.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE013_AsParameters_No_Constructor.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE014_Invalid_FromHeader_Type_Complex.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE016_Nested_AsParameters.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE017_Nullable_AsParameters.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE007_Type_Not_In_Json_Context.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE025_Missing_CamelCase_Policy.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE025_With_CamelCase_Policy_No_Diagnostic.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE039_Multiple_Validation_Attributes.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE039_Validation_Attribute_On_Parameter.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE041_Missing_ProblemDetails_In_JsonContext.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE041_No_Diagnostic_When_ProblemDetails_Present.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.EOE033_Method_Name_Lowercase_Start.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.EOE033_Method_Name_Snake_Case.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.EOE033_Method_Name_With_Underscore.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.Valid_Complex_Type_With_AsParameters.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.Valid_Complex_Type_With_FromBody_On_Post.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.Valid_Route_Parameter_Bound.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.Valid_Service_Type_Inferred.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE003_Route_Parameter_Not_Bound.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE003_Route_Parameter_With_Constraint_Not_Bound.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE005_Empty_Parameter_Name.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE005_Unclosed_Brace_In_Route.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE005_Unmatched_Close_Brace.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE006_Multiple_Body_Sources_FromBody_And_FromForm.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE006_Multiple_Body_Sources_Stream_And_FromBody.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE015_Anonymous_Return_Type.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE018_Private_Return_Type.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE019_Generic_Type_Parameter.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE020_Guid_Constraint_With_Int_Parameter.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE020_Int_Constraint_With_String_Parameter.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE021_Complex_Type_On_Delete_Without_Binding.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE021_Complex_Type_On_Get_Without_Binding.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/UnionTypeAndFactoryTests.EOE022_Too_Many_Result_Types.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/UnionTypeAndFactoryTests.EOE023_Unknown_Error_Factory.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/UnionTypeAndFactoryTests.EOE024_Interface_Call_With_ProducesError_No_Diagnostic.verified.txt
  • tests/ErrorOrX.Generators.Tests/Snapshots/UnionTypeAndFactoryTests.EOE024_Undocumented_Interface_Call.verified.txt
  • tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs
💤 Files with no reviewable changes (4)
  • src/ErrorOrX.Generators/Models/EndpointModels.cs
  • tests/ErrorOrX.Generators.Tests/DiagnosticTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingTests.cs
📜 Review details
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🧰 Additional context used
📓 Path-based instructions (11)
src/ErrorOrX.Generators/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/ErrorOrX.Generators/**/*.cs: Generated code must use ONLY IsError, Errors, and Value members from ErrorOr<T> to minimize interface coupling
Never emit ErrorOr<T>.Match() in generated code; use minimal interface with IsError check and Value/Errors access instead
Implement AOT Wrapper Pattern with two methods: a wrapper that returns Task<Results<...>> for OpenAPI metadata and a core method with handler logic
Generated endpoint wrappers must use (Delegate)Invoke_Ep* cast to force the Delegate overload for OpenAPI visibility
Generator must emit .RequireAuthorization() for [Authorize], .RequireRateLimiting() for [EnableRateLimiting], .CacheOutput() for [OutputCache], and .RequireCors() for [EnableCors] attributes
Parameter binding must follow the Smart Parameter Binding priority table: explicit attributes > special types > route parameters > primitive query > interface/abstract service > service naming patterns > POST/PUT/PATCH body > GET/DELETE error > service fallback
Complex types in GET/DELETE endpoints without explicit [FromQuery] binding must emit EOE021 diagnostic error
Map ErrorType to HTTP status codes according to RFC 9110: Validation=400, Unauthorized=401, Forbidden=403, NotFound=404, Conflict=409, Failure/Unexpected=500
Handler methods must be static and return ErrorOr<T>; reject non-static handlers with EOE002 and invalid return types with EOE001
Route validation must detect and error on unbound parameters (EOE003), duplicate routes (EOE004), and invalid route patterns (EOE005)
Binding validation must reject multiple [FromBody] parameters (EOE006, EOE008) and detect ambiguous bindings (EOE021)
Response types must be JSON serializable; check against the provided JsonSerializerContext with EOE007 and EOE025-026 for CamelCase/context issues
Union return types must not exceed the maximum allowed count (EOE022), must use known factories (EOE023), and all interfaces must be documented (EOE024)
AOT safety validation m...

Files:

  • src/ErrorOrX.Generators/Models/VersioningModels.cs
  • src/ErrorOrX.Generators/Models/OpenApiModels.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs
  • src/ErrorOrX.Generators/Models/MiddlewareInfo.cs
  • src/ErrorOrX.Generators/Models/RouteModels.cs
  • src/ErrorOrX.Generators/Models/ValidationModels.cs
  • src/ErrorOrX.Generators/Models/EndpointDescriptor.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Models/MethodAnalysis.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs
  • src/ErrorOrX.Generators/Models/EndpointParameters.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cs
  • src/ErrorOrX.Generators/OpenApiTransformerGenerator.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
**/*.cs

📄 CodeRabbit inference engine (.editorconfig)

**/*.cs: Suppress CA1062 (Validate arguments of public methods) in C# — Roslyn callbacks guarantee non-null parameters
Suppress CA1308 (Normalize strings to uppercase) in C# — lowercase is standard for URL routes
Suppress Meziantou analyzer rules (MA0002, MA0006, MA0008, MA0011, MA0048, MA0049, MA0004, MA0016, MA0051) in C# for opinionated style rules
Suppress RCS1084 (Use coalesce instead of conditional) in C# — the ternary form is idiomatic for lifting T? to ErrorOr via implicit conversion
Set pattern matching style rules (IDE0019, IDE0020, IDE0078, IDE0083, IDE0170) to suggestion severity in C# due to netstandard2.0 source generator compatibility constraints
Enable C# pattern matching style preferences (csharp_style_prefer_pattern_matching, csharp_style_prefer_not_pattern, csharp_style_prefer_extended_property_pattern) at suggestion level

**/*.cs: Use [Get], [Post], [Put], [Delete], [Patch] attributes on static handler methods to define ASP.NET Core Minimal API endpoints
Handler methods must be static and return ErrorOr<T> or ErrorOr<Result.{Marker}> types
Use error factory methods like Error.Validation(), Error.Unauthorized(), Error.Forbidden(), Error.NotFound(), Error.Conflict(), Error.Failure(), and Error.Unexpected() to create properly typed errors mapped to HTTP status codes
Use nullable-to-ErrorOr extensions like .OrNotFound(), .OrValidation(), .OrUnauthorized(), .OrForbidden(), .OrConflict(), .OrFailure(), and .OrUnexpected() to convert nullable values to ErrorOr results
Use fluent API methods (Then, Else, Match, Switch, FailIf) to chain operations in railway-oriented programming style
Handler method parameters must be inferred correctly: route parameters match {param} in the route, primitive types not in route become query parameters, complex types in POST/PUT/PATCH become body parameters, interfaces and DI types become service injections, and HttpContext, CancellationToken, IFormFile are...

Files:

  • src/ErrorOrX.Generators/Models/VersioningModels.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs
  • src/ErrorOrX.Generators/Models/OpenApiModels.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs
  • src/ErrorOrX.Generators/Models/MiddlewareInfo.cs
  • src/ErrorOrX.Generators/Models/RouteModels.cs
  • src/ErrorOrX.Generators/Models/ValidationModels.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs
  • tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs
  • src/ErrorOrX.Generators/Models/EndpointDescriptor.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs
  • tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cs
  • tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs
  • tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Models/MethodAnalysis.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs
  • tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs
  • src/ErrorOrX.Generators/Models/EndpointParameters.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cs
  • src/ErrorOrX.Generators/OpenApiTransformerGenerator.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs

⚙️ CodeRabbit configuration file

C#/.NET review. Treat warnings, nullable annotations, async flow, package boundaries, and generated code as correctness concerns. Flag sync-over-async (.Result, .Wait(), .GetAwaiter().GetResult()), fire-and-forget work without lifecycle/error handling, missing CancellationToken on public/internal async methods, DateTime.Now/UtcNow in domain logic instead of TimeProvider, null-forgiving ! without a documented proof, catch-and-fallback paths that hide failures, and service registration drift. Prefer data-driven, composable code over hardcoded switch/case tables. Public package/API surface changes need XML docs and explicit compatibility notes.

Files:

  • src/ErrorOrX.Generators/Models/VersioningModels.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs
  • src/ErrorOrX.Generators/Models/OpenApiModels.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs
  • src/ErrorOrX.Generators/Models/MiddlewareInfo.cs
  • src/ErrorOrX.Generators/Models/RouteModels.cs
  • src/ErrorOrX.Generators/Models/ValidationModels.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs
  • tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs
  • src/ErrorOrX.Generators/Models/EndpointDescriptor.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs
  • tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cs
  • tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs
  • tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Models/MethodAnalysis.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs
  • tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs
  • src/ErrorOrX.Generators/Models/EndpointParameters.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cs
  • src/ErrorOrX.Generators/OpenApiTransformerGenerator.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
**/src/**/*.cs

📄 CodeRabbit inference engine (.editorconfig)

Suppress ReSharper highlighting for enumerable parameter type suggestions and base type suggestions in src C# files

Files:

  • src/ErrorOrX.Generators/Models/VersioningModels.cs
  • src/ErrorOrX.Generators/Models/OpenApiModels.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs
  • src/ErrorOrX.Generators/Models/MiddlewareInfo.cs
  • src/ErrorOrX.Generators/Models/RouteModels.cs
  • src/ErrorOrX.Generators/Models/ValidationModels.cs
  • src/ErrorOrX.Generators/Models/EndpointDescriptor.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Models/MethodAnalysis.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs
  • src/ErrorOrX.Generators/Models/EndpointParameters.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cs
  • src/ErrorOrX.Generators/OpenApiTransformerGenerator.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
**/*.{cs,ts,tsx,py}

📄 CodeRabbit inference engine (Custom checks)

Fail C#/TypeScript/Python changes that introduce sync-over-async, unobserved fire-and-forget work, missing CancellationToken/AbortSignal propagation on public/internal async boundaries, sleeps for synchronization, or resource disposal paths that can drop in-flight work

Files:

  • src/ErrorOrX.Generators/Models/VersioningModels.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs
  • src/ErrorOrX.Generators/Models/OpenApiModels.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs
  • src/ErrorOrX.Generators/Models/MiddlewareInfo.cs
  • src/ErrorOrX.Generators/Models/RouteModels.cs
  • src/ErrorOrX.Generators/Models/ValidationModels.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs
  • tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs
  • src/ErrorOrX.Generators/Models/EndpointDescriptor.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs
  • tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cs
  • tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs
  • tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Models/MethodAnalysis.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs
  • tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs
  • src/ErrorOrX.Generators/Models/EndpointParameters.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cs
  • src/ErrorOrX.Generators/OpenApiTransformerGenerator.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
tests/ErrorOrX.Generators.Tests/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Generator snapshot tests must be located in ErrorOrX.Generators.Tests using the ANcpLua testing framework

Files:

  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs
  • tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs
  • tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs
  • tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs
  • tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs
  • tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs
tests/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Use AwesomeAssertions for fluent assertion syntax in test files

Suppress ReSharper highlighting for nullable warnings, unused positional properties, multiple enumeration, pure method return values, and redundant nullable suppressions in test C# files

Files:

  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs
  • tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs
  • tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs
  • tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs
  • tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs
  • tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
**/*Tests/**/*.cs

⚙️ CodeRabbit configuration file

.NET tests. Review that tests assert observable behavior, not implementation details; async tests return Task; timing-sensitive tests use deterministic signals/fakes instead of sleeps; Testcontainers resources are disposed; cancellation tokens are threaded; mocks are strict where the repo convention expects strictness; and new behavior has focused regression coverage. Do not accept weakening or deleting truthful tests to make a PR pass.

Files:

  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs
  • tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs
  • tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs
  • tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs
  • tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs
  • tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs
  • tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
tests/ErrorOrX.Tests/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Runtime unit tests must be located in ErrorOrX.Tests using xunit.v3

Files:

  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs
  • tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
src/ErrorOrX.Generators/**/*Emitter.cs

📄 CodeRabbit inference engine (src/ErrorOrX.Generators/CLAUDE.md)

src/ErrorOrX.Generators/**/*Emitter.cs: Generated code must use ONLY IsError, Errors, and Value properties when working with ErrorOr<T> - never emit Match() method calls
Use AOT wrapper pattern: wrapper method returns Task<Results<...>> for OpenAPI visibility, core method returns Task<IResult>, and MapGet uses (Delegate)Invoke_* cast to force Delegate overload

Files:

  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs
  • src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs
**/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs

📄 CodeRabbit inference engine (.editorconfig)

Suppress AL0029 (Use HasAttribute instead of foreach-over-GetAttributes) in ErrorOrEndpointGenerator.Extractor.cs — legitimate need to extract constructor and named arguments from AttributeData

Files:

  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
src/ErrorOrX.Generators/**/*ParameterBinding.cs

📄 CodeRabbit inference engine (src/ErrorOrX.Generators/CLAUDE.md)

src/ErrorOrX.Generators/**/*ParameterBinding.cs: Parameter binding classification must follow priority order: (1) Explicit attributes ([FromBody], [FromServices], etc.), (2) Special types (HttpContext, CancellationToken, IFormFile), (3) Route match, (4) Primitives (query binding), (5) Custom binding (TryParse/BindAsync), (6) Smart inference
Smart inference for parameter binding must detect service types including interfaces with Service suffix (ITodoService) and common DI patterns (*Repository, *Handler, *Manager, *Provider, *Factory, *Client, *DbContext)

Files:

  • src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
🔇 Additional comments (37)
tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs (1)

4-7: LGTM!

tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs (1)

1-160: LGTM!

tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs (1)

1-99: LGTM!

src/ErrorOrX.Generators/Models/EndpointDescriptor.cs (1)

1-183: LGTM!

src/ErrorOrX.Generators/Models/EndpointParameters.cs (1)

1-176: LGTM!

src/ErrorOrX.Generators/Models/MethodAnalysis.cs (1)

1-65: LGTM!

src/ErrorOrX.Generators/Models/MiddlewareInfo.cs (1)

1-26: LGTM!

src/ErrorOrX.Generators/Models/OpenApiModels.cs (1)

1-41: LGTM!

src/ErrorOrX.Generators/Models/RouteModels.cs (1)

1-40: LGTM!

src/ErrorOrX.Generators/Models/ValidationModels.cs (1)

1-31: LGTM!

tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs (1)

1-200: LGTM!

tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs (1)

1-204: LGTM!

tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs (1)

1-202: LGTM!

Also applies to: 225-250

tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs (1)

1-152: LGTM!

tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs (1)

1-171: LGTM!

tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs (1)

1-26: LGTM!

Also applies to: 31-74

tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs (1)

1-321: LGTM!

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs (4)

1-55: LGTM!


57-157: LGTM!


159-262: LGTM!


292-313: LGTM!

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs (2)

109-126: LGTM!


1-88: LGTM!

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cs (3)

22-55: LGTM!


57-99: LGTM!


101-133: LGTM!

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs (1)

20-295: LGTM!

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs (1)

12-163: LGTM!

tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs (1)

1-155: LGTM!

tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs (1)

13-94: LGTM!

Also applies to: 121-122

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs (1)

15-93: LGTM!

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs (1)

8-189: LGTM!

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs (1)

1-138: LGTM!

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs (2)

22-90: LGTM!

Also applies to: 128-174, 176-203


9-14: ⚡ Quick win

No issues identified. The sibling partial Extractor.Metadata.cs exists and the documentation reference is accurate.

src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs (1)

13-147: LGTM!

src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs (1)

3-38: LGTM!

Comment on lines +69 to +85
foreach (var param in method.Parameters)
{
foreach (var attr in param.GetAttributes())
{
if (attr.AttributeClass is null) continue;

// Check if the attribute inherits from ValidationAttribute
if (InheritsFrom(attr.AttributeClass, validationAttributeType))
{
context.ReportDiagnostic(Diagnostic.Create(
Descriptors.ValidationUsesReflection,
param.Locations.FirstOrDefault() ?? method.Locations.FirstOrDefault(),
param.Name,
method.Name));
break; // Only report once per parameter
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

EOE039 misses validation attributes on request DTO properties.

This only inspects param.GetAttributes(). The generator stack already collects validation properties for emitted DataAnnotations validation, so [Required]/[Range] on a body or query DTO will bypass the analyzer while still taking the reflection path at generation/runtime. Traverse the parameter type's reachable properties as well, with cycle protection, before deciding EOE039 is absent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs`
around lines 69 - 85, The current loop only checks param.GetAttributes() so
EOE039 misses ValidationAttributes on DTO properties; update the logic around
the parameter inspection (the foreach over method.Parameters and the
InheritsFrom(...) check against validationAttributeType used when reporting
Descriptors.ValidationUsesReflection) to also traverse the parameter type's
reachable properties and their attributes (starting from the parameter's
ITypeSymbol/INamedTypeSymbol), using a visited set to protect against cycles,
and treat any property attribute that InheritsFrom(validationAttributeType) as
equivalent to a parameter attribute so the analyzer reports the EOE039
diagnostic (use param.Locations.FirstOrDefault() or
method.Locations.FirstOrDefault() as before) once per parameter.

Comment on lines +107 to +131
// Get the actual type, unwrapping Nullable<T> for optional parameters
var actualTypeFqn = typeFqn.UnwrapNullable(rp.IsOptional || mp.IsNullable);

// Check if actual type matches any expected type
if (!DoesTypeMatchConstraint(actualTypeFqn, expectedTypes))
{
context.ReportDiagnostic(Diagnostic.Create(
Descriptors.RouteConstraintTypeMismatch,
attributeLocation,
rp.Name,
constraint,
expectedTypes[0],
mp.Name,
NormalizeTypeName(typeFqn)));
}
}

/// <summary>
/// Checks if an actual type matches any of the expected types for a constraint.
/// </summary>
private static bool DoesTypeMatchConstraint(string actualTypeFqn, IEnumerable<string> expectedTypes)
{
foreach (var expected in expectedTypes)
{
if (TypeNamesMatch(actualTypeFqn, expected))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== RouteMethodParameterInfo definition =="
rg -n -C3 'RouteMethodParameterInfo' src/ErrorOrX.Generators

echo
echo "== String-based type comparison / nullable unwrapping in generator sources =="
rg -n -C2 'TypeNamesEqual\(|UnwrapNullable\(|NormalizeTypeName\(' src/ErrorOrX.Generators

echo
echo "== ErrorOrContext nullable helpers =="
rg -n -C2 'class ErrorOrContext|UnwrapNullable' src/ErrorOrX.Generators

Repository: ANcpLua/ErrorOrX

Length of output: 32891


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Call sites of ValidateConstraintTypes in analyzer =="
rg -n -B2 -A2 'ValidateConstraintTypes\(' src/ErrorOrX.Generators/Analyzers/

echo
echo "== ErrorOrEndpointAnalyzer main entry point =="
rg -n 'AnalyzeSymbol|SymbolAnalysisContext' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | head -20

echo
echo "== How method symbol is accessed in analyzer =="
rg -n 'IMethodSymbol\|context\.' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs | head -15

Repository: ANcpLua/ErrorOrX

Length of output: 1504


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== AnalyzeMethod signature and how it uses context =="
rg -n -A30 'private static void AnalyzeMethod' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs

echo
echo "== What does SymbolAnalysisContext expose =="
rg -n 'context\.Symbol\|context\.Compilation' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | head -10

Repository: ANcpLua/ErrorOrX

Length of output: 1492


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full AnalyzeEndpoint method =="
rg -n -A50 'private static void AnalyzeEndpoint' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | head -80

echo
echo "== RouteMethodParameterInfo creation =="
rg -n -B5 -A10 'BuildRouteParameterLookup.*methodParams' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs

Repository: ANcpLua/ErrorOrX

Length of output: 2518


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== RouteBindingHelper.BindRouteParameters implementation =="
rg -n -A50 'public static.*BindRouteParameters' src/ErrorOrX.Generators/Validation/RouteBindingHelper.cs | head -70

echo
echo "== Where RouteMethodParameterInfo is constructed =="
rg -n -B3 -A3 'new RouteMethodParameterInfo' src/ErrorOrX.Generators/

Repository: ANcpLua/ErrorOrX

Length of output: 2973


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== EndpointParameter definition =="
rg -n -A15 'record.*EndpointParameter|struct.*EndpointParameter' src/ErrorOrX.Generators/Models/

echo
echo "== ErrorOrEndpointGenerator.BindParameters implementation (first 100 lines) =="
rg -n -A100 'public static.*BindParameters' src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs | head -120

Repository: ANcpLua/ErrorOrX

Length of output: 2235


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for BindParameters method =="
rg -n 'BindParameters' src/ErrorOrX.Generators/Core/ --type cs

echo
echo "== Search for where EndpointParameter is created from IParameterSymbol =="
rg -n -B3 -A8 'IParameterSymbol' src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs | head -100

Repository: ANcpLua/ErrorOrX

Length of output: 1656


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== RouteValidator.ValidateConstraintTypes (generator phase) =="
rg -n -A30 'public static.*ValidateConstraintTypes' src/ErrorOrX.Generators/Validation/RouteValidator.cs | head -50

echo
echo "== Check if RouteValidator uses symbols or strings =="
rg -n 'MatchesExpectedType\|TypeNamesEqual\|NormalizeTypeName' src/ErrorOrX.Generators/Validation/RouteValidator.cs

Repository: ANcpLua/ErrorOrX

Length of output: 1780


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== How is IMethodSymbol.Parameters used in the generator =="
rg -n 'method\.Parameters' src/ErrorOrX.Generators/Core/ | head -20

echo
echo "== Does RouteValidator have access to method symbol in generator phase? =="
rg -n -B5 'ValidateConstraintTypes.*method' src/ErrorOrX.Generators/Validation/RouteValidator.cs | head -30

echo
echo "== Can we retrieve parameter symbol from method given parameter name? =="
rg -n 'method\.Parameters.*FirstOrDefault\|method\.Parameters.*Where' src/ErrorOrX.Generators/

Repository: ANcpLua/ErrorOrX

Length of output: 2148


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Analyzer call: does it have access to method.Parameters? =="
rg -n -B10 -A10 'ValidateConstraintTypes.*in context' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | grep -A20 'AnalyzeEndpoint'

echo
echo "== Check if method symbol is available when ValidateConstraintTypes is called =="
rg -n -B15 'ValidateConstraintTypes\(in context' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs

echo
echo "== Full context of where ValidateConstraintTypes is invoked in analyzer =="
sed -n '110,150p' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs

Repository: ANcpLua/ErrorOrX

Length of output: 123


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Get lines around the ValidateConstraintTypes invocation in analyzer =="
cat -n src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | sed -n '130,160p'

echo
echo "== Get the full AnalyzeEndpoint method =="
cat -n src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | sed -n '87,175p'

Repository: ANcpLua/ErrorOrX

Length of output: 5796


Pass the method symbol to route-constraint validation and use Roslyn type comparison.

The analyzer calls ValidateConstraintTypes without passing method, forcing constraint validation to work entirely on string-based type FQNs. Line 108 unwraps via typeFqn.UnwrapNullable(...) and lines 127–135 compare via TypeNamesEqual(...), both violating the repo guidelines requiring symbol-based semantics.

The method symbol is available in AnalyzeEndpoint (line 89) but not forwarded to ValidateConstraintTypes (line 143). Either extend RouteMethodParameterInfo to carry the resolved ITypeSymbol for each parameter, or pass method explicitly and resolve parameter types within validation. Then use ErrorOrContext.UnwrapNullable(ITypeSymbol) and Roslyn ITypeSymbol.Equals() for constraint matching.

String-based type comparison can drift on generic aliases, nullable lifting semantics, and nested type normalization, leading to inconsistent EOE020 diagnostics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs`
around lines 107 - 131, The validator currently unwraps and compares types by
FQN strings in ValidateConstraintTypes/DoesTypeMatchConstraint (using
TypeNamesMatch), which can misrepresent semantics; update the call-site in
AnalyzeEndpoint to pass the method symbol (or extend RouteMethodParameterInfo to
include the resolved ITypeSymbol for each parameter) so validation can resolve
parameter types via ErrorOrContext.UnwrapNullable(ITypeSymbol) and perform
comparisons with ITypeSymbol.Equals (or SymbolEqualityComparer) instead of
string equality; adjust ValidateConstraintTypes and DoesTypeMatchConstraint to
accept ITypeSymbol(s) and use Roslyn type equality for constraint matching.

Comment on lines +54 to +67
var hasValidation = !ep.ErrorInference.InferredErrorTypeNames.IsDefaultOrEmpty &&
ep.ErrorInference.InferredErrorTypeNames.AsImmutableArray().Contains(ErrorMapping.Validation);

if (!hasValidation) return;

code.AppendLine($" if (first.Type == {WellKnownTypes.Fqn.ErrorType}.Validation)");
code.AppendLine(" {");
BindingCodeEmitter.EmitValidationDictBuilder(
code, 20, "validationDict", "result.Errors", "e",
"e.Code", "e.Description",
$"e.Type != {WellKnownTypes.Fqn.ErrorType}.Validation");
code.AppendLine(
$" return {ctx.WrapReturn($"{WellKnownTypes.Fqn.TypedResults.ValidationProblem}(validationDict)")};");
code.AppendLine(" }");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validation dispatch is gated only by inferred errors, not full declared error contract.

hasValidation (and downstream switch coverage) only inspects InferredErrorTypeNames. Endpoints that declare validation errors but don’t infer them from body analysis can miss the ValidationProblem 400 path and fall through to failure handling.

As per coding guidelines, "Map ErrorType to HTTP status codes according to RFC 9110: Validation=400, Unauthorized=401, Forbidden=403, NotFound=404, Conflict=409, Failure/Unexpected=500".

Also applies to: 89-106

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs`
around lines 54 - 67, The current gating uses hasValidation which only checks
ep.ErrorInference.InferredErrorTypeNames, so endpoints that declare a Validation
error but don't infer it are missing the 400 ValidationProblem path; update the
condition to consider declared/contract error types as well (e.g., check
ep.DeclaredErrorTypeNames or the collection that represents the endpoint's
declared errors in addition to ep.ErrorInference.InferredErrorTypeNames) so that
hasValidation becomes true if either inferred OR declared error type names
contain ErrorMapping.Validation; apply the same change to the other similar
guards around lines 89-106; keep the existing generation blocks
(BindingCodeEmitter.EmitValidationDictBuilder, the Type comparison to
WellKnownTypes.Fqn.ErrorType.Validation, and the ctx.WrapReturn call) unchanged
so only the presence check is broadened.

Comment on lines +151 to +167
foreach (var (_, paramName) in validationParams)
{
code.AppendLine(
$" var {paramName}ValidationResults = new {WellKnownTypes.Fqn.List}<{WellKnownTypes.Fqn.ValidationResult}>();");
code.AppendLine(
$" if (!{WellKnownTypes.Fqn.Validator}.TryValidateObject({paramName}!, new {WellKnownTypes.Fqn.ValidationContext}({paramName}!), {paramName}ValidationResults, validateAllProperties: true))");
code.AppendLine(" {");
BindingCodeEmitter.EmitValidationDictBuilder(
code, 16, "validationDict", $"{paramName}ValidationResults", "vr",
"key", "vr.ErrorMessage ?? \"\"",
keyVarDecl: "var key = vr.MemberNames.FirstOrDefault() ?? \"\";");

var returnExpr = isAsync
? $"{WellKnownTypes.Fqn.TypedResults.ValidationProblem}(validationDict)"
: $"Task.FromResult<{returnTypeFqn}>({WellKnownTypes.Fqn.TypedResults.ValidationProblem}(validationDict))";
code.AppendLine($" return {returnExpr};");
code.AppendLine(" }");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the EmitBclValidation method and surrounding context
fd -t f "*.cs" src/ErrorOrX.Generators | while read f; do
  if grep -q "EmitBclValidation" "$f"; then
    echo "=== $f ==="
    wc -l "$f"
  fi
done

Repository: ANcpLua/ErrorOrX

Length of output: 344


🏁 Script executed:

#!/bin/bash
# Search for the file mentioned in the review
cat -n "src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs" | sed -n '130,180p'

Repository: ANcpLua/ErrorOrX

Length of output: 2830


🏁 Script executed:

#!/bin/bash
# Find how validation params are collected and filtered
rg "RequiresValidation" -A 3 -B 3 src/ErrorOrX.Generators

Repository: ANcpLua/ErrorOrX

Length of output: 9645


🏁 Script executed:

#!/bin/bash
# Check parameter binding and nullability handling
rg "validationParams" -B 5 -A 10 src/ErrorOrX.Generators/Core

Repository: ANcpLua/ErrorOrX

Length of output: 6230


🏁 Script executed:

#!/bin/bash
# Find the RequiresValidation method implementation
rg "public static bool RequiresValidation" -A 20 src/ErrorOrX.Generators/TypeResolution

Repository: ANcpLua/ErrorOrX

Length of output: 1934


🏁 Script executed:

#!/bin/bash
# Check if nullable parameters are filtered out
rg "IsNullable" src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs -B 3 -A 3

Repository: ANcpLua/ErrorOrX

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for how validation is gated on parameter nullability
rg "RequiresValidation.*IsNullable\|IsNullable.*RequiresValidation" src/ErrorOrX.Generators

Repository: ANcpLua/ErrorOrX

Length of output: 42


🌐 Web query:

System.ComponentModel.DataAnnotations.Validator.TryValidateObject null reference behavior

💡 Result:

The System.ComponentModel.DataAnnotations.Validator.TryValidateObject method exhibits specific behaviors regarding null references, primarily centered on its input parameters and internal validation logic. 1. Instance Parameter: If the instance parameter passed to TryValidateObject is null, the method will throw an ArgumentNullException [1]. 2. ValidationContext Parameter: The ValidationContext constructor itself requires a non-null instance [2]. If you attempt to create a ValidationContext with a null instance, the constructor will throw an ArgumentNullException [2]. 3. Internal Null Reference Issues: Historically, there have been reports of NullReferenceException occurring within the Validator logic itself, particularly when dealing with IValidatableObject implementations that return null instead of an empty collection of validation results [3]. In such cases, the internal implementation of Validator may fail to check for null before iterating over the results, leading to an exception [3]. 4. ValidationResults Collection: If the ICollection parameter is provided but the validation fails, it is populated with errors [1]. If this collection is null, the method may throw a NullReferenceException when it attempts to add results to it. Ensure you initialize this collection (e.g., new List) before passing it to the method [4][5]. To avoid these issues, ensure that the object being validated is not null, the ValidationContext is correctly initialized with a non-null instance, and the validation results collection is instantiated [1][2][5].

Citations:


🏁 Script executed:

#!/bin/bash
# Check if parameters that require validation can also be nullable
# Look at parameter binding flag assignment logic
cat -n src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs | sed -n '1,150p'

Repository: ANcpLua/ErrorOrX

Length of output: 7008


🏁 Script executed:

#!/bin/bash
# Check parameter binding to see what determines nullability
rg "ParameterFlags.Nullable" src/ErrorOrX.Generators/Core -B 3 -A 3

Repository: ANcpLua/ErrorOrX

Length of output: 918


🏁 Script executed:

#!/bin/bash
# Look for any guards on nullable parameters in validation flow
rg "IsNullable.*validat\|validat.*IsNullable" -i src/ErrorOrX.Generators

Repository: ANcpLua/ErrorOrX

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Verify that nullable validatable parameters can occur in practice
# Check if there's any constraint preventing nullable + requires validation combo
rg "RequiresValidation" src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs -B 5 -A 5

Repository: ANcpLua/ErrorOrX

Length of output: 377


Guard nullable validation targets before emitting TryValidateObject.

Parameters marked RequiresValidation can be nullable. The emitted validation code unconditionally dereferences paramName! and passes it to Validator.TryValidateObject, which throws ArgumentNullException on null instance. This bypasses the intended validation error response path.

Proposed fix
-            if (!{WellKnownTypes.Fqn.Validator}.TryValidateObject({paramName}!, new {WellKnownTypes.Fqn.ValidationContext}({paramName}!), {paramName}ValidationResults, validateAllProperties: true))
+            if ({paramName} is not null
+                && !{WellKnownTypes.Fqn.Validator}.TryValidateObject({paramName}, new {WellKnownTypes.Fqn.ValidationContext}({paramName}), {paramName}ValidationResults, validateAllProperties: true))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs`
around lines 151 - 167, The generated code currently dereferences {paramName}!
when calling {WellKnownTypes.Fqn.Validator}.TryValidateObject which will throw
on null; add a null-check for each validation target (from validationParams /
paramName) before calling TryValidateObject: if the parameter is null, build the
same validationDict indicating a required/null error for that parameter and
return the appropriate ValidationProblem (use the same isAsync-aware returnExpr
pattern used below), otherwise proceed to call TryValidateObject with the
non-null instance; ensure you reference the same
BindingCodeEmitter.EmitValidationDictBuilder/validationDict and the isAsync
return branches so behavior and return type remain consistent.

Comment on lines +35 to +36
var refs = method.DeclaringSyntaxReferences;
if (refs.IsDefaultOrEmpty || refs.Length is 0) return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Redundant length check.

IsDefaultOrEmpty already returns true for zero-length arrays. The || refs.Length is 0 clause is unreachable.

Proposed fix
     var refs = method.DeclaringSyntaxReferences;
-    if (refs.IsDefaultOrEmpty || refs.Length is 0) return null;
+    if (refs.IsDefaultOrEmpty) return null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs`
around lines 35 - 36, The conditional checking refs for emptiness is redundant:
remove the "|| refs.Length is 0" portion and keep only the IsDefaultOrEmpty
check so the early-return uses "if (refs.IsDefaultOrEmpty) return null;"; update
the code where refs is assigned from method.DeclaringSyntaxReferences (in
ErrorOrEndpointGenerator.Extractor.ErrorInference) to rely solely on
refs.IsDefaultOrEmpty to determine emptiness.

Comment on lines +56 to +76
public async Task DisableCors_Emits_DisableCors()
{
const string Source = """
using ErrorOr;
using Microsoft.AspNetCore.Cors;

public static class Api
{
[Get("/internal")]
[DisableCors]
public static ErrorOr<string> Internal() => "internal";
}
""";

using var result = await RunAsync(Source);

result.Diagnostics.Should().BeEmpty();
var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content;
// DisableCors should be emitted
generated.Should().Match("*Cors*");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

DisableCors test does not validate disable behavior.

Match("*Cors*") passes for both enable and disable paths. This test should assert the exact emitted API for disable semantics.

Suggested assertion hardening
-        // DisableCors should be emitted
-        generated.Should().Match("*Cors*");
+        generated.Should().Contain(".DisableCors()");
+        generated.Should().NotContain(".RequireCors(");

As per coding guidelines **/*Tests/**/*.cs: “Review that tests assert observable behavior, not implementation details”.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task DisableCors_Emits_DisableCors()
{
const string Source = """
using ErrorOr;
using Microsoft.AspNetCore.Cors;
public static class Api
{
[Get("/internal")]
[DisableCors]
public static ErrorOr<string> Internal() => "internal";
}
""";
using var result = await RunAsync(Source);
result.Diagnostics.Should().BeEmpty();
var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content;
// DisableCors should be emitted
generated.Should().Match("*Cors*");
}
public async Task DisableCors_Emits_DisableCors()
{
const string Source = """
using ErrorOr;
using Microsoft.AspNetCore.Cors;
public static class Api
{
[Get("/internal")]
[DisableCors]
public static ErrorOr<string> Internal() => "internal";
}
""";
using var result = await RunAsync(Source);
result.Diagnostics.Should().BeEmpty();
var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content;
generated.Should().Contain(".DisableCors()");
generated.Should().NotContain(".RequireCors(");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs`
around lines 56 - 76, The test DisableCors_Emits_DisableCors currently only
checks for a generic "Cors" token which matches both enable and disable paths;
update the assertion to validate the exact disable semantics by asserting the
generated API contains the explicit disable marker emitted by the generator
(e.g. the attribute or mapping representing disable CORS) rather than a
wildcard. Replace generated.Should().Match("*Cors*") in
DisableCors_Emits_DisableCors with a precise check on the generated string (for
example generated.Should().Contain("[DisableCors]") or the exact snippet the
generator emits into ErrorOrEndpointMappings.cs) and optionally assert that any
enable-CORS marker is not present to ensure correct disable behavior.

Comment on lines +133 to +175
public async Task OutputCache_With_Duration_Emits_Duration()
{
const string Source = """
using ErrorOr;
using Microsoft.AspNetCore.OutputCaching;

public static class Api
{
[Get("/cached")]
[OutputCache(Duration = 60)]
public static ErrorOr<string> Cached() => "cached";
}
""";

using var result = await RunAsync(Source);

result.Diagnostics.Should().BeEmpty();
var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content;
// Duration should be handled - either via policy builder or inline
generated.Should().Match("*.CacheOutput*");
}

[Fact]
public async Task OutputCache_With_VaryByQueryKeys_Emits_VaryBy()
{
const string Source = """
using ErrorOr;
using Microsoft.AspNetCore.OutputCaching;

public static class Api
{
[Get("/cached")]
[OutputCache(VaryByQueryKeys = new[] { "page", "sort" })]
public static ErrorOr<string> Cached() => "cached";
}
""";

using var result = await RunAsync(Source);

result.Diagnostics.Should().BeEmpty();
var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content;
generated.Should().Match("*.CacheOutput*");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Duration/VaryByQueryKeys tests can pass when those settings are silently dropped.

Both tests only check that some .CacheOutput* call exists. They do not verify that duration/vary configuration is preserved in emitted code.

Suggested assertion hardening
-        // Duration should be handled - either via policy builder or inline
-        generated.Should().Match("*.CacheOutput*");
+        generated.Should().Contain(".CacheOutput(");
+        generated.Should().Contain("60");
...
-        generated.Should().Match("*.CacheOutput*");
+        generated.Should().Contain(".CacheOutput(");
+        generated.Should().Contain("page");
+        generated.Should().Contain("sort");

As per coding guidelines **/*Tests/**/*.cs: “Review that tests assert observable behavior, not implementation details”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs`
around lines 133 - 175, The two tests OutputCache_With_Duration_Emits_Duration
and OutputCache_With_VaryByQueryKeys_Emits_VaryBy currently only assert that
some CacheOutput call exists, so update each test to assert the emitted code
preserves the specific configuration: inspect the generated string (variable
generated from the file with HintName "ErrorOrEndpointMappings.cs") for the
concrete duration or vary-by-query representation (e.g., ensure the generated
text contains the duration value/setting such as "60" or a policy builder call
that sets Duration = 60 for OutputCache_With_Duration_Emits_Duration, and ensure
it contains the VaryByQueryKeys list or an equivalent builder call for
OutputCache_With_VaryByQueryKeys_Emits_VaryBy); modify the assertions on the
generated variable in those two test methods to look for those specific tokens
rather than just "*.CacheOutput*".

Comment on lines +203 to +224
[Fact]
public async Task Complex_Type_With_Explicit_FromQuery_NoWarning()
{
const string Source = """
using ErrorOr;
using Microsoft.AspNetCore.Mvc;

public record SearchFilter(string Query, int Page);

public static class Api
{
[Get("/test")]
public static ErrorOr<string> Handler([FromQuery] SearchFilter filter) => "result";
}
""";

using var result = await RunAsync(Source);

// EOE011: [FromQuery] only supports primitives or collections of primitives
// This is expected behavior - complex types can't be query bound without [AsParameters]
result.Diagnostics.Should().ContainSingle(static d => d.Id == "EOE011");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Rename test to match asserted diagnostic behavior.

Complex_Type_With_Explicit_FromQuery_NoWarning currently asserts EOE011 is emitted. The method name should reflect expected warning/diagnostic to avoid future false assumptions during refactors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs` around
lines 203 - 224, The test Complex_Type_With_Explicit_FromQuery_NoWarning is
asserting that diagnostic "EOE011" is produced, so rename the test method to
reflect that a warning is expected (for example
Complex_Type_With_Explicit_FromQuery_EmitsEOE011 or
Complex_Type_With_Explicit_FromQuery_ReportsEOE011) to match the assertion on
result.Diagnostics.Should().ContainSingle(d => d.Id == "EOE011"); update only
the method name of the test (the method signature on the Fact) so callers like
the test runner and references such as RunAsync and result.Diagnostics remain
unchanged.

Comment on lines +27 to +30
// HttpContext binds directly from ctx parameter (uses p0, p1, etc. naming)
generated.Should().Contain("= ctx;");
generated.Should().Contain("global::Api.GetInfo(p0)");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Avoid asserting generated temporary parameter names.

Asserting global::Api.GetInfo(p0) couples this test to emitter-local symbol naming instead of binding behavior and will create churn on harmless refactors.

Proposed change
-        generated.Should().Contain("global::Api.GetInfo(p0)");
+        generated.Should().Contain("global::Api.GetInfo(");

As per coding guidelines, ".NET tests. Review that tests assert observable behavior, not implementation details."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs` around
lines 27 - 30, The test currently asserts the emitter's temporary parameter name
by checking for the exact string "global::Api.GetInfo(p0)"; change this to
assert observable behavior instead (e.g., that the generated output contains a
call to "global::Api.GetInfo(" or otherwise validates that GetInfo is invoked)
so the test no longer depends on emitter-local names; update the assertion that
references "global::Api.GetInfo(p0)" in ParameterBindingSpecialTypesTests to use
a name-agnostic check while keeping the existing check for the HttpContext
binding ("= ctx;") intact.

Comment on lines +97 to +114
public Task EOE024_Interface_Call_With_ProducesError_No_Diagnostic()
{
const string Source = """
using ErrorOr;

namespace DiagnosticTest;

public interface ITodoService
{
ErrorOr<string> GetById(int id);
}

public static class TodoApi
{
[Get("/todos/{id}")]
[ProducesError(404, "NotFound")]
public static ErrorOr<string> GetById(int id, ITodoService svc)
=> svc.GetById(id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify attribute contract usage for EOE024 and related extraction path.

rg -n -C3 '\b(ProducesError|ReturnsError)\s*\(' tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs
rg -n -C3 'UndocumentedInterfaceCall|ReturnsError|ProducesError' src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs

Repository: ANcpLua/ErrorOrX

Length of output: 4488


🏁 Script executed:

rg -n 'ReturnsError|ProducesError' tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs | head -30

Repository: ANcpLua/ErrorOrX

Length of output: 260


🏁 Script executed:

grep -n "public Task.*EOE024" tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs

Repository: ANcpLua/ErrorOrX

Length of output: 192


🏁 Script executed:

sed -n '73,96p' tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs

Repository: ANcpLua/ErrorOrX

Length of output: 871


EOE024 no-diagnostic case exercises the fallback path, not the documented interface metadata contract.

The test lacks coverage of the primary code path: extracting [ReturnsError] from interface method declarations. Line 112 places [ProducesError(...)] on the handler, which satisfies the generator's escape-hatch logic (line 57 of InterfaceDetection.cs: "endpoint already has [ProducesError], assume developer knows what they're doing"), but this bypasses the intended interface metadata extraction altogether. The test can pass without the generator ever executing the TryExtractReturnsErrorAttributes path.

Per coding guidelines and the generator's own design, [ReturnsError] belongs on the interface method declaration to document the error contract at the source. The test should validate this primary path.

Proposed fix
 public interface ITodoService
 {
+    [ReturnsError(ErrorType.NotFound, "NotFound")]
     ErrorOr<string> GetById(int id);
 }
 
 public static class TodoApi
 {
     [Get("/todos/{id}")]
-    [ProducesError(404, "NotFound")]
     public static ErrorOr<string> GetById(int id, ITodoService svc)
         => svc.GetById(id);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Task EOE024_Interface_Call_With_ProducesError_No_Diagnostic()
{
const string Source = """
using ErrorOr;
namespace DiagnosticTest;
public interface ITodoService
{
ErrorOr<string> GetById(int id);
}
public static class TodoApi
{
[Get("/todos/{id}")]
[ProducesError(404, "NotFound")]
public static ErrorOr<string> GetById(int id, ITodoService svc)
=> svc.GetById(id);
public Task EOE024_Interface_Call_With_ProducesError_No_Diagnostic()
{
const string Source = """
using ErrorOr;
namespace DiagnosticTest;
public interface ITodoService
{
[ReturnsError(ErrorType.NotFound, "NotFound")]
ErrorOr<string> GetById(int id);
}
public static class TodoApi
{
[Get("/todos/{id}")]
public static ErrorOr<string> GetById(int id, ITodoService svc)
=> svc.GetById(id);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs` around lines 97
- 114, The test EOE024_Interface_Call_With_ProducesError_No_Diagnostic is
exercising the generator fallback by placing [ProducesError(404, "NotFound")] on
the handler instead of the interface; move the attribute to the interface method
so the generator's interface metadata extraction is exercised: add
[ProducesError(404, "NotFound")] to ITodoService.GetById and remove it from
TodoApi.GetById, ensuring the generator path that invokes
TryExtractReturnsErrorAttributes in InterfaceDetection.cs is covered.

@ANcpLua ANcpLua merged commit 6e22125 into main May 16, 2026
46 of 47 checks passed
@ANcpLua ANcpLua deleted the refactor/split-oversized-files branch May 16, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant