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
7 changes: 7 additions & 0 deletions Frends.JSON.Validate/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
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()
{
ThrowOnInvalidJson = 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 Down Expand Up @@ -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);
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 @@ -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);
Expand Down Expand Up @@ -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);
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);
}
}
21 changes: 20 additions & 1 deletion Frends.JSON.Validate/Frends.JSON.Validate/Definitions/Options.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Frends.JSON.Validate.Definitions;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Frends.JSON.Validate.Definitions;

/// <summary>
/// Options parameters.
Expand All @@ -10,4 +13,20 @@ public class Options
/// </summary>
/// <example>true</example>
public bool ThrowOnInvalidJson { 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>
[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>1.1.0</Version>
<Authors>Frends</Authors>
<Copyright>Frends</Copyright>
<Company>Frends</Company>
Expand Down
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.ThrowOnInvalidJson)
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.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);
}
}


Expand Down