Skip to content

F# code generation Phase C (partial): static-response HTTP endpoints (GH-2969)#2976

Merged
jeremydmiller merged 1 commit into
feat-2969-fsharp-codegen-foundationfrom
feat-2969-fsharp-phase-c
May 29, 2026
Merged

F# code generation Phase C (partial): static-response HTTP endpoints (GH-2969)#2976
jeremydmiller merged 1 commit into
feat-2969-fsharp-codegen-foundationfrom
feat-2969-fsharp-phase-c

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Phase C of the F# code-generation audit (#2969) — Wolverine.Http. Stands up the HTTP F# fixture surface and renders a real HttpChain to compilable F# for the static-response (GET) path.

Stacked on the feat-2969-fsharp-codegen-foundation branch (with A+B). Note: Phase B still needs to reach main via #2974 — watch merge order (phase→foundation before foundation→main).

What's here

  • New per-surface trio under src/Testing/: Wolverine.Http.FSharpContracts (endpoint classes), Wolverine.Http.FSharpFixture (F#, checked-in Generated.fs), Wolverine.Http.FSharpTests (compile gate + HTTP fsharp-coverage smoke test). Added to wolverine_fsharp.slnx; CI now runs dotnet test wolverine_fsharp.slnx across both the Core and Http surfaces.
  • No-web-host rendering: HttpChain.ChainFor<T>(x => x.Method(), httpGraph)httpGraph.StartAssembly(rules)AssembleTypesGenerateFSharpCode(new ServiceCollectionServerVariableSource(container)). The generated type subclasses the public HttpHandler base (override _.Handle(httpContext) : Task).
  • WriteStringFrame emits F# — HttpHandler.WriteString is static, so it resolves without a this. A [WolverineGet] string endpoint renders and compiles end to end.

Generated F#

type GET_fsharp_hello(wolverineHttpOptions: WolverineHttpOptions) =
    inherit Wolverine.Http.HttpHandler(wolverineHttpOptions)
    override _.Handle(httpContext: HttpContext) : Task =
        task {
            let thingEndpoints = ThingEndpoints()
            let result_of_Hello = thingEndpoints.Hello()
            do! Wolverine.Http.HttpHandler.WriteString(httpContext, result_of_Hello)
        }

Deferred — blocked on a JasperFx-layer gap (to be filed upstream)

HttpHandler.ReadJsonAsync<T> and WriteJsonAsync<T> are instance methods called unqualified in the generated handler. F# can't resolve them from a member _.Handle body (no self identifier), so ReadJsonBody / WriteJsonFrame — the whole JSON POST path — can't emit F# until JasperFx emits a named self for generated members (e.g. member this.Handle + frames emit this.WriteJsonAsync). This is the same root gap as RecordMessageCausationFrame (Phase B) and is the single biggest lever for HTTP coverage. The POST endpoint is kept in the contracts for that follow-up but isn't rendered yet.

Verification

  • Compile-gate — the regenerated Generated.fs (GET endpoint) compiles via dotnet build.
  • fsharp-coverage with Wolverine.Http loaded — 25 implemented / 2 skipped / 54 remaining of 81.
  • dotnet build wolverine_fsharp.slnx -c Release + all 4 gate/coverage tests — green.
  • Full dotnet build wolverine.slnx -c Release regression — 0 warnings, 0 errors.

Part of #2969.

🤖 Generated with Claude Code

…H-2969)

Phase C of the F# code-generation audit (#2969) — Wolverine.Http. Stands up the
HTTP F# fixture surface and renders a real HttpChain to compilable F# for the
static-response (GET) path.

- New per-surface trio under src/Testing/: Wolverine.Http.FSharpContracts
  (endpoint classes), Wolverine.Http.FSharpFixture (F#, checked-in Generated.fs),
  Wolverine.Http.FSharpTests (compile gate + HTTP fsharp-coverage smoke test).
  Added to wolverine_fsharp.slnx; CI now runs `dotnet test wolverine_fsharp.slnx`
  across both the Core and Http surfaces.
- HTTP chains render with no web host via HttpChain.ChainFor<T>(x => x.Method(),
  httpGraph) + httpGraph.StartAssembly(rules) + AssembleTypes + GenerateFSharpCode.
  The generated type subclasses the public HttpHandler base.
- WriteStringFrame emits F# (HttpHandler.WriteString is static, so it resolves
  without a `this`). A `[WolverineGet]` endpoint returning a string renders and
  compiles end to end.

Deferred — blocked on the JasperFx F# self-identifier gap (to be filed upstream):
HttpHandler.ReadJsonAsync<T> and WriteJsonAsync<T> are INSTANCE methods called
unqualified in the generated handler, which F# cannot resolve from a
`member _.Handle` body. So ReadJsonBody / WriteJsonFrame (the JSON POST path)
can't emit F# until JasperFx emits a named self for generated members. The POST
endpoint is kept in the contracts for that follow-up but is not rendered yet.
Same root gap as RecordMessageCausationFrame (Phase B).

fsharp-coverage with Wolverine.Http loaded: 25 implemented / 2 skipped /
54 remaining of 81 Frame types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 1a196f7 into feat-2969-fsharp-codegen-foundation May 29, 2026
This was referenced Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant