Add parameter SchemaFile to Test-Json cmdlet (#11934)

This commit is contained in:
beatcracker
2020-06-02 13:51:57 -07:00
committed by GitHub
parent b03b968d0a
commit c22ccbebd2
7 changed files with 201 additions and 31 deletions
@@ -2,11 +2,12 @@
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Internal;
using Newtonsoft.Json;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Security;
using Newtonsoft.Json.Linq;
using NJsonSchema;
@@ -15,50 +16,122 @@ namespace Microsoft.PowerShell.Commands
/// <summary>
/// This class implements Test-Json command.
/// </summary>
[Cmdlet(VerbsDiagnostic.Test, "Json", HelpUri = "")]
[Cmdlet(VerbsDiagnostic.Test, "Json", DefaultParameterSetName = ParameterAttribute.AllParameterSets, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096609")]
public class TestJsonCommand : PSCmdlet
{
private const string SchemaFileParameterSet = "SchemaFile";
private const string SchemaStringParameterSet = "SchemaString";
/// <summary>
/// An JSON to be validated.
/// Gets or sets JSON string to be validated.
/// </summary>
[Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)]
public string Json { get; set; }
/// <summary>
/// A schema to validate the JSON against.
/// Gets or sets schema to validate the JSON against.
/// This is optional parameter.
/// If the parameter is absent the cmdlet only attempts to parse the JSON string.
/// If the parameter present the cmdlet attempts to parse the JSON string and
/// then validates the JSON against the schema. Before testing the JSON string,
/// the cmdlet parses the schema doing implicitly check the schema too.
/// </summary>
[Parameter(Position = 1)]
[ValidateNotNullOrEmpty()]
[Parameter(Position = 1, ParameterSetName = SchemaStringParameterSet)]
[ValidateNotNullOrEmpty]
public string Schema { get; set; }
/// <summary>
/// Gets or sets path to the file containg schema to validate the JSON string against.
/// This is optional parameter.
/// </summary>
[Parameter(Position = 1, ParameterSetName = SchemaFileParameterSet)]
[ValidateNotNullOrEmpty]
public string SchemaFile { get; set; }
private JsonSchema _jschema;
/// <summary>
/// Prepare an JSON schema.
/// Process all exceptions in the AggregateException.
/// Unwrap TargetInvocationException if any and
/// rethrow inner exception without losing the stack trace.
/// </summary>
/// <param name="e">AggregateException to be unwrapped.</param>
/// <returns>Return value is unreachable since we always rethrow.</returns>
private static bool UnwrapException(Exception e)
{
if (e is TargetInvocationException)
{
ExceptionDispatchInfo.Capture(e.InnerException).Throw();
}
else
{
ExceptionDispatchInfo.Capture(e).Throw();
}
return true;
}
/// <summary>
/// Prepare a JSON schema.
/// </summary>
protected override void BeginProcessing()
{
if (Schema != null)
string resolvedpath = string.Empty;
try
{
try
if (Schema != null)
{
_jschema = JsonSchema.FromJsonAsync(Schema).Result;
try
{
_jschema = JsonSchema.FromJsonAsync(Schema).Result;
}
catch (AggregateException ae)
{
// Even if only one exception is thrown, it is still wrapped in an AggregateException exception
// https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library
ae.Handle(UnwrapException);
}
}
catch (Exception exc)
else if (SchemaFile != null)
{
Exception exception = new Exception(TestJsonCmdletStrings.InvalidJsonSchema, exc);
ThrowTerminatingError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, null));
try
{
resolvedpath = Context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(SchemaFile);
_jschema = JsonSchema.FromFileAsync(resolvedpath).Result;
}
catch (AggregateException ae)
{
ae.Handle(UnwrapException);
}
}
}
catch (Exception e) when (
// Handle exceptions related to file access to provide more specific error message
// https://docs.microsoft.com/en-us/dotnet/standard/io/handling-io-errors
e is IOException ||
e is UnauthorizedAccessException ||
e is NotSupportedException ||
e is SecurityException
)
{
Exception exception = new Exception(
string.Format(
CultureInfo.CurrentUICulture,
TestJsonCmdletStrings.JsonSchemaFileOpenFailure,
resolvedpath),
e);
ThrowTerminatingError(new ErrorRecord(exception, "JsonSchemaFileOpenFailure", ErrorCategory.OpenError, resolvedpath));
}
catch (Exception e)
{
Exception exception = new Exception(TestJsonCmdletStrings.InvalidJsonSchema, e);
ThrowTerminatingError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, resolvedpath));
}
}
/// <summary>
/// Validate an JSON.
/// Validate a JSON.
/// </summary>
protected override void ProcessRecord()
{
@@ -126,4 +126,7 @@
<data name="InvalidJsonAgainstSchema" xml:space="preserve">
<value>The JSON is not valid with the schema.</value>
</data>
<data name="JsonSchemaFileOpenFailure" xml:space="preserve">
<value>Can not open JSON schema file: {0}</value>
</data>
</root>
@@ -3,6 +3,12 @@
Describe "Test-Json" -Tags "CI" {
BeforeAll {
$validSchemaJsonPath = Join-Path -Path (Join-Path $PSScriptRoot -ChildPath assets) -ChildPath valid_schema_reference.json
$invalidSchemaJsonPath = Join-Path -Path (Join-Path $PSScriptRoot -ChildPath assets) -ChildPath invalid_schema_reference.json
$missingSchemaJsonPath = Join-Path -Path (Join-Path $PSScriptRoot -ChildPath assets) -ChildPath no_such_file.json
$validSchemaJson = @"
{
'description': 'A person',
@@ -61,40 +67,73 @@ Describe "Test-Json" -Tags "CI" {
"@
}
It "Missing JSON schema file doesn't exist" {
Test-Path -LiteralPath $missingSchemaJsonPath | Should -BeFalse
}
It "Json is valid" {
Test-Json -Json $validJson | Should -BeTrue
}
It "Json is valid against a valid schema" {
It "Json is valid against a valid schema from string" {
Test-Json -Json $validJson -Schema $validSchemaJson | Should -BeTrue
}
It "Json is valid against a valid schema from file" {
Test-Json -Json $validJson -SchemaFile $validSchemaJsonPath | Should -BeTrue
}
It "Json is invalid" {
Test-Json -Json $invalidNodeInJson -ErrorAction SilentlyContinue | Should -BeFalse
}
It "Json is invalid against a valid schema" {
It "Json is invalid against a valid schema from string" {
Test-Json -Json $invalidTypeInJson2 -Schema $validSchemaJson -ErrorAction SilentlyContinue | Should -BeFalse
Test-Json -Json $invalidNodeInJson -Schema $validSchemaJson -ErrorAction SilentlyContinue | Should -BeFalse
}
It "Test-Json throw if a schema is invalid" {
It "Json is invalid against a valid schema from file" {
Test-Json -Json $invalidTypeInJson2 -SchemaFile $validSchemaJsonPath -ErrorAction SilentlyContinue | Should -BeFalse
Test-Json -Json $invalidNodeInJson -SchemaFile $validSchemaJsonPath -ErrorAction SilentlyContinue | Should -BeFalse
}
It "Test-Json throw if a schema from string is invalid" {
{ Test-Json -Json $validJson -Schema $invalidSchemaJson -ErrorAction Stop } | Should -Throw -ErrorId "InvalidJsonSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}
It "Test-Json write an error on invalid (<name>) Json against a valid schema" -TestCases @(
@{ name = "type"; json = $invalidTypeInJson; errorId = "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand" }
@{ name = "node"; json = $invalidNodeInJson; errorId = "InvalidJson,Microsoft.PowerShell.Commands.TestJsonCommand" }
) {
param ($json, $errorId)
$errorVar = $null
Test-Json -Json $json -Schema $validSchemaJson -ErrorVariable errorVar -ErrorAction SilentlyContinue
$errorVar.FullyQualifiedErrorId | Should -BeExactly $errorId
It "Test-Json throw if a schema from file is invalid" {
{ Test-Json -Json $validJson -SchemaFile $invalidSchemaJsonPath -ErrorAction Stop } | Should -Throw -ErrorId "InvalidJsonSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}
It "Test-Json return all errors when check invalid Json against a valid schema" {
It "Test-Json throw if a path to a schema from file is invalid" {
{ Test-Json -Json $validJson -SchemaFile $missingSchemaJsonPath -ErrorAction Stop } | Should -Throw -ErrorId "JsonSchemaFileOpenFailure,Microsoft.PowerShell.Commands.TestJsonCommand"
}
It "Test-Json write an error on invalid (<name>) Json against a valid schema from string" -TestCases @(
@{ name = "type"; json = $invalidTypeInJson; errorId = "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand" }
@{ name = "node"; json = $invalidNodeInJson; errorId = "InvalidJson,Microsoft.PowerShell.Commands.TestJsonCommand" }
) {
param ($json, $errorId)
$errorVar = $null
Test-Json -Json $json -Schema $validSchemaJson -ErrorVariable errorVar -ErrorAction SilentlyContinue
$errorVar.FullyQualifiedErrorId | Should -BeExactly $errorId
}
It "Test-Json write an error on invalid (<name>) Json against a valid schema from file" -TestCases @(
@{ name = "type"; json = $invalidTypeInJson; errorId = "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand" }
@{ name = "node"; json = $invalidNodeInJson; errorId = "InvalidJson,Microsoft.PowerShell.Commands.TestJsonCommand" }
) {
param ($json, $errorId)
$errorVar = $null
Test-Json -Json $json -SchemaFile $validSchemaJsonPath -ErrorVariable errorVar -ErrorAction SilentlyContinue
$errorVar.FullyQualifiedErrorId | Should -BeExactly $errorId
}
It "Test-Json return all errors when check invalid Json against a valid schema from string" {
$errorVar = $null
Test-Json -Json $invalidTypeInJson2 -Schema $validSchemaJson -ErrorVariable errorVar -ErrorAction SilentlyContinue
@@ -103,4 +142,14 @@ Describe "Test-Json" -Tags "CI" {
$errorVar[0].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
$errorVar[1].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}
It "Test-Json return all errors when check invalid Json against a valid schema from file" {
$errorVar = $null
Test-Json -Json $invalidTypeInJson2 -SchemaFile $validSchemaJsonPath -ErrorVariable errorVar -ErrorAction SilentlyContinue
# '$invalidTypeInJson2' contains two errors in property types.
$errorVar.Count | Should -Be 2
$errorVar[0].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
$errorVar[1].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}
}
@@ -0,0 +1,8 @@
{
"definitions": {
"name": {
"type": "string"
},
"hobbies"
}
}
@@ -0,0 +1,12 @@
{
"description": "A person",
"type": "object",
"properties": {
"name": {
"$ref": "invalid_schema_definitions.json#/definitions/name"
},
"hobbies": {
"$ref": "invalid_schema_definitions.json#/definitions/hobbies"
}
}
}
@@ -0,0 +1,13 @@
{
"definitions": {
"name": {
"type": "string"
},
"hobbies": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
@@ -0,0 +1,12 @@
{
"description": "A person",
"type": "object",
"properties": {
"name": {
"$ref": "valid_schema_definitions.json#/definitions/name"
},
"hobbies": {
"$ref": "valid_schema_definitions.json#/definitions/hobbies"
}
}
}