Skip to content

Commit

Permalink
Refactor ModelQuery (#18843)
Browse files Browse the repository at this point in the history
* Refactor ModelQuery.
* Resolve PR feedback.
* Combine constant files.
  • Loading branch information
digimaun authored Feb 18, 2021
1 parent b2d52a1 commit aa993b6
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 74 deletions.
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(ModelRepositoryConstants.ModelProperties.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(ModelRepositoryConstants.ModelProperties.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(ModelRepositoryConstants.ModelProperties.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(ModelRepositoryConstants.ModelProperties.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(ModelRepositoryConstants.ModelProperties.Type, out JsonElement objectType) &&
objectType.ValueKind == JsonValueKind.String &&
objectType.GetString() == ModelRepositoryConstants.ModelProperties.TypeValueInterface;
}

private static bool IsComponentObject(JsonElement root)
{
return root.ValueKind == JsonValueKind.Object &&
root.TryGetProperty(ModelRepositoryConstants.ModelProperties.Type, out JsonElement objectType) &&
objectType.ValueKind == JsonValueKind.String &&
objectType.GetString() == ModelRepositoryConstants.ModelProperties.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 @@ -13,5 +13,20 @@ internal static class ModelRepositoryConstants
// File Extensions
public const string JsonFileExtension = ".json";
public const string ExpandedJsonFileExtension = ".expanded.json";

/// <summary>
/// The <c>ModelRepositoryConstants.ModelProperties</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 ModelProperties
{
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";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,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 @@ -26,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 @@ -70,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 @@ -91,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 @@ -149,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

0 comments on commit aa993b6

Please sign in to comment.