Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Frends.JSON.Validate/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [2.0.0] - 2026-08-11

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot we didn't release 1.1.0 we changed from 1.0.0 to 2.0.0, unify 11.0 and 2.0.0 under 2.0.0. and wire [Breaking Change] at the start of this sentence: [Breaking Change] Renamed the ThrowOnInvalidJson...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Merged the 1.1.0 and 2.0.0 entries into a single [2.0.0] entry and prefixed the rename line with [Breaking Change].

### 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.
- [Breaking Change] Renamed the `ThrowOnInvalidJson` option to `FailOnInvalidJson` to better reflect its purpose: it controls whether invalid JSON is treated as an error at all, independent of whether that error is thrown or returned (which is controlled by `ThrowErrorOnFailure`). Update any existing task configurations to use `FailOnInvalidJson`.

## [1.0.0] - 2023-06-15
### Added
- Initial implementation
Original file line number Diff line number Diff line change
@@ -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()
{
FailOnInvalidJson = true,
ThrowErrorOnFailure = true,
};

[TestMethod]
public void Should_Throw_Error_When_ThrowErrorOnFailure_Is_True()
{
var ex = Assert.ThrowsException<Exception>(() =>
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<Exception>(() =>
JSON.Validate(DefaultInput(), options, CancellationToken.None));
Assert.IsNotNull(ex);
Assert.IsTrue(ex.Message.Contains(CustomErrorMessage));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -26,13 +27,13 @@ public class UnitTests
public void StartUp()
{
_input = new Input() { Json = ValidUserJson, JsonSchema = ValidUserSchema };
_options = new Options() { ThrowOnInvalidJson = true };
_options = new Options() { FailOnInvalidJson = true };
}

[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);
Expand All @@ -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)
{
Expand All @@ -66,9 +67,9 @@ public void InvalidSchema()
input.JsonSchema = schema;

var options = _options;
options.ThrowOnInvalidJson = false;
options.FailOnInvalidJson = 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);
Expand Down Expand Up @@ -96,9 +97,9 @@ public void JsonShouldNotValidateToResult()
input.JsonSchema = schema;

var options = _options;
options.ThrowOnInvalidJson = false;
options.FailOnInvalidJson = 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);
Expand Down Expand Up @@ -127,7 +128,7 @@ public void JsonShouldNotValidateThrow()

var options = _options;

var ex = Assert.ThrowsException<JsonException>(() => JSON.Validate(input, _options));
var ex = Assert.ThrowsException<Exception>(() => JSON.Validate(input, _options, CancellationToken.None));
Assert.IsNotNull(ex);
}
}
27 changes: 24 additions & 3 deletions Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Options.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
namespace Frends.JSON.Validate.Definitions;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Frends.JSON.Validate.Definitions;

/// <summary>
/// Options parameters.
/// </summary>
public class Options
{
/// <summary>
/// A flag to indicate whether an error should be thrown if JSON was invalid.
/// A flag to indicate whether invalid JSON should be treated as an error.
/// When true, validation failure or parse error is treated as an error (subject to ThrowErrorOnFailure).
/// When false, parse/validation errors are returned as a non-successful result with Success=false without going through error handling.
/// </summary>
/// <example>true</example>
public bool FailOnInvalidJson { get; set; }

/// <summary>
/// If set to true, the task will throw an exception on failure.
/// If set to false, the task returns a result with Success = false.
/// </summary>
/// <example>true</example>
public bool ThrowOnInvalidJson { get; set; }
[DefaultValue(true)]
public bool ThrowErrorOnFailure { get; set; } = true;

/// <summary>
/// Optional custom error message used when ThrowErrorOnFailure is true or when returning a failed result.
/// </summary>
/// <example></example>
[DisplayFormat(DataFormatString = "Text")]
[DefaultValue("")]
public string ErrorMessageOnFailure { get; set; } = string.Empty;
}
28 changes: 26 additions & 2 deletions Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Result.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;

namespace Frends.JSON.Validate.Definitions;

Expand All @@ -11,7 +12,7 @@ public class Result
/// Operation complete without errors.
/// </summary>
/// <example>true</example>
public bool Success { get; private set; }
public bool Success { get; set; }

/// <summary>
/// JSON was valid.
Expand All @@ -25,10 +26,33 @@ public class Result
/// <example>{ An error occured..., Another error }</example>
public IList<string> Errors { get; set; }

/// <summary>
/// Error information when Success is false.
/// </summary>
public Error Error { get; set; }

internal Result(bool success, bool isValid, IList<string> errors)
{
Success = success;
IsValid = isValid;
Errors = errors;
}

internal Result() { }
}

/// <summary>
/// Error details.
/// </summary>
public class Error
{
/// <summary>
/// Error message.
/// </summary>
public string Message { get; set; }

/// <summary>
/// Additional error information.
/// </summary>
public Exception AdditionalInfo { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<Version>1.0.0</Version>
<TargetFrameworks>net8.0</TargetFrameworks>
<Version>2.0.0</Version>
<Authors>Frends</Authors>
<Copyright>Frends</Copyright>
<Company>Frends</Company>
Expand All @@ -22,7 +22,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Frends.Newtonsoft.SchemaActivation" Version="1.0.0" />
<PackageReference Include="Frends.Newtonsoft.SchemaActivation" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
</ItemGroup>
</Project>
</Project>
45 changes: 45 additions & 0 deletions Frends.JSON.Validate/Frends.JSON.Validate/Helpers/ErrorHandler.cs
Original file line number Diff line number Diff line change
@@ -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,
},
};
}
}
60 changes: 35 additions & 25 deletions Frends.JSON.Validate/Frends.JSON.Validate/Validate.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -7,54 +8,63 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;

namespace Frends.JSON.Validate;

/// <summary>
/// JSON Task.
/// </summary>
public class JSON
public static class JSON
{
/// <summary>
/// Validate your JSON with Json.NET Schema.
/// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.JSON.Validate)
/// </summary>
/// <param name="input">Input parameters</param>
/// <param name="options">Optional parameter.</param>
/// <returns>Object { bool Success, bool IsValid, IList&lt;string&gt; Errors }</returns>
public static Result Validate([PropertyTab] Input input, [PropertyTab] Options options)
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Object { bool Success, bool IsValid, IList&lt;string&gt; Errors, Error Error }</returns>
public static Result Validate([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken)
{
SchemaActivation.Activate();
JSchema schema;
IList<string> 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<string> errors;
JToken jToken;

errors = new List<string>();
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.FailOnInvalidJson)
throw; // re-throw

return new Result(false, false, errors);
}
errors = new List<string>();
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.FailOnInvalidJson)
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);
}
}


Expand Down