Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,20 @@ public string LiteralPath
[ValidateSet(IgnoreCommentsOption, AllowTrailingCommasOption)]
public string[] Options { get; set; } = Array.Empty<string>();

/// <summary>
/// If set, the cmdlet will retrieve external (URI, file) references in the JSON schema.
/// </summary>
[Parameter]
public SwitchParameter FetchExternalReference { get; set; }

#endregion

#region Private Members

private bool _isLiteralPath = false;
private JsonSchema _jschema;
private JsonDocumentOptions _documentOptions;
private EvaluationOptions _evaluationOptions;

#endregion

Expand All @@ -128,42 +135,47 @@ public string LiteralPath
/// </summary>
protected override void BeginProcessing()
{
// By default, a JSON Schema implementation isn't supposed to automatically fetch content.
// Instead JsonSchema.Net has been set up with a registry so that users can pre-register
// any schemas they may need to resolve.
// However, pre-registering schemas doesn't make sense in the context of a Powershell command,
// and automatically fetching referenced URIs is likely the preferred behavior. To do that,
// this property must be set with a method to retrieve and deserialize the content.
// For more information, see https://json-everything.net/json-schema#automatic-resolution
SchemaRegistry.Global.Fetch = static uri =>
_evaluationOptions = new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical };

if (FetchExternalReference)
{
try
// By default, a JSON Schema implementation isn't supposed to automatically fetch content.
// Instead JsonSchema.Net has been set up with a registry so that users can pre-register
// any schemas they may need to resolve.
// However, pre-registering schemas doesn't make sense in the context of a Powershell command,
// and automatically fetching referenced URIs is likely the preferred behavior. To do that,
// this property must be set with a method to retrieve and deserialize the content.
// For more information, see https://json-everything.net/json-schema#automatic-resolution
_evaluationOptions.SchemaRegistry.Fetch = static uri =>
{
string text;
switch (uri.Scheme)
try
{
case "http":
case "https":
{
using var client = new HttpClient();
text = client.GetStringAsync(uri).Result;
string text;
switch (uri.Scheme)
{
case "http":
case "https":
{
using var client = new HttpClient();
text = client.GetStringAsync(uri).Result;
break;
}
case "file":
var filename = Uri.UnescapeDataString(uri.AbsolutePath);
text = File.ReadAllText(filename);
break;
}
case "file":
var filename = Uri.UnescapeDataString(uri.AbsolutePath);
text = File.ReadAllText(filename);
break;
default:
throw new FormatException(string.Format(TestJsonCmdletStrings.InvalidUriScheme, uri.Scheme));
}
default:
throw new FormatException(string.Format(TestJsonCmdletStrings.InvalidUriScheme, uri.Scheme));
}

return JsonSerializer.Deserialize<JsonSchema>(text);
}
catch (Exception e)
{
throw new JsonSchemaReferenceResolutionException(e);
}
};
return JsonSerializer.Deserialize<JsonSchema>(text);
}
catch (Exception e)
{
throw new JsonSchemaReferenceResolutionException(e);
}
};
}

string resolvedpath = string.Empty;

Expand Down Expand Up @@ -264,7 +276,7 @@ protected override void ProcessRecord()

if (_jschema != null)
{
EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical });
EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, _evaluationOptions);
result = evaluationResults.IsValid;
if (!result)
{
Expand All @@ -279,6 +291,13 @@ protected override void ProcessRecord()
Exception exception = new(TestJsonCmdletStrings.InvalidJsonSchema, jsonExc);
WriteError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, _jschema));
}
catch (RefResolutionException refExc)
{
result = false;

Exception exception = new(TestJsonCmdletStrings.UnresolvedJsonSchemaRef, refExc);
WriteError(new ErrorRecord(exception, "UnresolvedJsonSchemaRef", ErrorCategory.InvalidData, _jschema));
}
catch (Exception exc)
{
result = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,7 @@
<data name="InvalidUriScheme" xml:space="preserve">
<value>URI scheme '{0}' is not supported. Only HTTP(S) and local file system URIs are allowed.</value>
</data>
<data name="UnresolvedJsonSchemaRef" xml:space="preserve">
<value>The JSON schema contains a reference that could not be resolved. External schema references are not supported by default, use the 'FetchExternalReference' parameter to enable it.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
Describe "Test-Json" -Tags "CI" {
BeforeAll {
$assetsPath = Join-Path $PSScriptRoot -ChildPath assets
$validSchemaJsonPath = Join-Path -Path $assetsPath -ChildPath valid_schema_reference.json
$invalidSchemaJsonPath = Join-Path -Path $assetsPath -ChildPath invalid_schema_reference.json
$validSchemaJsonPath = Join-Path -Path $assetsPath -ChildPath valid_schema.json
$validSchemaWithLocalRefJsonPath = Join-Path -Path $assetsPath -ChildPath valid_schema_references_internal.json
$validSchemaWithExternalFileRefJsonPath = Join-Path -Path $assetsPath -ChildPath valid_schema_references_external.json
$validSchemaWithExternalUriRefJsonPath = Join-Path -Path $assetsPath -ChildPath valid_schema_uri_references_external.json
$invalidSchemaWithExternalRefJsonPath = Join-Path -Path $assetsPath -ChildPath invalid_schema_reference.json
$invalidSchemaJsonPath = Join-Path -Path $assetsPath -ChildPath invalid_schema.json
$missingSchemaJsonPath = Join-Path -Path $assetsPath -ChildPath no_such_file.json
$missingJsonPath = Join-Path -Path $assetsPath -ChildPath no_such_file.json

Expand Down Expand Up @@ -252,6 +256,46 @@ Describe "Test-Json" -Tags "CI" {
Test-Json -Json $validJson -Schema $validSchemaJson | Should -BeTrue
}

It "Json is valid against a valid schema with local reference" {
Test-Json -Json $validJson -SchemaFile $validSchemaWithLocalRefJsonPath | Should -BeTrue
}

It "Json is invalid by default against a schema with external file reference" {
Test-Json -Json $validJson -SchemaFile $validSchemaWithExternalFileRefJsonPath -ErrorVariable ErrorVar -ErrorAction SilentlyContinue | Should -BeFalse
$ErrorVar[0].FullyQualifiedErrorId | Should -BeExactly "UnresolvedJsonSchemaRef,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Json is invalid by default against a schema file with external URI reference" {
Test-Json -Json $validJson -SchemaFile $validSchemaWithExternalUriRefJsonPath -ErrorVariable ErrorVar -ErrorAction SilentlyContinue | Should -BeFalse
$ErrorVar[0].FullyQualifiedErrorId | Should -BeExactly "UnresolvedJsonSchemaRef,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Json is valid, with FetchExternalReference parameter, against a schema from file with external file reference" {
Test-Json -Json $validJson -SchemaFile $validSchemaWithExternalFileRefJsonPath -FetchExternalReference | Should -BeTrue
}

It "Json is valid but unresolvable, with FetchExternalReference parameter, against a schema with external URI reference" {
Test-Json -Json $validJson -SchemaFile $validSchemaWithExternalUriRefJsonPath -FetchExternalReference -ErrorVariable ErrorVar -ErrorAction SilentlyContinue | Should -BeFalse
$ErrorVar[0].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Json is invalid, with FetchExternalReference parameter, against an invalid schema with external file reference" {
Test-Json -Json $validJson -SchemaFile $invalidSchemaWithExternalRefJsonPath -FetchExternalReference -ErrorVariable ErrorVar -ErrorAction SilentlyContinue | Should -BeFalse
$ErrorVar[0].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "FetchExternalReference does not affect later invocations" {
Test-Json -Json $validJson -SchemaFile $validSchemaWithExternalFileRefJsonPath -FetchExternalReference | Should -BeTrue

$ErrorVar = @()
Test-Json -Json $validJson -SchemaFile $validSchemaWithExternalFileRefJsonPath -ErrorVariable ErrorVar -ErrorAction SilentlyContinue | Should -BeFalse
$ErrorVar[0].FullyQualifiedErrorId | Should -BeExactly "UnresolvedJsonSchemaRef,Microsoft.PowerShell.Commands.TestJsonCommand"

$ErrorVar = @()
Test-Json -Json $validJson -SchemaFile $validSchemaWithExternalUriRefJsonPath -ErrorVariable ErrorVar -ErrorAction SilentlyContinue | Should -BeFalse
$ErrorVar[0].FullyQualifiedErrorId | Should -BeExactly "UnresolvedJsonSchemaRef,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Json is valid against a valid schema from file" {
Test-Json -Json $validJson -SchemaFile $validSchemaJsonPath | Should -BeTrue
($validJson | Test-Json -SchemaFile $validSchemaJsonPath) | Should -BeTrue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"type": "object",
"properties": {
"name": {
"$ref": "valid_schema_definitions.json#/definitions/name"
"type":"string"
},
"hobbies": {
"$ref": "valid_schema_definitions.json#/definitions/hobbies"
"type": 5
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"description": "A person",
"type": "object",
"properties": {
"name": {
"type":"string"
},
"hobbies": {
"type":"array",
"items":{
"type":"string"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"description": "A person",
"type": "object",
"properties": {
"name": {
"$ref": "valid_schema_definitions_external.json#/definitions/name"
},
"hobbies": {
"$ref": "valid_schema_definitions_external.json#/definitions/hobbies"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"description": "A person",
"type": "object",
"definitions": {
"name": {
"type": "string"
},
"hobbies": {
"type": "array",
"items": {
"type": "string"
}
}
},
"properties": {
"name": {
"$ref": "#/definitions/name"
},
"hobbies": {
"$ref": "#/definitions/hobbies"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"$ref":"http://example.com/root.json"
}
Loading