Polly icon indicating copy to clipboard operation
Polly copied to clipboard

How do you set up a HandleResult<Result<T>> for any T?

Open LazerFX opened this issue 4 years ago • 3 comments

I understand the requirements of having a strongly-typed HandleResult<T> and only having one T per policy, however I'm looking to do something with a library that returns a generic wrapped result, with the error message contained within the wrapper.

Example (from memory code, may contain issues):

interface IWebCallResult<T> {
     public bool Success { get; }
     public Error? Error { get; }  // Will be filled in if there's an error.
     public T? Data { get; } // Will be filled in if there's no error
}

I'd like to do something like:

TimoutPolicy = Policy
    .HandleResult<IWebCallResult>(r => r.Success == false && (r.Error?.Message.Contains("timed out") ?? false))
    .RetryAsync();

In other words, if the success is false and the error is timed out, then retry, otherwise fail. I have about 40 of these, so writing loads of boilerplate all saying exactly the same really feels DRY. Is there a way to handle this in Polly?

As it stands, I've got to do something like:

StringTimeoutPolicy = Policy
    .HandleResult<IWebCallResult<string>>(r => r.Success == false && (r.Error?.Message.Contains("timed out") ?? false))
    .RetryAsync();
ObjectTimeoutPolicy = Policy
    .HandleResult<IWebCallResult<object>>(r => r.Success == false && (r.Error?.Message.Contains("timed out") ?? false))
    .RetryAsync();
ThingTimeoutPolicy = Policy
    .HandleResult<IWebCallResult<Thing>>(r => r.Success == false && (r.Error?.Message.Contains("timed out") ?? false))
    .RetryAsync();

LazerFX avatar Nov 01 '21 00:11 LazerFX

I've come to a solution, but I don't like it. I've worked out the following:

StandardPollyTimeoutPolicy = Policy
                .HandleResult<object>(r => {
                    if (r is not WebCallResult webCallResult) { return false; }
                    return webCallResult.Success == false && (webCallResult.Error?.Message.Contains("timed out") ?? false); })
                .WaitAndRetryAsync(
                    retryCount: 5,
                    sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(1), 
                    onRetry: (outcome, retryNumber, ctx) => logger.LogWarning($"Retry: {retryNumber}"));

and to use it, I have to cast the result back as follows (Could have made this generic, but that's for later refactoring):

var result = await StandardPollyTimeoutPolicy.ExecuteAsync(async () => await ICalledObject.DoSomething()) as WebCallResult<string>;

            if (result == null) { throw new InvalidCastException("DoSomething does not return a string"); }

It's clunky and loses all sense of compile-time safety which the Generic<T> method was supposed to provide; but it also provides the .HandleResult<>() method that I require to do a check on the error body that's returned.

This is not, in my mind, a solution, merely a hacky workaround that I'll use until there's a proper solution. If anyone's got a better way, please let me know as I'd love to know it :D

LazerFX avatar Nov 02 '21 22:11 LazerFX

Hi,

I just discovered the same pain point in my application. Trying to register a policy to handle any generic IEnumerable<T>-compatible result which is empty (!coll.Any()), and if so, retry the request until some result are found.

I have the exact same syntax issues as you, if I try to register like

Policy.HandleResult<IEnumerable<object>>(response => !response.Any())

, I end up with InvalidCastExceptions when interpreting any result which implements or inherits at some point from IEnumerable<T>. If I register with only <object>, I also lose compile-time safety.

Please help :)

orty avatar Jan 25 '22 14:01 orty

Sadly, I've found no better way of doing this - but I've also moved on from this project, so it's become less of an issue. I can, however, see that I'll be needing this another time, so I'll post here to echo - still looking, still trying to figure out how to handle this. It might be worth looking at how Flurl handles their responses as they appear to be able to handle this sort of request... I've not used it directly, but I've used code that uses Flurl and they have a retry mechanism built-in that can return a generic <T> style HandleResult-equivalent.

LazerFX avatar Jan 25 '22 14:01 LazerFX

@LazerFX , @orty

This issue is addressed in v8 and will be closed in once v8 is released.

See the example: https://github.com/App-vNext/Polly/blob/v8-fixed-issues/src/Polly.Core.Tests/Issues/IssuesTests.HandleMultipleResults_898.cs

martintmk avatar Apr 25 '23 08:04 martintmk

V8 Alpha released: https://www.nuget.org/packages/Polly.Core/

Samples: https://github.com/App-vNext/Polly/tree/main/samples

martintmk avatar Jun 19 '23 10:06 martintmk