Closed ghost closed 1 year ago
Yes, we have had that problem ourselves. The way we solved it was to have a custom runAsync
e.g something like below:
let runAsync<'TResult> (ctx: Microsoft.AspNetCore.Http.HttpContext) (handler: Oryx.HttpHandler<'TResult>) =
task {
let statusCode = extractStatus ctx
return! handler |> HttpHandler.map (fun result -> (result, statusCode)) |> runAsync
}
Thanks @dbrattli . Will check shortly if it fits my purpose. Any suggestion at what part of the Oryx code should I take a look to improve this and fire a PR?
@dbrattli the example you've provided doesn't seem to work well in this case. If we take this code as an example
member _.Initialize() =
httpRequest
|> withLogger logger
|> withLogLevel LogLevel.Information
|> GET
|> withHttpClient httpClient
|> withUrl (baseUrl "/login/initialize")
|> fetch
|> json<InitializeResponse> JSON.options
|> log
...
And later call this method:
task {
let! response = sut.Initialize() |> runAsyncDetailed
}
It will fail with type mismatch error, since we didn't provide an HttpContext
The type 'Pipeline.IAsyncNext<HttpContext,InitializeResponse> -> System.Threading.Tasks.Task<unit>' is not compatible with the type 'AspNetCore.Http.HttpContext'
What I tried to do, is to use code similar to Core.parse
:
module OryxExtensions =
open Oryx
type Details<'TResponseData> = {
StatusCode: HttpStatusCode
Headers: Map<string,seq<string>>
Data: 'TResponseData
}
let private withResult<'TResult> (ctx: HttpContext) (result: 'TResult) : Details<'TResult> =
{
StatusCode = ctx.Response.StatusCode
Headers = ctx.Response.Headers
Data = result
}
let withDetails<'TResult> (source: HttpHandler<'TResult>) : HttpHandler<Details<'TResult>> =
fun next ->
{ new IHttpNext<'TResult> with
member _.OnSuccessAsync(ctx, content: 'TResult) =
task {
let detailedResult = withResult ctx content
return! next.OnSuccessAsync(ctx, detailedResult )
}
member _.OnErrorAsync(ctx, exn) = next.OnErrorAsync(ctx, exn)
member _.OnCancelAsync(ctx) = next.OnCancelAsync(ctx) }
|> source
so we can add that into the pipeline, like this:
member _.Initialize() =
httpRequest
|> withLogger logger
|> withLogLevel LogLevel.Information
|> GET
|> withHttpClient httpClient
|> withUrl (baseUrl "/login/initialize")
|> fetch
|> json<InitializeResponse> JSON.options
|> withDetails
|> log
and utilize in test scenario:
[<Fact>]
member test.``Initialize session``() = task {
match! sut.Initialize() |> runAsync with
| Error err -> Assert.Fail err.Message
| Ok res ->
Assert.Equal(HttpStatusCode.OK, res.StatusCode)
As a side effect, logs are now more detailed:
info: ____._________.E2E.Framework.Services._____[0]
Oryx: GET https://___________/v1/login/initialize
→ (null)
← { StatusCode = OK
Headers =
map
[("Cache-Control", [|"no-store, must-revalidate, no-cache, private"|]);
("Connection", [|"keep-alive"|]);
("Date", [|"Tue, 22 Nov 2022 17:21:25 GMT"|]);
("Strict-Transport-Security", [|"max-age=15724800; includeSubDomains"|]);
("Vary", [|"Origin"; "Cookie"|])]
Data = { Id = "___________________" } }
Hi there. Thanks for providing such a neat library to ease http requesting. I've currently started using it for testing some of the APIs, and it seems Oryx misses some basic functionality for this stuff, say access to
ResponseCode
,ContentType
andResponseHeaders
. In Oryx, after|> runAsync
what we get isResult<T, exn>
whereT
contains only response body. Is there a way to add another handler, likerunAsyncDetailed
which will returnResult<Details<T>, exn>
whereDetails
are something like this:This would ease using Oryx for testing purpose instead of FsHttp or RestSharp, or even plain HttpClient.