feat(web): source-generated typed-delegate endpoint binding [L03.01.02.03] - #936
Merged
Merged
Conversation
…ient sees the response terminate [L01.01.11.44] An HTTP/3 response body is delimited by the request stream's end (RFC 9114 §4.1), not by Content-Length. The h3 send path wrote HEADERS/DATA, flushed, and returned without ending the request stream's write side, so a real .NET HTTP/3 client stayed in ReadResponseContentAsync waiting for a FIN that only arrived at connection teardown — at which point the control/QPACK critical streams closing surfaced at the client as H3_CLOSED_CRITICAL_STREAM (0x104). The buffered SendAsync and the streaming sink's CompleteFramedAsync now end the request stream's write side (graceful QUIC FIN via the IConnection half-close contract, Output.Complete()) once the response is fully flushed. Best-effort: a teardown race that disposed the stream is swallowed. Both #928 send observations resolve here: a bodyless 200 completes the client's zero-length drain via the FIN, and the buffered-body content length already matched the DATA written (ReadBodyAsync reads the whole buffer, position-independent). Adds platform-guarded real-QUIC round-trip regression tests (full round-trip, bodyless 200, end-position body, sequential requests pinning critical-stream lifetime) and flips the Web.Hosting UseHttp3 e2e from best-effort to a hard client-observed status+body assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…op StaticFiles compensation [L01.01.11.43] Http1MessageReader now surfaces IHttpRequest.Path from the origin-form request-target through the same HttpPath.FromUriComponent decode HTTP/2 (Http2Stream) and HTTP/3 (Http3HeaderCodec) run over the :path pseudo-header, so identical wire bytes yield an identical decoded path on every transport: %2e%2e decodes to "..", %2F stays encoded (never a separator), ordinary octets decode, and invalid/overlong escapes are left intact per UrlDecoder. A decoded octet that is not a legal path character (space, control, ?/#, NUL) makes the target malformed and is surfaced as the reader's existing wire-level failure. Only origin-form is decoded; absolute/authority/asterisk keep their handling. The query is unchanged — already split before the decode and parsed by HttpQuery.Parse identically on all transports. Removes the now-double-decoding version-gated compensation in Web.StaticFiles.StaticFilesMiddleware (Http11 + Uri.UnescapeDataString) in the same change and re-points its tests: the encoded-traversal/name decode is pinned at the transport (new Http.Connections parity suite + the E2E traversal/name round-trips), and a middleware test guards against re-decoding a handed path. Tests: Http.Connections 434, Web.StaticFiles 85, Web.Routing 250, Http core 1231 — all green. Updates both touched DESIGN.md files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ared HttpHost primitive [L03.01.02.01.09] Host filtering (#781) added a structural host[:port] split to HttpHost (TryGetComponents) that deliberately mirrored RouteHostConstraint's own private TrySplitHostAndPort/TryParsePort (#788). The two copies could drift, so route host SELECTION and host allowlist VALIDATION might disagree on what a wire value means. Extract the structural split as an internal HttpHost.TrySplitHostPort and widen TryParsePort to internal (granted to Web.Routing via InternalsVisibleTo). TryGetComponents now layers its stricter port validation on top of that shared split, byte-for-byte unchanged. RouteHostConstraint.TryParse/IsMatch delegate to the shared helpers and the duplicated private copies are deleted. Direct delegation to TryGetComponents was impossible without changing behavior: it fuses port validation into the split and returns false (no host) for a present-but-invalid port, which would break the documented #788 leniency where a port-unconstrained route tolerates junk port text in the request host and matches on the host part alone. The shared *structural* split preserves that leniency exactly; new RouteHostConstraintTests cases pin both the leniency and its structural boundary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ion seam [L03.01.02.03] Delivers the Stage-4 finale of the HTTP/Web program (#796) as the middleware-first re-scope, NOT the stale issue body: no IResult, no typed result unions, no Web.Functions/Web.Results (deleted). Handlers write responses imperatively. - Web.Api gains typed Delegate Map* overloads (Map/MapGet/MapPost/MapPut/MapPatch/MapDelete plus IValidator-carrying variants) as throwing placeholders the generator rewrites, a metadata-carrying raw Map overload, the [From*] binding attributes, an EndpointValidationMetadata carrier, and the EndpointValidation problem-writer. Homed in Web.Api (no new package/manifest assembly). - analyzers/Assimalign.Cohesion.SourceGeneration.Web: an interceptor-style IIncrementalGenerator (EndpointBindingGenerator) that casts each typed handler back to its delegate type and invokes it directly (no reflection, no Expression.Compile), binding route/query/header/body/form plus injections (IHttpContext, CancellationToken, IHttpFeature). Failures are outcomes: unparseable or missing scalar -> 400 problem+json (errors extension), unsupported/absent body reader -> 415, JsonException -> 400; handler-thrown exceptions propagate to the #881 boundary. - Opt-in per-endpoint validation seam over Assimalign.Cohesion.ObjectValidation (as-is): the validator runs against the bound model before the handler; failures short-circuit to 400. - ObjectValidation NativeAOT hardening: removed all three Expression.Compile sites (compile-free reflective member-walk getter; Func-first When on IValidationRuleDescriptor<T> / IValidationCondition<T>). Now IL2026/IL3050-clean; full 218-test suite green. - Delivery: CohesionAnalyzerReference for in-repo/test projects; CohesionFrameworkAnalyzer in App.props (App.Web) for Sdk.Web consumers (bundled at analyzers/dotnet/cs in App.Web.Ref); consumers allow-list Assimalign.Cohesion.Web.Api.Generated via InterceptorsNamespaces. Verification: 15 runtime e2e tests through WebApplicationTestFactory + 7 generator unit tests; regressions green (Web.Routing 263, Web.Serialization 42, Http 1231, Http.Connections 434, ObjectValidation 218). App.Web.Ref/Runtime packs validated (generator bundled at analyzers/dotnet/cs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Base automatically changed from
feature/L03.01.02.01.09-route-host-httphost
to
main
July 20, 2026 18:26
…owner decision Removes the IValidator-carrying Map* overloads, EndpointValidationMetadata, EndpointValidation, the metadata-carrying Map overload that existed only for the carrier, the generator's validator modeling/emission, and the ObjectValidation dependency from Web.Api (src and tests) and the generator tests. The ObjectValidation AOT hardening (compile-free member walk; When -> Func<T,bool>) is deliberately retained - it stands on its own. Descope recorded in the Web.Api DESIGN (owner decision 2026-07-20) with validation now an explicit v1 non-goal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Batch 5, stack 4/4 (base:
feature/L03.01.02.01.09-route-host-httphost, PR #934) — the Stage-4 finale, built to the middleware-first re-scope (the issue body's IResult/result-union/Web.Functions/IEndpointFilter framing predates the 2026-07-10 direction change; no result types anywhere).Map*overloads on the existing Web.Api surface (MapGet("/users/{id}", (int id, IHttpContext ctx) => …)): runtime bodies throw a clear "requires the generator" error;EndpointBindingGenerator(interceptor-styleIIncrementalGeneratorinanalyzers/SourceGeneration.Web, mirroring the live MapperProfileGenerator) intercepts call sites and emits AOT-safe thunks — exact-delegate-type cast + direct invoke, inlineIParsable<T>/Enum.TryParseconversions; no reflection, no Expression.Compile. C# 14extension(...)-member interception proven by the runtime e2e suite.IHttpContext/CancellationToken/typed-feature injection. Inference (injection →[From*]attribute → route-token match → scalar=query, complex=body) with attributes as compile-time generator input only.errorsextension naming the parameter (unparseable scalars, JsonException) · 415 (no reader/unreadable Content-Type). Handler exceptions propagate untouched to the [L03.01.01.19] Pipeline exception boundary, status-code pages, and 404 terminal over IResult (supersedes #776) #881 boundary.PropertyInfo/FieldInfo(no runtime codegen, IL2026/IL3050-clean, null-chain semantics preserved);WhenmovedExpression<Func<T,bool>>→Func<T,bool>— source-compatible for lambdas, binary-breaking for materialized-Expression callers (deliberate, documented in its DESIGN).CohesionAnalyzerReferencefor in-repo consumers; the pre-existingCohesionFrameworkAnalyzermanifest machinery ships the DLL atanalyzers/dotnet/cs/in App.Web.Ref (pack-verified). Consumers allow-listAssimalign.Cohesion.Web.Api.GeneratedviaInterceptorsNamespaces(Sdk auto-set recorded as follow-up).Tests (post-descope)
Web.Api e2e 13 (every source, inference vs attributes, 400/415 matrix, injections) · generator unit 6 · ObjectValidation 218 (unchanged — hardening regression-proof) · Web.Routing 263 · Web.Serialization 42 · core Http 1231 · Http.Connections 434 — all passing. App.Web Ref + Runtime packs validated.
Follow-up candidates (not filed)
Sdk.Web auto-setting
InterceptorsNamespaces· compile-time diagnostics for unmodelable call sites · analyzer unit-test CI home (pre-existing gap) · handler filter chains ·UseRouting()auto-wiring ergonomics · the future validation-integration design.Closes #796
🤖 Generated with Claude Code