Handlebars-Net / Handlebars.Net

A real .NET Handlebars engine
MIT License
1.26k stars 217 forks source link

Using named arguments in partials in nested loop causes error #455

Open glenntKF opened 3 years ago

glenntKF commented 3 years ago

Describe the bug

Hi, we have started using Handlebars.net and it is a great library. We are running into an issue where using named arguments in a partial when in a nested loop causes an error. We are using a JsonDocument from System.Text.Json as the model for the template.

Expected behavior:

Partials in nested loops with named arguments should not cause errors when used.

Test to reproduce

{{#each items }} {{#each this.nested}} {{> myPartial arg1=value1 arg2=value2 }} {{/each} {{/each}}

Other related info

Stack trace HandlebarsDotNet.HandlebarsRuntimeException: Runtime error while rendering partial 'local/details', see inner exception for more information ---> System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. at HandlebarsDotNet.Extension.Json.Utils.GetEnumerator(JsonElement document) at HandlebarsDotNet.BindingContext.PopulateHash(HashParameterDictionary hash, Object from) at HandlebarsDotNet.BindingContext.Initialize() at HandlebarsDotNet.BindingContext.BindingContextPool.CreateContext(ICompiledHandlebarsConfiguration configuration, Object value, BindingContext parent, TemplateDelegate partialBlockTemplate) at lambda_method240(Closure , EncodedTextWriter& , BindingContext ) at HandlebarsDotNet.Extension.Json.JsonElementIterator.IterateArray(EncodedTextWriter& writer, BindingContext context, ChainSegment[] blockParamsVariables, JsonElement target, TemplateDelegate template, TemplateDelegate ifEmpty) at HandlebarsDotNet.Extension.Json.JsonElementIterator.HandlebarsDotNet.Iterators.IIterator.Iterate(EncodedTextWriter& writer, BindingContext context, ChainSegment[] blockParamsVariables, Object input, TemplateDelegate template, TemplateDelegate ifEmpty) at HandlebarsDotNet.Compiler.Iterator.Iterate(BindingContext context, EncodedTextWriter writer, ChainSegment[] blockParamsVariables, Object target, TemplateDelegate template, TemplateDelegate ifEmpty) at lambda_method241(Closure , EncodedTextWriter& , BindingContext ) at HandlebarsDotNet.Extension.Json.JsonElementIterator.IterateArray(EncodedTextWriter& writer, BindingContext context, ChainSegment[] blockParamsVariables, JsonElement target, TemplateDelegate template, TemplateDelegate ifEmpty) at HandlebarsDotNet.Extension.Json.JsonElementIterator.HandlebarsDotNet.Iterators.IIterator.Iterate(EncodedTextWriter& writer, BindingContext context, ChainSegment[] blockParamsVariables, Object input, TemplateDelegate template, TemplateDelegate ifEmpty) at HandlebarsDotNet.Compiler.Iterator.Iterate(BindingContext context, EncodedTextWriter writer, ChainSegment[] blockParamsVariables, Object target, TemplateDelegate template, TemplateDelegate ifEmpty) at lambda_method242(Closure , EncodedTextWriter& , BindingContext ) at HandlebarsDotNet.HandlebarsEnvironment.<>cDisplayClass15_0.b__0(TextWriter writer, Object context, Object data) at HandlebarsDotNet.Compiler.PartialBinder.InvokePartial(String partialName, BindingContext context, EncodedTextWriter writer, ICompiledHandlebarsConfiguration configuration) --- End of inner exception stack trace --- at HandlebarsDotNet.Compiler.PartialBinder.InvokePartial(String partialName, BindingContext context, EncodedTextWriter writer, ICompiledHandlebarsConfiguration configuration) at HandlebarsDotNet.Compiler.PartialBinder.InvokePartialWithFallback(String partialName, BindingContext context, EncodedTextWriter writer, ICompiledHandlebarsConfiguration configuration) at lambda_method253(Closure , EncodedTextWriter& , BindingContext ) at HandlebarsDotNet.HandlebarsEnvironment.<>cDisplayClass15_0.b0(TextWriter writer, Object context, Object data) at HandlebarsDotNet.HandlebarsEnvironment.<>cDisplayClass16_0.b0(Object context, Object data) at Reports.Services.TemplatingService.GenerateReportHtmlAsync(Manifest manifest, JsonDocument reportJson) in /Users/gtolenti/dev/bitbucket/kf-sell-platform/Reports/Services/TemplatingService.cs:line 57 at Reports.Services.ReportService.GenerateReportAsync(JsonDocument reportJson) in /Users/gtolenti/dev/bitbucket/kf-sell-platform/Reports/Services/ReportService.cs:line 93 at Reporting.Controllers.ReportsController.Post(JsonDocument reportJson) in /Users/gtolenti/dev/bitbucket/kf-sell-platform/Reports/Controllers/ReportsController.cs:line 47 at lambda_method7(Closure , Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)


After submitting the issue

Please consider contributing to the project by submitting a PR with a fix to the issue. This would help to solve your problem in a shorter time as well as help other users of the project.

In case you do not know where to start - feel free to ask for help in the issue thread.

Building an active community is essential for any project survival as time of maintainers is limited.

oformaniuk commented 3 years ago

Hello @glenntKF It would be very helpful if you provide a test that can reproduce the bug.

glenntKF commented 3 years ago

Hi @zjklee,

Thanks for the quick reply. Upon further testing it looks like it doesn't have anything to do with nested loops. It occurs in a loop where the JSON being iterated on is an array with values in it:

{
   "items": [
      "value1",
      "value2"
   ]
}

Below is a test case. We are using .net 5 core.

[Fact]
public void Handlebars_EachPartialError()
{
    // Arrange
    string jsonString = @"{
        ""items"": [
            ""item1"",
            ""item2""
        ]
    }";

    JsonDocument json = JsonSerializer.Deserialize<JsonDocument>(jsonString);

    IHandlebars handlebars = HandlebarsDotNet.Handlebars.Create();
    handlebars.Configuration.UseJson();

    string template = @"
        This is a template
        {{> local/myPartial value=""test partial value""}}
        {{#each items}}
            {{name}}          
            {{> local/myPartial value=name}}            
        {{/#each}}";

    handlebars.RegisterTemplate("local/myPartial", "This is a value {{value}}");

    var compiledTemplate = handlebars.Compile(template);

    var result = compiledTemplate(json);

    Assert.Equal("", "");
} 

If I wrap the values in the array with objects it works fine:

{
   "items": [
      {
         "value": "value1"
      },
      {
        "value": "value2"
      }
   ]
}

Thanks.