From 933acac221b2e3760cb5901f0d496bcce851c5e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:07:53 +0000 Subject: [PATCH 1/2] Initial plan From 88ef94a6cf491e99f77921d9ea35acb552ee7b3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:13:13 +0000 Subject: [PATCH 2/2] Fix compliance findings in Frends.JSON.Validate Co-authored-by: MichalFrends1 <167774394+MichalFrends1@users.noreply.github.com> --- Frends.JSON.Validate/CHANGELOG.md | 7 +++ .../ErrorHandlerTests.cs | 52 ++++++++++++++++ .../Frends.JSON.Validate.UnitTests.csproj | 2 +- .../UnitTests.cs | 11 ++-- .../Definitions/Options.cs | 21 ++++++- .../Definitions/Result.cs | 28 ++++++++- .../Frends.JSON.Validate.csproj | 4 +- .../Helpers/ErrorHandler.cs | 45 ++++++++++++++ .../Frends.JSON.Validate/Validate.cs | 60 +++++++++++-------- 9 files changed, 194 insertions(+), 36 deletions(-) create mode 100644 Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/ErrorHandlerTests.cs create mode 100644 Frends.JSON.Validate/Frends.JSON.Validate/Helpers/ErrorHandler.cs diff --git a/Frends.JSON.Validate/CHANGELOG.md b/Frends.JSON.Validate/CHANGELOG.md index f3891ed..40e1cd5 100644 --- a/Frends.JSON.Validate/CHANGELOG.md +++ b/Frends.JSON.Validate/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.1.0] - 2026-08-05 +### Changed +- The task now targets .NET 8. +- Added `ThrowErrorOnFailure` and `ErrorMessageOnFailure` options: you can now choose whether the task throws an exception or returns a failed result when an error occurs, and optionally provide a custom error message. +- Added a `CancellationToken` parameter to support task cancellation. +- The `Result` object now includes an `Error` property with details when the task fails. + ## [1.0.0] - 2023-06-15 ### Added - Initial implementation \ No newline at end of file diff --git a/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/ErrorHandlerTests.cs b/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/ErrorHandlerTests.cs new file mode 100644 index 0000000..5e90d73 --- /dev/null +++ b/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/ErrorHandlerTests.cs @@ -0,0 +1,52 @@ +using Frends.JSON.Validate.Definitions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Threading; + +namespace Frends.JSON.Validate.UnitTests; + +[TestClass] +public class ErrorHandlerTests +{ + private const string CustomErrorMessage = "CustomErrorMessage"; + + private static Input DefaultInput() => new() + { + Json = "not valid json {{{{", + JsonSchema = @"{'type': 'object'}" + }; + + private static Options DefaultOptions() => new() + { + ThrowOnInvalidJson = true, + ThrowErrorOnFailure = true, + }; + + [TestMethod] + public void Should_Throw_Error_When_ThrowErrorOnFailure_Is_True() + { + var ex = Assert.ThrowsException(() => + JSON.Validate(DefaultInput(), 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.Validate(DefaultInput(), options, CancellationToken.None); + Assert.IsFalse(result.Success); + } + + [TestMethod] + public void Should_Use_Custom_ErrorMessageOnFailure() + { + var options = DefaultOptions(); + options.ErrorMessageOnFailure = CustomErrorMessage; + var ex = Assert.ThrowsException(() => + JSON.Validate(DefaultInput(), options, CancellationToken.None)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex.Message.Contains(CustomErrorMessage)); + } +} diff --git a/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/Frends.JSON.Validate.UnitTests.csproj b/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/Frends.JSON.Validate.UnitTests.csproj index 2cf24ac..b8e00e0 100644 --- a/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/Frends.JSON.Validate.UnitTests.csproj +++ b/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/Frends.JSON.Validate.UnitTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable diff --git a/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/UnitTests.cs b/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/UnitTests.cs index a17091d..d86f09b 100644 --- a/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/UnitTests.cs +++ b/Frends.JSON.Validate/Frends.JSON.Validate.UnitTests/rends.JSON.Validate.UnitTests/UnitTests.cs @@ -1,6 +1,7 @@ using Frends.JSON.Validate.Definitions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; +using System.Threading; namespace Frends.JSON.Validate.UnitTests; @@ -32,7 +33,7 @@ public void StartUp() [TestMethod] public void JsonShouldValidate() { - var result = JSON.Validate(_input, _options); + var result = JSON.Validate(_input, _options, CancellationToken.None); Assert.IsTrue(result.IsValid); Assert.IsTrue(result.Success); Assert.AreEqual(0, result.Errors.Count); @@ -41,7 +42,7 @@ public void JsonShouldValidate() [TestMethod] public void ShouldHaveLicenseSetForExecutingMoreThan1000Validations() { - var results = Enumerable.Range(0, 2000).Select(i => JSON.Validate(_input, _options)).ToList(); + var results = Enumerable.Range(0, 2000).Select(i => JSON.Validate(_input, _options, CancellationToken.None)).ToList(); foreach (var result in results) { @@ -68,7 +69,7 @@ public void InvalidSchema() var options = _options; options.ThrowOnInvalidJson = false; - var result = JSON.Validate(input, options); + var result = JSON.Validate(input, options, CancellationToken.None); Assert.IsFalse(result.IsValid); Assert.IsFalse(result.Success); Assert.AreEqual(1, result.Errors.Count); @@ -98,7 +99,7 @@ public void JsonShouldNotValidateToResult() var options = _options; options.ThrowOnInvalidJson = false; - var result = JSON.Validate(input, options); + var result = JSON.Validate(input, options, CancellationToken.None); Assert.IsFalse(result.IsValid); Assert.IsTrue(result.Success); Assert.AreEqual(1, result.Errors.Count); @@ -127,7 +128,7 @@ public void JsonShouldNotValidateThrow() var options = _options; - var ex = Assert.ThrowsException(() => JSON.Validate(input, _options)); + var ex = Assert.ThrowsException(() => JSON.Validate(input, _options, CancellationToken.None)); Assert.IsNotNull(ex); } } \ No newline at end of file diff --git a/Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Options.cs b/Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Options.cs index d850d43..d846ec4 100644 --- a/Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Options.cs +++ b/Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Options.cs @@ -1,4 +1,7 @@ -namespace Frends.JSON.Validate.Definitions; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Frends.JSON.Validate.Definitions; /// /// Options parameters. @@ -10,4 +13,20 @@ public class Options /// /// true public bool ThrowOnInvalidJson { get; set; } + + /// + /// If set to true, the task will throw an exception on failure. + /// If set to false, the task returns a result with Success = false. + /// + /// true + [DefaultValue(true)] + public bool ThrowErrorOnFailure { get; set; } = true; + + /// + /// Optional custom error message used when ThrowErrorOnFailure is true or when returning a failed result. + /// + /// + [DisplayFormat(DataFormatString = "Text")] + [DefaultValue("")] + public string ErrorMessageOnFailure { get; set; } = string.Empty; } \ No newline at end of file diff --git a/Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Result.cs b/Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Result.cs index 0501269..009312f 100644 --- a/Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Result.cs +++ b/Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Result.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Frends.JSON.Validate.Definitions; @@ -11,7 +12,7 @@ public class Result /// Operation complete without errors. /// /// true - public bool Success { get; private set; } + public bool Success { get; set; } /// /// JSON was valid. @@ -25,10 +26,33 @@ public class Result /// { An error occured..., Another error } public IList Errors { get; set; } + /// + /// Error information when Success is false. + /// + public Error Error { get; set; } + internal Result(bool success, bool isValid, IList errors) { Success = success; IsValid = isValid; Errors = errors; } + + internal Result() { } +} + +/// +/// Error details. +/// +public class Error +{ + /// + /// Error message. + /// + public string Message { get; set; } + + /// + /// Additional error information. + /// + public Exception AdditionalInfo { get; set; } } \ No newline at end of file diff --git a/Frends.JSON.Validate/Frends.JSON.Validate/Frends.JSON.Validate.csproj b/Frends.JSON.Validate/Frends.JSON.Validate/Frends.JSON.Validate.csproj index a99b90d..f4e771c 100644 --- a/Frends.JSON.Validate/Frends.JSON.Validate/Frends.JSON.Validate.csproj +++ b/Frends.JSON.Validate/Frends.JSON.Validate/Frends.JSON.Validate.csproj @@ -1,8 +1,8 @@  - net6.0 - 1.0.0 + net8.0 + 1.1.0 Frends Frends Frends diff --git a/Frends.JSON.Validate/Frends.JSON.Validate/Helpers/ErrorHandler.cs b/Frends.JSON.Validate/Frends.JSON.Validate/Helpers/ErrorHandler.cs new file mode 100644 index 0000000..ab85302 --- /dev/null +++ b/Frends.JSON.Validate/Frends.JSON.Validate/Helpers/ErrorHandler.cs @@ -0,0 +1,45 @@ +using System; +using Frends.JSON.Validate.Definitions; + +namespace Frends.JSON.Validate.Helpers; + +internal static class ErrorHandler +{ + internal static Result Handle(this Exception exception, Options options, bool throwCanceled = true) + { + ThrowIfCanceled(exception, throwCanceled); + if (options.ThrowErrorOnFailure) ThrowBaseException(exception, options.ErrorMessageOnFailure); + + return ReturnResult(exception, options.ErrorMessageOnFailure); + } + + private static void ThrowIfCanceled(Exception exception, bool throwCanceled = true) + { + if (throwCanceled && exception is OperationCanceledException) throw exception; + } + + private static void ThrowBaseException(Exception exception, string customMessage = null) + { + if (string.IsNullOrEmpty(customMessage)) + throw new Exception(exception.Message, exception); + + throw new Exception(customMessage, exception); + } + + private static Result ReturnResult(Exception exception, string customMessage = null) + { + var errorMessage = string.IsNullOrEmpty(customMessage) + ? exception.Message + : $"{customMessage}: {exception.Message}"; + + return new Result + { + Success = false, + Error = new Error + { + Message = errorMessage, + AdditionalInfo = exception, + }, + }; + } +} diff --git a/Frends.JSON.Validate/Frends.JSON.Validate/Validate.cs b/Frends.JSON.Validate/Frends.JSON.Validate/Validate.cs index 41c6a20..102fc31 100644 --- a/Frends.JSON.Validate/Frends.JSON.Validate/Validate.cs +++ b/Frends.JSON.Validate/Frends.JSON.Validate/Validate.cs @@ -1,4 +1,5 @@ using Frends.JSON.Validate.Definitions; +using Frends.JSON.Validate.Helpers; using Frends.Newtonsoft.SchemaActivation; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -7,13 +8,14 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Threading; namespace Frends.JSON.Validate; /// /// JSON Task. /// -public class JSON +public static class JSON { /// /// Validate your JSON with Json.NET Schema. @@ -21,40 +23,48 @@ public class JSON /// /// Input parameters /// Optional parameter. - /// Object { bool Success, bool IsValid, IList<string> Errors } - public static Result Validate([PropertyTab] Input input, [PropertyTab] Options options) + /// Cancellation token. + /// Object { bool Success, bool IsValid, IList<string> Errors, Error Error } + public static Result Validate([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken) { - SchemaActivation.Activate(); - JSchema schema; - IList errors; - JToken jToken; - try { - schema = JSchema.Parse(input.JsonSchema); - jToken = GetJTokenFromInput(input.Json); - } - catch (Exception exception) - { - if (options.ThrowOnInvalidJson) - throw; // re-throw + SchemaActivation.Activate(); + JSchema schema; + IList errors; + JToken jToken; - errors = new List(); - while (exception != null) + try { - errors.Add(exception.Message); - exception = exception.InnerException; + schema = JSchema.Parse(input.JsonSchema); + jToken = GetJTokenFromInput(input.Json); } + catch (Exception exception) + { + if (options.ThrowOnInvalidJson) + throw; // re-throw - return new Result(false, false, errors); - } + errors = new List(); + while (exception != null) + { + errors.Add(exception.Message); + exception = exception.InnerException; + } - var isValid = jToken.IsValid(schema, out errors); + return new Result(false, false, errors); + } + + var isValid = jToken.IsValid(schema, out errors); - if (!isValid && options.ThrowOnInvalidJson) - throw new JsonException($"Json is not valid. {string.Join("; ", errors)}"); + if (!isValid && options.ThrowOnInvalidJson) + throw new JsonException($"Json is not valid. {string.Join("; ", errors)}"); - return new Result(true, isValid, errors); + return new Result(true, isValid, errors); + } + catch (Exception ex) + { + return ex.Handle(options); + } }