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

Refactor ModelQuery #18843

Merged
merged 3 commits into from
Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Azure.Iot.ModelsRepository
{
/// <summary>
/// The <c>ModelPropertyConstants</c> class contains DTDL v2 property names and property values
/// used by the <c>ModelQuery</c> class to parse DTDL model key indicators.
/// </summary>
internal static class ModelPropertyConstants
digimaun marked this conversation as resolved.
Show resolved Hide resolved
{
public const string Dtmi = "@id";
public const string Type = "@type";
public const string Extends = "extends";
public const string Contents = "contents";
public const string Schema = "schema";
public const string TypeValueInterface = "Interface";
public const string TypeValueComponent = "Component";
}
}
189 changes: 120 additions & 69 deletions sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@

namespace Azure.Iot.ModelsRepository
{
/// <summary>
/// The <c>ModelQuery</c> class is responsible for parsing DTDL v2 models to produce key metadata.
/// In the current form <c>ModelQuery</c> is focused on determining model dependencies recursively
/// via extends and component schemas.
/// </summary>
internal class ModelQuery
{
private readonly string _content;
Expand All @@ -21,17 +26,24 @@ public ModelQuery(string content)
};
}

public ModelMetadata GetMetadata()
public ModelMetadata ParseModel()
{
return new ModelMetadata(GetId(), GetExtends(), GetComponentSchemas());
using JsonDocument document = JsonDocument.Parse(_content, _parseOptions);
return ParseInterface(document.RootElement);
}

public string GetId()
private static ModelMetadata ParseInterface(JsonElement root)
{
using JsonDocument document = JsonDocument.Parse(_content, _parseOptions);
JsonElement _root = document.RootElement;
string rootDtmi = ParseRootDtmi(root);
IList<string> extends = ParseExtends(root);
IList<string> contents = ParseContents(root);

if (_root.ValueKind == JsonValueKind.Object && _root.TryGetProperty("@id", out JsonElement id))
return new ModelMetadata(rootDtmi, extends, contents);
}

private static string ParseRootDtmi(JsonElement root)
{
if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty(ModelPropertyConstants.Dtmi, out JsonElement id))
{
if (id.ValueKind == JsonValueKind.String)
{
Expand All @@ -42,93 +54,132 @@ public string GetId()
return string.Empty;
}

public IList<string> GetExtends()
private static IList<string> ParseExtends(JsonElement root)
{
List<string> dependencies = new List<string>();
var dependencies = new List<string>();

using JsonDocument document = JsonDocument.Parse(_content, _parseOptions);
JsonElement _root = document.RootElement;
if (root.ValueKind != JsonValueKind.Object)
{
return dependencies;
}

if (_root.ValueKind == JsonValueKind.Object && _root.TryGetProperty("extends", out JsonElement extends))
if (!root.TryGetProperty(ModelPropertyConstants.Extends, out JsonElement extends))
{
if (extends.ValueKind == JsonValueKind.Array)
return dependencies;
}

if (extends.ValueKind == JsonValueKind.String)
{
dependencies.Add(extends.GetString());
}
else if (IsInterfaceObject(extends))
{
ModelMetadata meta = ParseInterface(extends);
dependencies.AddRange(meta.Dependencies);
}
else if (extends.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement extendElement in extends.EnumerateArray())
{
foreach (JsonElement extendElement in extends.EnumerateArray())
if (extendElement.ValueKind == JsonValueKind.String)
{
if (extendElement.ValueKind == JsonValueKind.String)
{
dependencies.Add(extendElement.GetString());
}
else if (extendElement.ValueKind == JsonValueKind.Object)
{
// extends can have multiple levels and can contain components.
// TODO: Support object ctor - inefficient serialize.
ModelMetadata nested_interface = new ModelQuery(JsonSerializer.Serialize(extendElement)).GetMetadata();
dependencies.AddRange(nested_interface.Dependencies);
}
dependencies.Add(extendElement.GetString());
}
// Extends can have multiple levels and contain inline interfaces.
else if (IsInterfaceObject(extendElement))
{
ModelMetadata meta = ParseInterface(extendElement);
dependencies.AddRange(meta.Dependencies);
}
}
else if (extends.ValueKind == JsonValueKind.String)
}

return dependencies;
}

private static IList<string> ParseContents(JsonElement root)
{
var dependencies = new List<string>();

if (root.ValueKind != JsonValueKind.Object)
{
return dependencies;
}

if (!root.TryGetProperty(ModelPropertyConstants.Contents, out JsonElement contents))
{
return dependencies;
}

if (contents.ValueKind != JsonValueKind.Array)
{
return dependencies;
}

foreach (JsonElement contentElement in contents.EnumerateArray())
{
if (IsComponentObject(contentElement))
{
dependencies.Add(extends.GetString());
dependencies.AddRange(ParseComponent(contentElement));
}
}

return dependencies;
}

// TODO: Consider refactor to an object type based processing.
public IList<string> GetComponentSchemas()
private static IList<string> ParseComponent(JsonElement root)
{
List<string> componentSchemas = new List<string>();
// We already know root is an object of @type Component

using JsonDocument document = JsonDocument.Parse(_content, _parseOptions);
JsonElement _root = document.RootElement;
var dependencies = new List<string>();

if (!root.TryGetProperty(ModelPropertyConstants.Schema, out JsonElement componentSchema))
{
return dependencies;
}

if (_root.ValueKind == JsonValueKind.Object && _root.TryGetProperty("contents", out JsonElement contents))
if (componentSchema.ValueKind == JsonValueKind.String)
{
if (contents.ValueKind == JsonValueKind.Array)
dependencies.Add(componentSchema.GetString());
}
else if (IsInterfaceObject(componentSchema))
{
ModelMetadata meta = ParseInterface(componentSchema);
dependencies.AddRange(meta.Dependencies);
}
else if (componentSchema.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement componentSchemaElement in componentSchema.EnumerateArray())
{
foreach (JsonElement element in contents.EnumerateArray())
if (componentSchemaElement.ValueKind == JsonValueKind.String)
{
if (element.TryGetProperty("@type", out JsonElement type))
{
if (type.ValueKind == JsonValueKind.String && type.GetString() == "Component")
{
if (element.TryGetProperty("schema", out JsonElement schema))
{
if (schema.ValueKind == JsonValueKind.String)
{
componentSchemas.Add(schema.GetString());
}
else if (schema.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement schemaElement in schema.EnumerateArray())
{
if (schemaElement.ValueKind == JsonValueKind.String)
{
componentSchemas.Add(schemaElement.GetString());
}
}
}
else if (schema.ValueKind == JsonValueKind.Object)
{
if (schema.TryGetProperty("extends", out JsonElement schemaObjExtends))
{
if (schemaObjExtends.ValueKind == JsonValueKind.String)
{
componentSchemas.Add(schemaObjExtends.GetString());
}
}
}
}
}
}
dependencies.Add(componentSchemaElement.GetString());
}
else if (IsInterfaceObject(componentSchemaElement))
{
ModelMetadata meta = ParseInterface(componentSchemaElement);
dependencies.AddRange(meta.Dependencies);
}
}
}

return componentSchemas;
return dependencies;
}

private static bool IsInterfaceObject(JsonElement root)
{
return root.ValueKind == JsonValueKind.Object &&
root.TryGetProperty(ModelPropertyConstants.Type, out JsonElement objectType) &&
objectType.ValueKind == JsonValueKind.String &&
objectType.GetString() == ModelPropertyConstants.TypeValueInterface;
}

private static bool IsComponentObject(JsonElement root)
{
return root.ValueKind == JsonValueKind.Object &&
root.TryGetProperty(ModelPropertyConstants.Type, out JsonElement objectType) &&
objectType.ValueKind == JsonValueKind.String &&
objectType.GetString() == ModelPropertyConstants.TypeValueComponent;
}

public Dictionary<string, string> ListToDict()
Expand All @@ -149,7 +200,7 @@ public Dictionary<string, string> ListToDict()
using StreamReader streamReader = new StreamReader(stream);
string serialized = streamReader.ReadToEnd();

string id = new ModelQuery(serialized).GetId();
string id = new ModelQuery(serialized).ParseModel().Id;
result.Add(id, serialized);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private async Task<IDictionary<string, string>> ProcessAsync(IEnumerable<string>
continue;
}

ModelMetadata metadata = new ModelQuery(result.Definition).GetMetadata();
ModelMetadata metadata = new ModelQuery(result.Definition).ParseModel();

if (ClientOptions.DependencyResolution >= DependencyResolutionOption.Enabled)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace Azure.Iot.ModelsRepository.Tests
{
Expand All @@ -27,7 +26,7 @@ public void GetId(string formatId, string expectedId)
{
string modelContent = string.Format(_modelTemplate, formatId, "", "");
ModelQuery query = new ModelQuery(modelContent);
Assert.AreEqual(query.GetId(), expectedId);
Assert.AreEqual(query.ParseModel().Id, expectedId);
}

[TestCase(
Expand Down Expand Up @@ -71,7 +70,7 @@ public void GetComponentSchema(string contents, string expected)
string[] expectedDtmis = expected.Split(new[] { "," }, System.StringSplitOptions.RemoveEmptyEntries);
string modelContent = string.Format(_modelTemplate, "", "", contents);
ModelQuery query = new ModelQuery(modelContent);
IList<string> componentSchemas = query.GetComponentSchemas();
IList<string> componentSchemas = query.ParseModel().ComponentSchemas;
Assert.AreEqual(componentSchemas.Count, expectedDtmis.Length);

foreach (string schema in componentSchemas)
Expand All @@ -92,7 +91,7 @@ public void GetExtends(string extends, string expected)
string[] expectedDtmis = expected.Split(new[] { "," }, System.StringSplitOptions.RemoveEmptyEntries);
string modelContent = string.Format(_modelTemplate, "", extends, "");
ModelQuery query = new ModelQuery(modelContent);
IList<string> extendsDtmis = query.GetExtends();
IList<string> extendsDtmis = query.ParseModel().Extends;
Assert.AreEqual(extendsDtmis.Count, expectedDtmis.Length);

foreach (string dtmi in extendsDtmis)
Expand Down Expand Up @@ -150,7 +149,7 @@ public void GetModelDependencies(string id, string extends, string contents, str
{
string[] expectedDtmis = expected.Split(new[] { "," }, System.StringSplitOptions.RemoveEmptyEntries);
string modelContent = string.Format(_modelTemplate, id, extends, contents);
ModelMetadata metadata = new ModelQuery(modelContent).GetMetadata();
ModelMetadata metadata = new ModelQuery(modelContent).ParseModel();

IList<string> dependencies = metadata.Dependencies;

Expand Down