Last active
January 16, 2023 16:11
-
-
Save TheAngryByrd/11f07b859ad3093877fa1cccb42e8d8f to your computer and use it in GitHub Desktop.
F# Giraffe Compose Async SRTP
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
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