From 32c97b0c46fe8fb27e38de036dfb617bb25427f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 06:21:52 +0000 Subject: [PATCH 1/2] Initial plan From 8e7d5d8bf34095741a5ff2e2aa4dfa26c1962c71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 06:25:32 +0000 Subject: [PATCH 2/2] Fix compliance findings in Frends.JSON.Handlebars Co-authored-by: MichalFrends1 <167774394+MichalFrends1@users.noreply.github.com> --- Frends.JSON.Handlebars/CHANGELOG.md | 6 +++ .../ErrorHandlerTests.cs | 50 +++++++++++++++++++ .../Frends.JSON.Handlebars.UnitTests.csproj | 2 +- .../UnitTests.cs | 6 ++- .../Definitions/Error.cs | 20 ++++++++ .../Definitions/Options.cs | 25 ++++++++++ .../Definitions/Result.cs | 13 +++-- .../Frends.JSON.Handlebars.csproj | 4 +- .../Frends.JSON.Handlebars/Handlebars.cs | 34 ++++++++----- .../Helpers/ErrorHandler.cs | 34 +++++++++++++ 10 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/ErrorHandlerTests.cs create mode 100644 Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Error.cs create mode 100644 Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Options.cs create mode 100644 Frends.JSON.Handlebars/Frends.JSON.Handlebars/Helpers/ErrorHandler.cs diff --git a/Frends.JSON.Handlebars/CHANGELOG.md b/Frends.JSON.Handlebars/CHANGELOG.md index 3cd2f88..8834c8b 100644 --- a/Frends.JSON.Handlebars/CHANGELOG.md +++ b/Frends.JSON.Handlebars/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [1.3.0] - 2026-08-05 +### Changed +- The task now targets .NET 8. +- The task now supports configurable error handling: a new **Options** parameter lets you choose whether failures throw an exception (default) or return a result with `Success = false` and error details in the `Error` property. +- The result object now includes an `Error` property with the error message and exception details when the task fails without throwing. + ## [1.2.0] - 2024-11-26 ### Changed - Removed old Handlebars DLL from the libs directory and replaced it with the NuGet package Handlebars.Net version 2.1.6. diff --git a/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/ErrorHandlerTests.cs b/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/ErrorHandlerTests.cs new file mode 100644 index 0000000..405089a --- /dev/null +++ b/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/ErrorHandlerTests.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading; +using Frends.JSON.Handlebars.Definitions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Frends.JSON.Handlebars.UnitTests; + +[TestClass] +public class ErrorHandlerTests +{ + private const string CustomErrorMessage = "CustomErrorMessage"; + + private static Input InvalidInput() => new Input + { + Json = "not valid json {{{", + HandlebarTemplate = @"{{title}}", + HandlebarPartials = new HandlebarPartial[0] + }; + + private static Options DefaultOptions() => new Options { ThrowErrorOnFailure = true }; + + [TestMethod] + public void Should_Throw_Error_When_ThrowErrorOnFailure_Is_True() + { + var ex = Assert.ThrowsException(() => + JSON.Handlebars(InvalidInput(), DefaultOptions(), CancellationToken.None)); + Assert.IsNotNull(ex); + } + + [TestMethod] + public void Should_Return_Failed_Result_When_ThrowErrorOnFailure_Is_False() + { + var options = DefaultOptions(); + options.ThrowErrorOnFailure = false; + var result = JSON.Handlebars(InvalidInput(), options, CancellationToken.None); + Assert.IsFalse(result.Success); + Assert.IsNotNull(result.Error); + } + + [TestMethod] + public void Should_Use_Custom_ErrorMessageOnFailure() + { + var options = DefaultOptions(); + options.ErrorMessageOnFailure = CustomErrorMessage; + var ex = Assert.ThrowsException(() => + JSON.Handlebars(InvalidInput(), options, CancellationToken.None)); + Assert.IsNotNull(ex); + StringAssert.Contains(ex.Message, CustomErrorMessage); + } +} diff --git a/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/Frends.JSON.Handlebars.UnitTests.csproj b/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/Frends.JSON.Handlebars.UnitTests.csproj index 604d9d4..9b1d08d 100644 --- a/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/Frends.JSON.Handlebars.UnitTests.csproj +++ b/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/Frends.JSON.Handlebars.UnitTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable diff --git a/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/UnitTests.cs b/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/UnitTests.cs index 6f89071..8f9a0f7 100644 --- a/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/UnitTests.cs +++ b/Frends.JSON.Handlebars/Frends.JSON.Handlebars.UnitTests/UnitTests.cs @@ -6,6 +6,8 @@ namespace Frends.JSON.Handlebars.UnitTests; [TestClass] public class UnitTests { + private static Options DefaultOptions() => new Options { ThrowErrorOnFailure = true }; + [TestMethod] public void HandlebarShouldGenerateTemplate() { @@ -16,7 +18,7 @@ public void HandlebarShouldGenerateTemplate() HandlebarPartials = new HandlebarPartial[0] }; - var result = JSON.Handlebars(input, default); + var result = JSON.Handlebars(input, DefaultOptions(), default); Assert.IsTrue(result.Success); Assert.IsTrue(result.Data.Contains("Mr. Andersson")); } @@ -31,7 +33,7 @@ public void HandlebarShouldGeneratePartials() HandlebarPartials = new[] { new HandlebarPartial { Template = "{{name}}", TemplateName = "strongName" } } }; - var result = JSON.Handlebars(input, default); + var result = JSON.Handlebars(input, DefaultOptions(), default); Assert.IsTrue(result.Success); Assert.IsTrue(result.Data.Contains("Mr. Andersson")); } diff --git a/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Error.cs b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Error.cs new file mode 100644 index 0000000..b6390e2 --- /dev/null +++ b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Error.cs @@ -0,0 +1,20 @@ +using System; + +namespace Frends.JSON.Handlebars.Definitions; + +/// +/// Error information returned when the task fails and ThrowErrorOnFailure is false. +/// +public class Error +{ + /// + /// Human-readable error message. + /// + /// Something went wrong. + public string Message { get; internal set; } + + /// + /// The exception that caused the error, if available. + /// + public Exception AdditionalInfo { get; internal set; } +} diff --git a/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Options.cs b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Options.cs new file mode 100644 index 0000000..bfa24c8 --- /dev/null +++ b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Options.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Frends.JSON.Handlebars.Definitions; + +/// +/// Options parameters. +/// +public class Options +{ + /// + /// Whether to throw an exception on failure or return a Result with Success = false. + /// + /// true + [DefaultValue(true)] + public bool ThrowErrorOnFailure { get; set; } = true; + + /// + /// Custom error message used when an error occurs. + /// + /// + [DisplayFormat(DataFormatString = "Text")] + [DefaultValue("")] + public string ErrorMessageOnFailure { get; set; } = string.Empty; +} diff --git a/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Result.cs b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Result.cs index fffecd1..bf82947 100644 --- a/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Result.cs +++ b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Definitions/Result.cs @@ -9,17 +9,24 @@ public class Result /// Operation complete without errors. /// /// true - public bool Success { get; private set; } + public bool Success { get; internal set; } /// /// Result data. /// /// <div><span>Mr.</span> <strong>Andersson</strong></div> - public string Data { get; private set; } + public string Data { get; internal set; } + + /// + /// Error information when Success is false. + /// + public Error Error { get; internal set; } internal Result(bool success, string data) { Success = success; Data = data; } -} \ No newline at end of file + + internal Result() { } +} diff --git a/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Frends.JSON.Handlebars.csproj b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Frends.JSON.Handlebars.csproj index fc65ff0..bc7b9cb 100644 --- a/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Frends.JSON.Handlebars.csproj +++ b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Frends.JSON.Handlebars.csproj @@ -1,8 +1,8 @@  - net6.0 - 1.2.0 + net8.0 + 1.3.0 Frends Frends Frends diff --git a/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Handlebars.cs b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Handlebars.cs index b200e7b..2396c3c 100644 --- a/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Handlebars.cs +++ b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Handlebars.cs @@ -1,5 +1,7 @@ using Frends.JSON.Handlebars.Definitions; +using Frends.JSON.Handlebars.Helpers; using Newtonsoft.Json.Linq; +using System; using System.ComponentModel; using System.IO; using System.Reflection; @@ -11,7 +13,7 @@ namespace Frends.JSON.Handlebars; /// /// JSON Task. /// -public class JSON +public static class JSON { /// Mem cleanup. static JSON() @@ -27,22 +29,30 @@ static JSON() /// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.JSON.Handlebars) /// /// Input parameters + /// Options parameters /// Token generated by frends to stop this Task. - /// Object { bool Success, string Data } - public static Result Handlebars([PropertyTab] Input input, CancellationToken cancellationToken) + /// Object { bool Success, string Data, Error Error } + public static Result Handlebars([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken) { - var template = HandlebarsDotNet.Handlebars.Compile(input.HandlebarTemplate); + try + { + var template = HandlebarsDotNet.Handlebars.Compile(input.HandlebarTemplate); + + foreach (var partial in input.HandlebarPartials) + { + cancellationToken.ThrowIfCancellationRequested(); + using var reader = new StringReader(partial.Template); + var partialTemplate = HandlebarsDotNet.Handlebars.Compile(reader); + HandlebarsDotNet.Handlebars.RegisterTemplate(partial.TemplateName, partialTemplate); + } - foreach (var partial in input.HandlebarPartials) + JToken jToken = GetJTokenFromInput(input.Json); + return new Result(true, template(jToken)); + } + catch (Exception ex) { - cancellationToken.ThrowIfCancellationRequested(); - using var reader = new StringReader(partial.Template); - var partialTemplate = HandlebarsDotNet.Handlebars.Compile(reader); - HandlebarsDotNet.Handlebars.RegisterTemplate(partial.TemplateName, partialTemplate); + return ex.Handle(options); } - - JToken jToken = GetJTokenFromInput(input.Json); - return new Result(true, template(jToken)); } private static object GetJTokenFromInput(dynamic json) diff --git a/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Helpers/ErrorHandler.cs b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Helpers/ErrorHandler.cs new file mode 100644 index 0000000..825f1de --- /dev/null +++ b/Frends.JSON.Handlebars/Frends.JSON.Handlebars/Helpers/ErrorHandler.cs @@ -0,0 +1,34 @@ +using System; +using Frends.JSON.Handlebars.Definitions; + +namespace Frends.JSON.Handlebars.Helpers; + +internal static class ErrorHandler +{ + internal static Result Handle(this Exception exception, Options options, bool throwCanceled = true) + { + if (throwCanceled && exception is OperationCanceledException) throw exception; + + if (options.ThrowErrorOnFailure) + { + if (string.IsNullOrEmpty(options.ErrorMessageOnFailure)) + throw new Exception(exception.Message, exception); + + throw new Exception(options.ErrorMessageOnFailure, exception); + } + + var errorMessage = string.IsNullOrEmpty(options.ErrorMessageOnFailure) + ? exception.Message + : $"{options.ErrorMessageOnFailure}: {exception.Message}"; + + return new Result + { + Success = false, + Error = new Error + { + Message = errorMessage, + AdditionalInfo = exception, + }, + }; + } +}