Skip to content

Commit

Permalink
Remove NoChoiceException (#78)
Browse files Browse the repository at this point in the history
* Remove NoChoiceException

* Use the actual exceptions that we observe.

* Adjust the text in the README.
  • Loading branch information
dbrattli authored Mar 5, 2021
1 parent 53e8b62 commit e9ae9f4
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 15 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,13 +544,13 @@ Oryx v3 will significantly simplify the typing of HTTP handlers by:
2. Error type (`'TError`) is now simply an exception (`exn`).
3. Core logic refactored into a generic middleware (that can be reused for other purposes).

This change effectively makes Oryx an Async Observable:
This change effectively makes Oryx an Async Observable (with context):

```fs
type IHttpNext<'TSource> =
abstract member OnNextAsync: context: HttpContext * ?content: 'TSource -> Task<unit>
abstract member OnErrorAsync: context: HttpContext * error: exn -> Task<unit>
abstract member OnCompletedAsync: context: HttpContext -> Task<unit>
abstract member OnNextAsync: ctx: HttpContext * ?content: 'TSource -> Task<unit>
abstract member OnErrorAsync: ctx: HttpContext * error: exn -> Task<unit>
abstract member OnCompletedAsync: ctx: HttpContext -> Task<unit>
type IHttpHandler<'TSource, 'TResult> =
abstract member Subscribe: next: IHttpNext<'TResult> -> IHttpNext<'TSource>
Expand Down Expand Up @@ -615,7 +615,7 @@ Oryx v3 is mostly backwards compatible with v2. Your chains of operators will fo
same. There are however some notable changes:

- `Context` have been renamed to `HttpContext`.
- `HttpHandler` have been renamed `IHttpHandler`.
- `HttpHandler` have been renamed `IHttpHandler`. This is because `IHttpHandler` is now an interface.
- The `retry` operator has been deprecated for now. Use [Polly](https://github.com/App-vNext/Polly) instead.
- The `catch` operator needs to run __after__ the error producing operator e.g `fetch` (not before). This is because
Oryx v3 pushes results "down" instead of returning them "up" the chain of operators. The good thing with this change
Expand All @@ -627,7 +627,7 @@ same. There are however some notable changes:
`fetch<'TSource, 'TNext>` and the last two types can simply be removed from your code.
- `ResponseError` is gone. You need to sub-class an exception instead. This means that the `'TError' type is also gone
from the handlers.
- Custom context builders do not need any changes
- Custom context builders do not need any changes except renaming `Context` to `HttpContext`.
- Custom HTTP handlers must be refactored. Instead of returning a result (Ok/Error) the handler needs to push down the
result either using the Ok path `next.OnNextAsync()` or fail with an error `next.OnErrorAsync()`. This is very similar
to e.g Reactive Extensions (Rx) `OnNext` / `OnError`. E.g:
Expand Down Expand Up @@ -660,7 +660,7 @@ let withResource (resource: string): HttpHandler<'TSource> =
}}
```

It's a bit more verbose, but the hot path of the code is mostly the same.
It's a bit more verbose, but the hot path of the code is mostly the same as before.

## Upgrade from Oryx v1 to v2

Expand Down
26 changes: 18 additions & 8 deletions src/Middleware/Error.fs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ module Error =

member _.OnCompletedAsync(ctx) = next.OnCompletedAsync(ctx) } }

/// Thrown if no choice found.
exception NoChoiceException of unit

/// Choose from a list of middlewares to use. The first middleware that succeeds will be used.
let choose<'TContext, 'TSource, 'TResult>
(handlers: IAsyncMiddleware<'TContext, 'TSource, 'TResult> seq)
: IAsyncMiddleware<'TContext, 'TSource, 'TResult> =
{ new IAsyncMiddleware<'TContext, 'TSource, 'TResult> with
member _.Subscribe(next) =
let exns : ResizeArray<exn> = ResizeArray()

{ new IAsyncNext<'TContext, 'TSource> with
member _.OnNextAsync(ctx, ?content) =
let mutable found = false
Expand All @@ -44,9 +43,13 @@ module Error =
{ new IAsyncNext<'TContext, 'TResult> with
member _.OnNextAsync(ctx, ?content) =
found <- true
exns.Clear() // Clear to avoid buildup of exceptions in streaming scenarios.
next.OnNextAsync(ctx, ?content = content)

member _.OnErrorAsync(ctx, exn) = Task.FromResult()
member _.OnErrorAsync(ctx, error) =
exns.Add error
Task.FromResult()

member _.OnCompletedAsync _ = Task.FromResult() }

for handler in handlers do
Expand All @@ -56,12 +59,19 @@ module Error =
.Subscribe(obv)
.OnNextAsync(ctx, ?content = content)

if not found then
return! next.OnErrorAsync(ctx, NoChoiceException())
match found, exns with
| false, exns when exns.Count = 1 -> return! next.OnErrorAsync(ctx, exns.[0])
| false, _ -> return! next.OnErrorAsync(ctx, AggregateException(exns))
| true, _ -> ()
}

member _.OnErrorAsync(ctx, exn) = next.OnErrorAsync(ctx, exn)
member _.OnCompletedAsync(ctx) = next.OnCompletedAsync(ctx) } }
member _.OnErrorAsync(ctx, exn) =
exns.Clear()
next.OnErrorAsync(ctx, exn)

member _.OnCompletedAsync(ctx) =
exns.Clear()
next.OnCompletedAsync(ctx) } }

/// Error handler for forcing error. Use with e.g `req` computational expression if you need to "return" an error.
let throw<'TContext, 'TSource, 'TResult> (error: Exception) : IAsyncMiddleware<'TContext, 'TSource, 'TResult> =
Expand Down

0 comments on commit e9ae9f4

Please sign in to comment.