Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate Json schema using JsonSchema.NET #1626

Merged
merged 32 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
92dc278
Use JsonSchema.NET's Evaluate() method to validate a Json schema inst…
MaggieKimani1 Apr 15, 2024
b90aa03
Remove unnecessary using
MaggieKimani1 Apr 15, 2024
681f60a
Add a null check
MaggieKimani1 Apr 15, 2024
e8d504f
Make method public to expose it to clients
MaggieKimani1 Apr 16, 2024
1ecc6fd
Remove whitespace
MaggieKimani1 Apr 16, 2024
1d6b0a6
Decorate the nullable keyword with SchemaSpecVersion attribute for ev…
MaggieKimani1 Apr 16, 2024
f770dd6
Update assertions with correct test output post validation
MaggieKimani1 Apr 17, 2024
6ac327f
Fix failing test
MaggieKimani1 Apr 22, 2024
3f058f4
Refactor code to create JSON schema mappings without a Ref in the com…
MaggieKimani1 Apr 22, 2024
bd5287e
Merge remote-tracking branch 'origin/release/2.0.0' into mk/validate-…
MaggieKimani1 Apr 22, 2024
6e7c5a8
Fix failing tests
MaggieKimani1 Apr 22, 2024
e76101a
Adds an optional host document param to use during validation in orde…
MaggieKimani1 Apr 23, 2024
c829276
Walk each schema to resolve any present $refs
MaggieKimani1 Apr 26, 2024
2baf2f4
Code cleanup
MaggieKimani1 Apr 26, 2024
4591988
Update API interface
MaggieKimani1 Apr 26, 2024
3cae500
Adds an optional host document parameter
MaggieKimani1 Apr 29, 2024
7f5d24c
Register the host document with the schema registry for reference res…
MaggieKimani1 Apr 29, 2024
ff38301
Implement FindSubschema by fetching the referenced schema from our co…
MaggieKimani1 Apr 29, 2024
dcc7b7f
Fix failing tests
MaggieKimani1 Apr 29, 2024
babc887
Set the new schema's baseUri to match the document's
MaggieKimani1 Apr 29, 2024
d6593ab
Refactor V2 schema deserializer to update the referenceable schema's …
MaggieKimani1 Apr 29, 2024
0fb689d
Register the OpenApi-based vocabs
MaggieKimani1 Apr 29, 2024
85a2b54
Add a null check
MaggieKimani1 Apr 29, 2024
b09eb62
Revert the V31 deserializer
MaggieKimani1 Apr 29, 2024
a278267
Use the extension method from JsonSchema.NET to get a dicriminator ob…
MaggieKimani1 Apr 30, 2024
bb2726c
Merge branch 'mk/validate-json-schema' of https://github.com/microsof…
MaggieKimani1 Apr 30, 2024
e93cd4c
Use System.Text to deserialize a node into a JSON schema
MaggieKimani1 Apr 30, 2024
d1cb002
Replace Enumerable methods with indexing
MaggieKimani1 May 2, 2024
5c96d1c
Exclude files from build
MaggieKimani1 May 2, 2024
71c3963
Revert code to fix failing tests
MaggieKimani1 May 2, 2024
d0380ce
Remove static field modifier
MaggieKimani1 May 2, 2024
6cd0f02
Merge branch 'release/2.0.0' into mk/validate-json-schema
MaggieKimani1 May 2, 2024
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 @@ -206,6 +206,7 @@ public void Evaluate(EvaluationContext context)
/// The nullable keyword
/// </summary>
[SchemaKeyword(Name)]
[SchemaSpecVersion(SpecVersion.Draft202012)]
public class NullableKeyword : IJsonSchemaKeyword
Copy link
Member

Choose a reason for hiding this comment

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

Nullable is not a supported keyword in OpenAPI 3.1 It is implemented by assigning an array to the type property and including null as one of the values in the array.

{
/// <summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public static class OpenApiElementExtensions
public static IEnumerable<OpenApiError> Validate(this IOpenApiElement element, ValidationRuleSet ruleSet)
{
var validator = new OpenApiValidator(ruleSet);

if (element is OpenApiDocument doc)
{
validator.HostDocument = doc;
}

var walker = new OpenApiWalker(validator);
walker.Walk(element);
return validator.Errors.Cast<OpenApiError>().Union(validator.Warnings);
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ public static Type MapJsonSchemaValueTypeToSimpleType(this JsonSchema schema)
return result;
}

/// <summary>
/// Converts the Schema value type to its string equivalent
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
internal static string ConvertSchemaValueTypeToString(SchemaValueType value)
{
return value switch
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@

writer.WriteStartObject();

// openApi;

Check warning on line 131 in src/Microsoft.OpenApi/Models/OpenApiDocument.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code. (https://rules.sonarsource.com/csharp/RSPEC-125)
writer.WriteProperty(OpenApiConstants.OpenApi, "3.1.0");

// jsonSchemaDialect
Expand Down Expand Up @@ -490,7 +490,7 @@

if (referenceUri.OriginalString.StartsWith("#"))
{
// Local reference

Check warning on line 493 in src/Microsoft.OpenApi/Models/OpenApiDocument.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
uriLocation = BaseUri + relativePath;
}
else
Expand Down Expand Up @@ -676,7 +676,8 @@
/// <exception cref="NotImplementedException"></exception>
public JsonSchema FindSubschema(Json.Pointer.JsonPointer pointer, EvaluationOptions options)
{
throw new NotImplementedException();
var locationUri = string.Concat(BaseUri, pointer);
return (JsonSchema)Workspace.ResolveReference<IBaseDocument>(locationUri);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.OpenApi/Reader/ParseNodes/FixedFieldMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

using System;
using System.Collections.Generic;
using Microsoft.OpenApi.Models;

namespace Microsoft.OpenApi.Reader.ParseNodes
{
internal class FixedFieldMap<T> : Dictionary<string, Action<T, ParseNode>>
internal class FixedFieldMap<T> : Dictionary<string, Action<T, ParseNode, OpenApiDocument>>
{
}
}
4 changes: 2 additions & 2 deletions src/Microsoft.OpenApi/Reader/ParseNodes/ListNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ public ListNode(ParsingContext context, JsonArray jsonArray) : base(
_nodeList = jsonArray;
}

public override List<T> CreateList<T>(Func<MapNode, OpenApiDocument, T> map)
public override List<T> CreateList<T>(Func<MapNode, OpenApiDocument, T> map, OpenApiDocument hostDocument = null)
{
if (_nodeList == null)
{
throw new OpenApiReaderException($"Expected list while parsing {typeof(T).Name}", _nodeList);
}

return _nodeList?.Select(n => map(new MapNode(Context, n as JsonObject), null))
return _nodeList?.Select(n => map(new MapNode(Context, n as JsonObject), hostDocument))
.Where(i => i != null)
.ToList();
}
Expand Down
29 changes: 6 additions & 23 deletions src/Microsoft.OpenApi/Reader/ParseNodes/MapNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public PropertyNode this[string key]
}
}

public override Dictionary<string, T> CreateMap<T>(Func<MapNode, OpenApiDocument, T> map)
public override Dictionary<string, T> CreateMap<T>(Func<MapNode, OpenApiDocument, T> map, OpenApiDocument hostDocument = null)
{
var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context);
var nodes = jsonMap.Select(
Expand All @@ -62,7 +62,7 @@ public override Dictionary<string, T> CreateMap<T>(Func<MapNode, OpenApiDocument
{
Context.StartObject(key);
value = n.Value is JsonObject jsonObject
? map(new MapNode(Context, jsonObject), null)
? map(new MapNode(Context, jsonObject), hostDocument)
: default;
}
finally
Expand All @@ -79,10 +79,11 @@ public override Dictionary<string, T> CreateMap<T>(Func<MapNode, OpenApiDocument
return nodes.ToDictionary(k => k.key, v => v.value);
}

public override Dictionary<string, JsonSchema> CreateJsonSchemaMapWithReference(
public override Dictionary<string, JsonSchema> CreateJsonSchemaMap(
ReferenceType referenceType,
Func<MapNode, OpenApiDocument, JsonSchema> map,
OpenApiSpecVersion version)
OpenApiSpecVersion version,
OpenApiDocument hostDocument = null)
{
var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(JsonSchema).Name}", Context);

Expand All @@ -95,30 +96,12 @@ public override Dictionary<string, JsonSchema> CreateJsonSchemaMapWithReference(
{
Context.StartObject(key);
entry = (key,
value: map(new MapNode(Context, (JsonObject)n.Value), null)
value: map(new MapNode(Context, (JsonObject)n.Value), hostDocument)
);
if (entry.value == null)
{
return default; // Body Parameters shouldn't be converted to Parameters
}
// If the component isn't a reference to another component, then point it to itself.
if (entry.value.GetRef() == null)
{
var builder = new JsonSchemaBuilder();

// construct the Ref and append it to the builder
var reference = version == OpenApiSpecVersion.OpenApi2_0 ? string.Concat("#/definitions/", entry.key) :
string.Concat("#/components/schemas/", entry.key);

builder.Ref(reference);

// Append all the keywords in original schema to our new schema using a builder instance
foreach (var keyword in entry.value.Keywords)
{
builder.Add(keyword);
}
entry.value = builder.Build();
}
}
finally
{
Expand Down
9 changes: 5 additions & 4 deletions src/Microsoft.OpenApi/Reader/ParseNodes/ParseNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,21 @@ public static ParseNode Create(ParsingContext context, JsonNode node)
return new ValueNode(context, node as JsonValue);
}

public virtual List<T> CreateList<T>(Func<MapNode, OpenApiDocument, T> map)
public virtual List<T> CreateList<T>(Func<MapNode, OpenApiDocument, T> map, OpenApiDocument hostDocument = null)
{
throw new OpenApiReaderException("Cannot create list from this type of node.", Context);
}

public virtual Dictionary<string, T> CreateMap<T>(Func<MapNode, OpenApiDocument, T> map)
public virtual Dictionary<string, T> CreateMap<T>(Func<MapNode, OpenApiDocument, T> map, OpenApiDocument hostDocument = null)
{
throw new OpenApiReaderException("Cannot create map from this type of node.", Context);
}

public virtual Dictionary<string, JsonSchema> CreateJsonSchemaMapWithReference(
public virtual Dictionary<string, JsonSchema> CreateJsonSchemaMap(
ReferenceType referenceType,
Func<MapNode, OpenApiDocument, JsonSchema> map,
OpenApiSpecVersion version)
OpenApiSpecVersion version,
OpenApiDocument hostDocument = null)
{
throw new OpenApiReaderException("Cannot create map from this reference.", Context);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.OpenApi/Reader/ParseNodes/PatternFieldMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

using System;
using System.Collections.Generic;
using Microsoft.OpenApi.Models;

namespace Microsoft.OpenApi.Reader.ParseNodes
{
internal class PatternFieldMap<T> : Dictionary<Func<string, bool>, Action<T, string, ParseNode>>
internal class PatternFieldMap<T> : Dictionary<Func<string, bool>, Action<T, string, ParseNode, OpenApiDocument>>
{
}
}
11 changes: 6 additions & 5 deletions src/Microsoft.OpenApi/Reader/ParseNodes/PropertyNode.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
Expand Down Expand Up @@ -26,15 +26,16 @@ public PropertyNode(ParsingContext context, string name, JsonNode node) : base(

public void ParseField<T>(
T parentInstance,
IDictionary<string, Action<T, ParseNode>> fixedFields,
IDictionary<Func<string, bool>, Action<T, string, ParseNode>> patternFields)
IDictionary<string, Action<T, ParseNode, OpenApiDocument>> fixedFields,
IDictionary<Func<string, bool>, Action<T, string, ParseNode, OpenApiDocument>> patternFields,
OpenApiDocument hostDocument = null)
{
if (fixedFields.TryGetValue(Name, out var fixedFieldMap))
{
try
{
Context.StartObject(Name);
fixedFieldMap(parentInstance, Value);
fixedFieldMap(parentInstance, Value, hostDocument);
}
catch (OpenApiReaderException ex)
{
Expand All @@ -58,7 +59,7 @@ public void ParseField<T>(
try
{
Context.StartObject(Name);
map(parentInstance, Name, Value);
map(parentInstance, Name, Value, hostDocument);
}
catch (OpenApiReaderException ex)
{
Expand Down
Loading
Loading