Skip to content

Instantly share code, notes, and snippets.

@TheAngryByrd
Last active January 16, 2023 16:11
Show Gist options
  • Save TheAngryByrd/11f07b859ad3093877fa1cccb42e8d8f to your computer and use it in GitHub Desktop.
Save TheAngryByrd/11f07b859ad3093877fa1cccb42e8d8f to your computer and use it in GitHub Desktop.
F# Giraffe Compose Async SRTP
open System
open Giraffe
open Microsoft.AspNetCore.Http
open System.Threading
open System.Threading.Tasks
module HttpHandlerHelpers=
/// Converts an Async HttpHandler to a normal Giraffe Task based HttpHandler
let inline convertAsyncToTask (asyncHandler : HttpFunc -> HttpContext -> Async<HttpContext option>) (next : HttpFunc) (ctx : HttpContext) : HttpFuncResult =
let operation = async.Delay(fun () -> asyncHandler next ctx)
Async.StartAsTask(operation, cancellationToken = ctx.RequestAborted)
/// Converts an ValueTask HttpHandler to a normal Giraffe Task based HttpHandler
let inline convertValueTaskToTask (asyncHandler : HttpFunc -> HttpContext -> ValueTask<HttpContext option>) (next : HttpFunc) (ctx : HttpContext) : HttpFuncResult =
let operation = asyncHandler next ctx
operation.AsTask()
type Composers =
/// Normal Task and Task Compose
static member inline Compose (handler1 : HttpHandler, handler2 : HttpHandler) =
compose handler1 handler2
/// Async and Async Compose
static member inline Compose (asyncHandler1,asyncHandler2) =
compose (HttpHandlerHelpers.convertAsyncToTask asyncHandler1) (HttpHandlerHelpers.convertAsyncToTask asyncHandler2)
/// Async and Task Compose
static member inline Compose (asyncHandler1,handler2) =
compose (HttpHandlerHelpers.convertAsyncToTask asyncHandler1) handler2
/// Task and Async Compose
static member inline Compose (handler1,asyncHandler2) =
compose handler1 (HttpHandlerHelpers.convertAsyncToTask asyncHandler2)
/// ValueTask and ValueTask Compose
static member inline Compose (vTaskHandler1,vTaskHandler2) =
compose (HttpHandlerHelpers.convertValueTaskToTask vTaskHandler1) (HttpHandlerHelpers.convertValueTaskToTask vTaskHandler2)
/// ValueTask and Task Compose
static member inline Compose (vTaskHandler1,handler2) =
compose (HttpHandlerHelpers.convertValueTaskToTask vTaskHandler1) handler2
/// Task and ValueTask Compose
static member inline Compose (handler1,vTaskHandler2) =
compose handler1 (HttpHandlerHelpers.convertValueTaskToTask vTaskHandler2)
/// ValueTask and Async Compose
static member inline Compose (vTaskHandler1,asyncHandler2) =
compose (HttpHandlerHelpers.convertValueTaskToTask vTaskHandler1) (HttpHandlerHelpers.convertAsyncToTask asyncHandler2)
/// Async and ValueTask Compose
static member inline Compose (asyncHandler1,vTaskHandler2) =
compose (HttpHandlerHelpers.convertAsyncToTask asyncHandler1) (HttpHandlerHelpers.convertValueTaskToTask vTaskHandler2)
[<AutoOpen>]
module Giraffe =
/// <summary>
/// Combines two <see cref="HttpHandler"/> functions into one.
/// Please mind that both <see cref="HttpHandler"/> functions will get pre-evaluated at runtime by applying the next <see cref="HttpFunc"/> parameter of each handler.
/// </summary>
let inline (>==>) (handler1: ^Handler1) (handler2 : ^Handler2) =
// magic SRTP stuff, See FSharpPlus for more examples
let inline call (_: ^M, h1 : ^H1, h2 : ^H2) = ((^M or ^H1 or ^H2): (static member Compose: _ * _ -> HttpHandler) (h1,h2))
// The important part here is the Unchecked.defaultof<Composers>. SRTP will use the static methods defined there to choose the correct Compose method on the Task/ValueTask/Async combination.
call(Unchecked.defaultof<Composers>, handler1, handler2)
module Usage =
let someDatabaseCall (cancellationToken : CancellationToken) = // Passing in CancellationToken is important because if request is aboorted we should stop database query
task {
do! Task.Delay(TimeSpan.FromMilliseconds(100.), cancellationToken) // Some fake db lookup delay
return 42
}
let someAsyncRequest next ctx = async {
let! ct = Async.CancellationToken // This cancellationToken will be from the ctx.RequestAborted
let! answer = someDatabaseCall ct |> Async.AwaitTask // Assuming F# is interoperating with a Task based database API
return! text (string answer) next ctx |> Async.AwaitTask // Call Giraffe helpers and await them using Async.AwaitTask
}
// Standard Giraffe Routing
let app : HttpHandler = choose [
route "/AnswerToLife" // Returns a HttpFuncResult (which is a Task<HttpContext option>)
>==> someAsyncRequest // Returns an Async<HttpContext option>
>==> setStatusCode 202 // Returns a HttpFuncResult (which is a Task<HttpContext option>)
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment