M31.FluentAPI icon indicating copy to clipboard operation
M31.FluentAPI copied to clipboard

Feature: `FluentCollection` to allow multiple chained `withItem` method calls

Open oformaniuk opened this issue 1 year ago • 1 comments

Actual behavior:

When using FluentCollection you can use one of three methods withItems, withItem and withZeroItems.

Details

Once you use one of them you're redirected to the next step. Usage of FluentContinueWith targitting to the same step prevents from ever escaping the step. Using combination of FluentContinueWith and FluentSkippable does not work to escape the step loop.

Expected behavior:

When using FluentCollection you can use withItems, withItem and withZeroItems multiple times in a chain while next step is still accessible.

Example

[FluentApi]
public class HttpRequest
{
    [FluentCollection(0, "Header")]
    public List<(string, string)> Headers { get; private set; }

    public void Usage()
    {
        CreateHttpRequest
            .WithHeader(("Accept", "application/json"))
            .WithHeader(("Authorization", "Bearer x"));
    }

}

oformaniuk avatar Sep 19 '24 21:09 oformaniuk

Hi @oformaniuk,

Thank you for your suggestion! I see a conceptual issue with the expected behavior in your example. The WithHeader method can either return the IWithHeaders interface to add more headers or finalize the instance by returning the HttpRequest. It can't do both.

This issue could potentially be resolved by exposing a class instead of an interface in the final builder step, in case it is skippable. The class can have an implicit conversion operator to expose the instance.

Alternatively, a Build method has to be specified like so:

[FluentApi]
public class HttpRequest
{
    [FluentCollection(0, "Header")]
    [FluentSkippable]
    [FluentContinueWith(0)]
    public List<(string, string)> Headers { get; private set; }

    [FluentMethod(2)]
    private void Build()
    {
    }

    public void Usage()
    {
        CreateHttpRequest
            .WithHeader(("Accept", "application/json"))
            .WithHeader(("Authorization", "Bearer x"))
            .Build();
    }
}

However, as you pointed out, FluentSkippable currently doesn't work as expected for escaping the step loop, which I consider a bug - it should work this way.

Best regards, Kevin

m31coding avatar Sep 27 '24 11:09 m31coding

I revisited this issue and came to a different conclusion. I now believe that FluentSkippable behaves as expected in this scenario. Once the step decorated with FluentSkippable is reached, it can no longer be skipped.

Additionally, invoking a FluentCollection step multiple times introduces both semantic and technical challenges. Semantically, similar to how the with keyword works with records, the method WithHeader implies that the provided value should be the header, rather than being added to a collection of existing headers.

From a technical perspective, this would also be difficult to implement because the underlying collection may not necessarily be a list, and appending values may not be possible.

For your example, I recommend the following approach:

[FluentApi]
public class HttpRequest
{
    [FluentCollection(0, "Header")]
    public List<(string, string)> Headers { get; private set; }

    public void Usage()
    {
        CreateHttpRequest
            .WithHeaders(("Accept", "application/json"), ("Authorization", "Bearer x"));
    }
}

m31coding avatar Oct 07 '24 18:10 m31coding