Skip to content

F# codegen: FluentValidation + CosmosDB slice — runnable sample + compile-gate (GH-2969)#2986

Merged
jeremydmiller merged 1 commit into
mainfrom
feat-2969-fsharp-fluentvalidation-cosmos
May 29, 2026
Merged

F# codegen: FluentValidation + CosmosDB slice — runnable sample + compile-gate (GH-2969)#2986
jeremydmiller merged 1 commit into
mainfrom
feat-2969-fsharp-fluentvalidation-cosmos

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Part of the F# code-generation audit (#2969). A combined F# sample exercising two middleware surfaces in one handler — FluentValidation + CosmosDB persistence — plus a compile-gate proving the chain emits valid F#.

Frame F# emit

Only one frame needed new F# emit:

  • CosmosDB TransactionalFramecontext.EnlistInOutbox(CosmosDbEnvelopeTransaction(container, context)) (EnlistInOutbox is synchronous; CosmosDbEnvelopeTransaction is already public).

Everything else was already F# via MethodCall/existing frames:

  • FluentValidationdo! FluentValidationExecutor.ExecuteOne<T>(validator, failureAction, msg).
  • ISideEffect (the returned ICosmosDbOp) → if not (isNull outgoing1) then do! outgoing1.Execute(container).
  • Outbox flush → do! context.FlushOutgoingMessagesAsync().

Generated F# (from the gate):

task {
    let createThingValidator = WolverineCosmosFSharpSample.CreateThingValidator()
    let createThing = context.Envelope.Message :?> WolverineCosmosFSharpSample.CreateThing
    do! Wolverine.FluentValidation.Internals.FluentValidationExecutor.ExecuteOne<...CreateThing>(createThingValidator, _failureActionOfCreateThing, createThing)
    // Enlist in CosmosDB outbox transaction
    context.EnlistInOutbox(Wolverine.CosmosDb.Internals.CosmosDbEnvelopeTransaction(_container, context))
    let outgoing1 = WolverineCosmosFSharpSample.CreateThingHandler.Handle(createThing)
    if not (isNull outgoing1) then
        do! outgoing1.Execute(_container)
    do! context.FlushOutgoingMessagesAsync()
}

Runnable sample — src/Samples/WolverineCosmosFSharpSample

F# CreateThing command + CreateThingValidator : AbstractValidator<CreateThing> + a [<Transactional>] CreateThingHandler returning CosmosDbOps.Store(thing). UseFluentValidation() + UseCosmosDbPersistence(db). Runs via dynamic codegen (Wolverine.RuntimeCompilation + opts.UseRuntimeCompilation(), #2876) against the Cosmos emulator (the sample pre-creates the database, since Wolverine's resource setup only creates containers). Verified end-to-end: prints Stored a Thing through the F# Wolverine + CosmosDB handler (with FluentValidation).

F# + FluentValidation note

AbstractValidator<T>.RuleFor takes a LINQ Expression<Func<T,TProperty>>; F# auto-converts the property-selector lambdas (fun x -> x.Name) to expression trees, so the validator reads naturally.

Compile-gate — src/Testing/Wolverine.Cosmos.FSharp{Tests,Fixture}

Renders the sample's real CreateThing chain to F# via the no-host HandlerGraphAssembleTypesGenerateFSharpCode path, then dotnet builds the fixture. No live Cosmos connection (the CosmosClient is constructed lazily and never used at codegen).

Wire-up

  • Sample + gate added to wolverine_fsharp.slnx.
  • fsharp.yml runs the FluentValidation + CosmosDB gate as its own sequential step.

Verification

  • Compile-gate green.
  • dotnet build wolverine.slnx -c Release clean (per CLAUDE.md the slim build isn't sufficient).
  • Sample runs end-to-end against the Cosmos emulator.

🤖 Generated with Claude Code

A combined F# Wolverine sample exercising two middleware surfaces in one handler
(requested by Jeremy), plus a compile-gate proving the chain emits valid F#.

Frame F# emit (the only one that needed it):
- CosmosDb TransactionalFrame -> `context.EnlistInOutbox(CosmosDbEnvelopeTransaction(container, context))`
  (EnlistInOutbox is synchronous; CosmosDbEnvelopeTransaction is public).

Everything else was already F#:
- FluentValidation -> `do! FluentValidationExecutor.ExecuteOne<T>(validator, failureAction, msg)` (MethodCall).
- ISideEffect (ICosmosDbOp) -> `if not (isNull outgoing1) then do! outgoing1.Execute(container)`.
- Outbox flush -> `do! context.FlushOutgoingMessagesAsync()`.

Runnable sample (src/Samples/WolverineCosmosFSharpSample):
- F# CreateThing command + CreateThingValidator (AbstractValidator<CreateThing>; F# auto-converts the
  RuleFor property-selector lambdas to LINQ expression trees) + a [<Transactional>] CreateThingHandler
  returning CosmosDbOps.Store(thing). UseFluentValidation() + UseCosmosDbPersistence(db). Runs via
  dynamic codegen (Wolverine.RuntimeCompilation + UseRuntimeCompilation, GH-2876) against the Cosmos
  emulator (pre-creates the database, since resource setup only creates containers). Verified
  end-to-end: "Stored a Thing through the F# Wolverine + CosmosDB handler (with FluentValidation)."

Compile-gate (src/Testing/Wolverine.Cosmos.FSharp{Tests,Fixture}):
- Renders the sample's real CreateThing chain to F# via the no-host
  HandlerGraph/AssembleTypes/GenerateFSharpCode path and dotnet-builds the fixture. No live Cosmos
  connection (the CosmosClient is constructed lazily and never used at codegen).

Wire-up: sample + gate added to wolverine_fsharp.slnx; fsharp.yml runs the gate as its own sequential
step. No JasperFx release needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit a5958c5 into main May 29, 2026
24 checks passed
This was referenced Jun 1, 2026
This was referenced Jun 15, 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