From 067cdcda4273f0ed1d160d26f5a35eb8156bc034 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Fri, 12 Feb 2021 17:07:08 -0800 Subject: [PATCH 01/16] Changes to fix issues. --- .github/CODEOWNERS | 4 + .../src/Azure.Iot.ModelsRepository.csproj | 16 ++ .../src/DependencyResolutionOption.cs | 24 +++ .../src/DtmiConventions.cs | 15 +- .../src/Fetchers/FetchResult.cs | 4 +- .../src/Fetchers/LocalModelFetcher.cs | 2 + .../src/Fetchers/RemoteModelFetcher.cs | 2 + .../src/ModelRepositoryConstants.cs | 15 ++ .../src/RepositoryHandler.cs | 9 +- .../src/ResolverClient.cs | 7 +- .../src/ResolverClientOptions.cs | 26 +--- .../src/ResolverEventSource.cs | 10 +- .../src/ResolverException.cs | 15 +- .../src/ServiceStrings.Designer.cs | 144 +++++++++++++++++ .../src/ServiceStrings.resx | 147 ++++++++++++++++++ .../tests/ClientTests.cs | 7 +- .../tests/DtmiConversionTests.cs | 4 +- .../tests/ResolveIntegrationTests.cs | 69 ++++---- .../tests/TestHelpers.cs | 7 +- 19 files changed, 453 insertions(+), 74 deletions(-) create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ServiceStrings.Designer.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ServiceStrings.resx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index eb0cfe9042dad..c266bd39329e4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -261,6 +261,10 @@ # ServiceLabel: %Digital Twins %Service Attention /sdk/digitaltwins/ @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @bikamani @barustum @jamdavi +# PRLabel: %IoT Model Repository +# ServiceLabel: %IoT Model Repository %Service Attention +/sdk/modelrepository/ @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @bikamani @barustum @jamdavi @digimaun + # PRLabel: %TimeSeriesInsights # ServiceLabel: %TimeSeriesInsights %Service Attention /sdk/timeseriesinsights/ @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @bikamani @barustum @jamdavi @yeskarthik @rasidhan @dmdenmsft diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj index f522da43641c2..6f871b87c2c29 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj @@ -19,5 +19,21 @@ + + + + True + True + ServiceStrings.resx + + + + + + ResXFileCodeGenerator + ServiceStrings.Designer.cs + + + diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs new file mode 100644 index 0000000000000..c1a52d404f247 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Iot.ModelsRepository +{ + /// + /// The dependency processing options. + /// + public enum DependencyResolutionOption + { + /// + /// Do not process external dependencies. + /// + Disabled, + /// + /// Enable external dependencies. + /// + Enabled, + /// + /// Try to get external dependencies using .expanded.json. + /// + TryFromExpanded + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs index 13636761c71e6..3d1034d0c1fa2 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs @@ -3,28 +3,39 @@ using System; using System.Globalization; +using System.Text; using System.Text.RegularExpressions; namespace Azure.Iot.ModelsRepository { internal static class DtmiConventions { - public static bool IsDtmi(string dtmi) => !string.IsNullOrEmpty(dtmi) && new Regex(@"^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$").IsMatch(dtmi); + private static readonly Regex s_validDtmiRegex = new Regex(@"^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$"); + + public static bool IsDtmi(string dtmi) => !string.IsNullOrEmpty(dtmi) && s_validDtmiRegex.IsMatch(dtmi); + public static string DtmiToPath(string dtmi) => IsDtmi(dtmi) ? $"{dtmi.ToLowerInvariant().Replace(":", "/").Replace(";", "-")}.json" : null; public static string DtmiToQualifiedPath(string dtmi, string basePath, bool fromExpanded = false) { string dtmiPath = DtmiToPath(dtmi); + if (dtmiPath == null) + { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, StandardStrings.InvalidDtmiFormat, dtmi)); + } if (!basePath.EndsWith("/", StringComparison.InvariantCultureIgnoreCase)) + { basePath += "/"; + } string fullyQualifiedPath = $"{basePath}{dtmiPath}"; if (fromExpanded) - fullyQualifiedPath = fullyQualifiedPath.Replace(".json", ".expanded.json"); + { + fullyQualifiedPath = fullyQualifiedPath.Replace(ModelRepositoryConstants.JsonFileExtension, ModelRepositoryConstants.ExpandedJsonFileExtension); + } return fullyQualifiedPath; } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs index 30dd40088affb..0fbf0796aff23 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs @@ -6,7 +6,9 @@ namespace Azure.Iot.ModelsRepository.Fetchers internal class FetchResult { public string Definition { get; set; } + public string Path { get; set; } - public bool FromExpanded => Path.EndsWith("expanded.json", System.StringComparison.InvariantCultureIgnoreCase); + + public bool FromExpanded => Path.EndsWith(ModelRepositoryConstants.ExpandedJsonFileExtension, System.StringComparison.InvariantCultureIgnoreCase); } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs index 4eea403dd414a..65cf7518c775f 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs @@ -30,7 +30,9 @@ public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cance var work = new Queue(); if (_tryExpanded) + { work.Enqueue(GetPath(dtmi, repositoryUri, true)); + } work.Enqueue(GetPath(dtmi, repositoryUri, false)); diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs index a68ee01bf37c1..54ec00e129af3 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs @@ -34,7 +34,9 @@ public async Task FetchAsync(string dtmi, Uri repositoryUri, Cancel Queue work = new Queue(); if (_tryExpanded) + { work.Enqueue(GetPath(dtmi, repositoryUri, true)); + } work.Enqueue(GetPath(dtmi, repositoryUri, false)); diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs new file mode 100644 index 0000000000000..3207eecec89e1 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Iot.ModelsRepository +{ + internal static class ModelRepositoryConstants + { + public const string ModelRepositoryEventSourceName = "Azure-Iot-ModelsRepository"; + + // File Extensions + public const string JsonFileExtension = ".json"; + + public const string ExpandedJsonFileExtension = ".expanded.json"; + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs index 9350237b07ed8..3cb242a293456 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs @@ -44,7 +44,7 @@ public async Task> ProcessAsync(IEnumerable if (!DtmiConventions.IsDtmi(dtmi)) { ResolverEventSource.Shared.InvalidDtmiInput(dtmi); - string invalidArgMsg = string.Format(CultureInfo.InvariantCulture, StandardStrings.InvalidDtmiFormat, dtmi); + string invalidArgMsg = string.Format(CultureInfo.InvariantCulture, ServiceStrings.InvalidDtmiFormat, dtmi); throw new ResolverException(dtmi, invalidArgMsg, new ArgumentException(invalidArgMsg)); } @@ -65,10 +65,13 @@ public async Task> ProcessAsync(IEnumerable if (result.FromExpanded) { Dictionary expanded = await new ModelQuery(result.Definition).ListToDictAsync().ConfigureAwait(false); + foreach (KeyValuePair kvp in expanded) { if (!processedModels.ContainsKey(kvp.Key)) + { processedModels.Add(kvp.Key, kvp.Value); + } } continue; @@ -81,7 +84,9 @@ public async Task> ProcessAsync(IEnumerable IList dependencies = metadata.Dependencies; if (dependencies.Count > 0) + { ResolverEventSource.Shared.DiscoveredDependencies(string.Join("\", \"", dependencies)); + } foreach (string dep in dependencies) { @@ -93,7 +98,7 @@ public async Task> ProcessAsync(IEnumerable if (!parsedDtmi.Equals(targetDtmi, StringComparison.Ordinal)) { ResolverEventSource.Shared.IncorrectDtmiCasing(targetDtmi, parsedDtmi); - string formatErrorMsg = string.Format(CultureInfo.InvariantCulture, StandardStrings.IncorrectDtmiCasing, targetDtmi, parsedDtmi); + string formatErrorMsg = string.Format(CultureInfo.InvariantCulture, ServiceStrings.IncorrectDtmiCasing, targetDtmi, parsedDtmi); throw new ResolverException(targetDtmi, formatErrorMsg, new FormatException(formatErrorMsg)); } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs index a6bddeb27c52f..a3f6e896758c1 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs @@ -76,7 +76,8 @@ public ResolverClient(string repositoryUriStr) : this(repositoryUriStr, null) { /// /// ResolverClientOptions to configure resolution and client behavior. /// - public ResolverClient(string repositoryUriStr, ResolverClientOptions options) : this(new Uri(repositoryUriStr), options) { } + public ResolverClient(string repositoryUriStr, ResolverClientOptions options) + : this(new Uri(repositoryUriStr), options) { } /// /// Resolves a model definition identified by and optionally its dependencies. @@ -111,8 +112,8 @@ public virtual async Task> ResolveAsync(string dtmi, /// Thrown when a resolution failure occurs. /// A collection of well-formed DTDL model Ids. /// The cancellationToken. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "AZC0004:DO provide both asynchronous and synchronous variants for all service methods.", Justification = "")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "AZC0015:Unexpected client method return type.", Justification = "")] + [SuppressMessage("Usage", "AZC0004:DO provide both asynchronous and synchronous variants for all service methods.", Justification = "")] + [SuppressMessage("Usage", "AZC0015:Unexpected client method return type.", Justification = "")] public virtual async Task> ResolveAsync(IEnumerable dtmis, CancellationToken cancellationToken = default) { return await _repositoryHandler.ProcessAsync(dtmis, cancellationToken).ConfigureAwait(false); diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClientOptions.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClientOptions.cs index f49cb34ce9b0d..445fd689a7a84 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClientOptions.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClientOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Diagnostics.CodeAnalysis; using Azure.Core; namespace Azure.Iot.ModelsRepository @@ -21,7 +22,7 @@ public enum ServiceVersion /// /// 2021_02_11 /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "")] + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "")] V2021_02_11 = 1 } @@ -39,7 +40,9 @@ public enum ServiceVersion /// making requests. /// /// The dependency processing options. - public ResolverClientOptions(ServiceVersion version = LatestVersion, DependencyResolutionOption resolutionOption = DependencyResolutionOption.Enabled) + public ResolverClientOptions( + ServiceVersion version = LatestVersion, + DependencyResolutionOption resolutionOption = DependencyResolutionOption.Enabled) { DependencyResolution = resolutionOption; Version = version; @@ -50,23 +53,4 @@ public ResolverClientOptions(ServiceVersion version = LatestVersion, DependencyR /// public DependencyResolutionOption DependencyResolution { get; } } - - /// - /// The dependency processing options. - /// - public enum DependencyResolutionOption - { - /// - /// Do not process external dependencies. - /// - Disabled, - /// - /// Enable external dependencies. - /// - Enabled, - /// - /// Try to get external dependencies using .expanded.json. - /// - TryFromExpanded - } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs index 62b47ef3e2a13..3bb1c9319562c 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs @@ -11,7 +11,7 @@ namespace Azure.Iot.ModelsRepository internal sealed class ResolverEventSource : EventSource { // Set EventSource name to package name replacing . with - - private const string EventSourceName = "Azure-Iot-ModelsRepository"; + private const string EventSourceName = ModelRepositoryConstants.ModelRepositoryEventSourceName; // Event ids defined as constants to makes it easy to keep track of them private const int InitFetcherEventId = 1000; @@ -25,7 +25,13 @@ internal sealed class ResolverEventSource : EventSource public static ResolverEventSource Shared { get; } = new ResolverEventSource(); - private ResolverEventSource() : base(EventSourceName, EventSourceSettings.Default, AzureEventSourceListener.TraitName, AzureEventSourceListener.TraitValue) { } + private ResolverEventSource() + : base( + EventSourceName, + EventSourceSettings.Default, + AzureEventSourceListener.TraitName, + AzureEventSourceListener.TraitValue) + { } [Event(InitFetcherEventId, Level = EventLevel.Informational, Message = StandardStrings.ClientInitWithFetcher)] public void InitFetcher(Guid clientId, string scheme) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs index ceb4217c2d213..9cdcc0c519404 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs @@ -15,7 +15,8 @@ public class ResolverException : Exception /// TODO: Paymaun: Exception comments. /// /// - public ResolverException(string dtmi) : base(string.Format(CultureInfo.InvariantCulture, StandardStrings.GenericResolverError, dtmi)) + public ResolverException(string dtmi) + : base(string.Format(CultureInfo.InvariantCulture, ServiceStrings.GenericResolverError, dtmi)) { } @@ -24,8 +25,8 @@ public ResolverException(string dtmi) : base(string.Format(CultureInfo.Invariant /// /// /// - public ResolverException(string dtmi, string message) : - base($"{string.Format(CultureInfo.InvariantCulture, StandardStrings.GenericResolverError, dtmi)}{message}") + public ResolverException(string dtmi, string message) + : base($"{string.Format(CultureInfo.InvariantCulture, ServiceStrings.GenericResolverError, dtmi)} {message}") { } @@ -34,8 +35,8 @@ public ResolverException(string dtmi, string message) : /// /// /// - public ResolverException(string dtmi, Exception innerException) : - base(string.Format(CultureInfo.InvariantCulture, StandardStrings.GenericResolverError, dtmi), innerException) + public ResolverException(string dtmi, Exception innerException) + : base(string.Format(CultureInfo.InvariantCulture, ServiceStrings.GenericResolverError, dtmi), innerException) { } @@ -45,8 +46,8 @@ public ResolverException(string dtmi, Exception innerException) : /// /// /// - public ResolverException(string dtmi, string message, Exception innerException) : - base($"{string.Format(CultureInfo.InvariantCulture, StandardStrings.GenericResolverError, dtmi)}{message}", innerException) + public ResolverException(string dtmi, string message, Exception innerException) + : base($"{string.Format(CultureInfo.InvariantCulture, ServiceStrings.GenericResolverError, dtmi)} {message}", innerException) { } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ServiceStrings.Designer.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ServiceStrings.Designer.cs new file mode 100644 index 0000000000000..bdc55db521257 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ServiceStrings.Designer.cs @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Azure.Iot.ModelsRepository { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ServiceStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ServiceStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Azure.Iot.ModelsRepository.ServiceStrings", typeof(ServiceStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Client session "{0}" initialized with "{1}" content fetcher.. + /// + internal static string ClientInitWithFetcher { + get { + return ResourceManager.GetString("ClientInitWithFetcher", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Discovered dependencies "{0}".. + /// + internal static string DiscoveredDependencies { + get { + return ResourceManager.GetString("DiscoveredDependencies", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Model file "{0}" not found or not accessible in target repository.. + /// + internal static string ErrorFetchingModelContent { + get { + return ResourceManager.GetString("ErrorFetchingModelContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempting to retrieve model content from "{0}".. + /// + internal static string FetchingModelContent { + get { + return ResourceManager.GetString("FetchingModelContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to resolve "{0}"'.. + /// + internal static string GenericResolverError { + get { + return ResourceManager.GetString("GenericResolverError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Retrieved model has incorrect DTMI casing. Expected "{0}", parsed "{1}".. + /// + internal static string IncorrectDtmiCasing { + get { + return ResourceManager.GetString("IncorrectDtmiCasing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid DTMI format "{0}".. + /// + internal static string InvalidDtmiFormat { + get { + return ResourceManager.GetString("InvalidDtmiFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Processing DTMI "{0}".. + /// + internal static string ProcessingDtmi { + get { + return ResourceManager.GetString("ProcessingDtmi", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Already processed DTMI "{0}". Skipping.. + /// + internal static string SkippingPreProcessedDtmi { + get { + return ResourceManager.GetString("SkippingPreProcessedDtmi", resourceCulture); + } + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ServiceStrings.resx b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ServiceStrings.resx new file mode 100644 index 0000000000000..a3c519fd651c1 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ServiceStrings.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Client session "{0}" initialized with "{1}" content fetcher. + + + Discovered dependencies "{0}". + + + Model file "{0}" not found or not accessible in target repository. + + + Attempting to retrieve model content from "{0}". + + + Unable to resolve "{0}"'. + + + Retrieved model has incorrect DTMI casing. Expected "{0}", parsed "{1}". + + + Invalid DTMI format "{0}". + + + Processing DTMI "{0}". + + + Already processed DTMI "{0}". Skipping. + + \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs index 437c0231de9d1..78a2102d07f56 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using System; +using System.Diagnostics.Tracing; using System.Runtime.InteropServices; namespace Azure.Iot.ModelsRepository.Tests @@ -80,9 +81,9 @@ public void EvaluateEventSourceKPIs() Type eventSourceType = typeof(ResolverEventSource); Assert.NotNull(eventSourceType); - Assert.AreEqual("Azure-Iot-ModelsRepository", ResolverEventSource.GetName(eventSourceType)); - Assert.AreEqual(Guid.Parse("7678f8d4-81db-5fd2-39fc-23552d86b171"), ResolverEventSource.GetGuid(eventSourceType)); - Assert.IsNotEmpty(ResolverEventSource.GenerateManifest(eventSourceType, "assemblyPathToIncludeInManifest")); + Assert.AreEqual(ModelRepositoryConstants.ModelRepositoryEventSourceName, EventSource.GetName(eventSourceType)); + Assert.AreEqual(Guid.Parse("7678f8d4-81db-5fd2-39fc-23552d86b171"), EventSource.GetGuid(eventSourceType)); + Assert.IsNotEmpty(EventSource.GenerateManifest(eventSourceType, "assemblyPathToIncludeInManifest")); } } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/DtmiConversionTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/DtmiConversionTests.cs index a8919a1ddcaf5..70153c1e58c50 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/DtmiConversionTests.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/DtmiConversionTests.cs @@ -42,7 +42,9 @@ public void DtmiToQualifiedPath(string dtmi, string expectedPath, string reposit Assert.AreEqual($"{repository}/{expectedPath}", modelPath); string expandedModelPath = DtmiConventions.DtmiToQualifiedPath(dtmi, repository, true); - Assert.AreEqual($"{repository}/{expectedPath.Replace(".json", ".expanded.json")}", expandedModelPath); + Assert.AreEqual( + $"{repository}/{expectedPath.Replace(ModelRepositoryConstants.JsonFileExtension, ModelRepositoryConstants.ExpandedJsonFileExtension)}", + expandedModelPath); } } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ResolveIntegrationTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ResolveIntegrationTests.cs index 4eb95f2bd338a..058e101f1766a 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ResolveIntegrationTests.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ResolveIntegrationTests.cs @@ -17,8 +17,9 @@ public void ResolveWithWrongCasingThrowsException(string dtmi, TestHelpers.Clien { ResolverClient client = TestHelpers.GetTestClient(clientType); string expectedExMsg = - string.Format(StandardStrings.GenericResolverError, "dtmi:com:example:thermostat;1") + - string.Format(StandardStrings.IncorrectDtmiCasing, "dtmi:com:example:thermostat;1", "dtmi:com:example:Thermostat;1"); + string.Format(ServiceStrings.GenericResolverError, "dtmi:com:example:thermostat;1") + + " " + + string.Format(ServiceStrings.IncorrectDtmiCasing, "dtmi:com:example:thermostat;1", "dtmi:com:example:Thermostat;1"); ResolverException re = Assert.ThrowsAsync(async () => await client.ResolveAsync(dtmi)); Assert.AreEqual(re.Message, expectedExMsg); @@ -30,7 +31,7 @@ public void ResolveWithWrongCasingThrowsException(string dtmi, TestHelpers.Clien public void ResolveInvalidDtmiFormatThrowsException(string dtmi) { ResolverClient client = TestHelpers.GetTestClient(TestHelpers.ClientType.Local); - string expectedExMsg = $"{string.Format(StandardStrings.GenericResolverError, dtmi)}{string.Format(StandardStrings.InvalidDtmiFormat, dtmi)}"; + string expectedExMsg = $"{string.Format(ServiceStrings.GenericResolverError, dtmi)} {string.Format(ServiceStrings.InvalidDtmiFormat, dtmi)}"; ResolverException re = Assert.ThrowsAsync(async () => await client.ResolveAsync(dtmi)); Assert.AreEqual(re.Message, expectedExMsg); } @@ -48,8 +49,8 @@ public void ResolveNoneExistentDtmiFileThrowsException(string dtmi, TestHelpers. public void ResolveInvalidDtmiDepsThrowsException(string dtmi, string invalidDep) { ResolverClient client = TestHelpers.GetTestClient(TestHelpers.ClientType.Local); - ResolverException re = Assert.ThrowsAsync(async () => await client.ResolveAsync(dtmi)); - Assert.True(re.Message.StartsWith($"Unable to resolve \"{invalidDep}\"")); + ResolverException resolverException = Assert.ThrowsAsync(async () => await client.ResolveAsync(dtmi)); + Assert.True(resolverException.Message.StartsWith($"Unable to resolve \"{invalidDep}\"")); } [TestCase("dtmi:com:example:Thermostat;1", TestHelpers.ClientType.Local)] @@ -57,7 +58,7 @@ public void ResolveInvalidDtmiDepsThrowsException(string dtmi, string invalidDep public async Task ResolveSingleModelNoDeps(string dtmi, TestHelpers.ClientType clientType) { ResolverClient client = TestHelpers.GetTestClient(clientType); - var result = await client.ResolveAsync(dtmi); + IDictionary result = await client.ResolveAsync(dtmi); Assert.True(result.Keys.Count == 1); Assert.True(result.ContainsKey(dtmi)); Assert.True(TestHelpers.ParseRootDtmiFromJson(result[dtmi]) == dtmi); @@ -68,7 +69,7 @@ public async Task ResolveSingleModelNoDeps(string dtmi, TestHelpers.ClientType c public async Task ResolveMultipleModelsNoDeps(string dtmi1, string dtmi2, TestHelpers.ClientType clientType) { ResolverClient client = TestHelpers.GetTestClient(clientType); - var result = await client.ResolveAsync(new string[] { dtmi1, dtmi2 }); + IDictionary result = await client.ResolveAsync(new string[] { dtmi1, dtmi2 }); Assert.True(result.Keys.Count == 2); Assert.True(result.ContainsKey(dtmi1)); Assert.True(result.ContainsKey(dtmi2)); @@ -76,14 +77,20 @@ public async Task ResolveMultipleModelsNoDeps(string dtmi1, string dtmi2, TestHe Assert.True(TestHelpers.ParseRootDtmiFromJson(result[dtmi2]) == dtmi2); } - [TestCase("dtmi:com:example:TemperatureController;1", - "dtmi:com:example:Thermostat;1,dtmi:azure:DeviceManagement:DeviceInformation;1", TestHelpers.ClientType.Local)] - [TestCase("dtmi:com:example:TemperatureController;1", - "dtmi:com:example:Thermostat;1,dtmi:azure:DeviceManagement:DeviceInformation;1", TestHelpers.ClientType.Remote)] + [TestCase( + "dtmi:com:example:TemperatureController;1", + "dtmi:com:example:Thermostat;1,dtmi:azure:DeviceManagement:DeviceInformation;1", + TestHelpers.ClientType.Local + )] + [TestCase( + "dtmi:com:example:TemperatureController;1", + "dtmi:com:example:Thermostat;1,dtmi:azure:DeviceManagement:DeviceInformation;1", + TestHelpers.ClientType.Remote + )] public async Task ResolveSingleModelWithDeps(string dtmi, string expectedDeps, TestHelpers.ClientType clientType) { ResolverClient client = TestHelpers.GetTestClient(clientType); - var result = await client.ResolveAsync(dtmi); + IDictionary result = await client.ResolveAsync(dtmi); var expectedDtmis = $"{dtmi},{expectedDeps}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); Assert.True(result.Keys.Count == expectedDtmis.Length); @@ -97,18 +104,18 @@ public async Task ResolveSingleModelWithDeps(string dtmi, string expectedDeps, T /* // Verifying log entries for a Process(...) run - _logger.ValidateLog($"{StandardStrings.ClientInitWithFetcher(localClient.RepositoryUri.Scheme)}", LogLevel.Trace, Times.Once()); + _logger.ValidateLog($"{ServiceStringss.ClientInitWithFetcher(localClient.RepositoryUri.Scheme)}", LogLevel.Trace, Times.Once()); - _logger.ValidateLog($"{StandardStrings.ProcessingDtmi("dtmi:com:example:TemperatureController;1")}", LogLevel.Trace, Times.Once()); - _logger.ValidateLog($"{StandardStrings.FetchingContent(DtmiConventions.DtmiToQualifiedPath(expectedDtmis[0], localClient.RepositoryUri.AbsolutePath))}", LogLevel.Trace, Times.Once()); + _logger.ValidateLog($"{ServiceStringss.ProcessingDtmi("dtmi:com:example:TemperatureController;1")}", LogLevel.Trace, Times.Once()); + _logger.ValidateLog($"{ServiceStringss.FetchingContent(DtmiConventions.DtmiToQualifiedPath(expectedDtmis[0], localClient.RepositoryUri.AbsolutePath))}", LogLevel.Trace, Times.Once()); - _logger.ValidateLog($"{StandardStrings.DiscoveredDependencies(new List() { "dtmi:com:example:Thermostat;1", "dtmi:azure:DeviceManagement:DeviceInformation;1" })}", LogLevel.Trace, Times.Once()); + _logger.ValidateLog($"{ServiceStringss.DiscoveredDependencies(new List() { "dtmi:com:example:Thermostat;1", "dtmi:azure:DeviceManagement:DeviceInformation;1" })}", LogLevel.Trace, Times.Once()); - _logger.ValidateLog($"{StandardStrings.ProcessingDtmi("dtmi:com:example:Thermostat;1")}", LogLevel.Trace, Times.Once()); - _logger.ValidateLog($"{StandardStrings.FetchingContent(DtmiConventions.DtmiToQualifiedPath(expectedDtmis[1], localClient.RepositoryUri.AbsolutePath))}", LogLevel.Trace, Times.Once()); + _logger.ValidateLog($"{ServiceStringss.ProcessingDtmi("dtmi:com:example:Thermostat;1")}", LogLevel.Trace, Times.Once()); + _logger.ValidateLog($"{ServiceStringss.FetchingContent(DtmiConventions.DtmiToQualifiedPath(expectedDtmis[1], localClient.RepositoryUri.AbsolutePath))}", LogLevel.Trace, Times.Once()); - _logger.ValidateLog($"{StandardStrings.ProcessingDtmi("dtmi:azure:DeviceManagement:DeviceInformation;1")}", LogLevel.Trace, Times.Once()); - _logger.ValidateLog($"{StandardStrings.FetchingContent(DtmiConventions.DtmiToQualifiedPath(expectedDtmis[2], localClient.RepositoryUri.AbsolutePath))}", LogLevel.Trace, Times.Once()); + _logger.ValidateLog($"{ServiceStringss.ProcessingDtmi("dtmi:azure:DeviceManagement:DeviceInformation;1")}", LogLevel.Trace, Times.Once()); + _logger.ValidateLog($"{ServiceStringss.FetchingContent(DtmiConventions.DtmiToQualifiedPath(expectedDtmis[2], localClient.RepositoryUri.AbsolutePath))}", LogLevel.Trace, Times.Once()); */ } @@ -121,7 +128,7 @@ public async Task ResolveSingleModelWithDeps(string dtmi, string expectedDeps, T public async Task ResolveMultipleModelsWithDeps(string dtmi1, string dtmi2, string expectedDeps) { ResolverClient client = TestHelpers.GetTestClient(TestHelpers.ClientType.Local); - var result = await client.ResolveAsync(new[] { dtmi1, dtmi2 }); + IDictionary result = await client.ResolveAsync(new[] { dtmi1, dtmi2 }); var expectedDtmis = $"{dtmi1},{dtmi2},{expectedDeps}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); Assert.True(result.Keys.Count == expectedDtmis.Length); @@ -138,7 +145,7 @@ public async Task ResolveMultipleModelsWithDeps(string dtmi1, string dtmi2, stri public async Task ResolveMultipleModelsWithDepsFromExtends(string dtmi1, string dtmi2, string expectedDeps) { ResolverClient client = TestHelpers.GetTestClient(TestHelpers.ClientType.Local); - var result = await client.ResolveAsync(new[] { dtmi1, dtmi2 }); + IDictionary result = await client.ResolveAsync(new[] { dtmi1, dtmi2 }); var expectedDtmis = $"{dtmi1},{dtmi2},{expectedDeps}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); Assert.True(result.Keys.Count == expectedDtmis.Length); @@ -158,7 +165,7 @@ public async Task ResolveMultipleModelsWithDepsFromExtends(string dtmi1, string public async Task ResolveMultipleModelsWithDepsFromExtendsVariant(string dtmi1, string dtmi2, string expectedDeps) { ResolverClient client = TestHelpers.GetTestClient(TestHelpers.ClientType.Local); - var result = await client.ResolveAsync(new[] { dtmi1, dtmi2 }); + IDictionary result = await client.ResolveAsync(new[] { dtmi1, dtmi2 }); var expectedDtmis = $"{dtmi1},{dtmi2},{expectedDeps}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); Assert.True(result.Keys.Count == expectedDtmis.Length); @@ -173,7 +180,7 @@ public async Task ResolveMultipleModelsWithDepsFromExtendsVariant(string dtmi1, public async Task ResolveSingleModelWithDepsFromExtendsInline(string dtmi) { ResolverClient client = TestHelpers.GetTestClient(TestHelpers.ClientType.Local); - var result = await client.ResolveAsync(dtmi); + IDictionary result = await client.ResolveAsync(dtmi); Assert.True(result.Keys.Count == 1); Assert.True(result.ContainsKey(dtmi)); @@ -186,7 +193,7 @@ public async Task ResolveSingleModelWithDepsFromExtendsInline(string dtmi) public async Task ResolveSingleModelWithDepsFromExtendsInlineVariant(string dtmi, string expected) { ResolverClient client = TestHelpers.GetTestClient(TestHelpers.ClientType.Local); - var result = await client.ResolveAsync(dtmi); + IDictionary result = await client.ResolveAsync(dtmi); var expectedDtmis = $"{dtmi},{expected}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); Assert.True(result.Keys.Count == expectedDtmis.Length); @@ -201,7 +208,7 @@ public async Task ResolveSingleModelWithDepsFromExtendsInlineVariant(string dtmi public async Task ResolveEnsuresNoDupes(string dtmiDupe1, string dtmiDupe2) { ResolverClient client = TestHelpers.GetTestClient(TestHelpers.ClientType.Local); - var result = await client.ResolveAsync(new[] { dtmiDupe1, dtmiDupe2 }); + IDictionary result = await client.ResolveAsync(new[] { dtmiDupe1, dtmiDupe2 }); Assert.True(result.Keys.Count == 1); Assert.True(TestHelpers.ParseRootDtmiFromJson(result[dtmiDupe1]) == dtmiDupe1); } @@ -235,7 +242,7 @@ public async Task ResolveSingleModelTryFromExpanded(string dtmi, string expected ResolverClientOptions options = new ResolverClientOptions(resolutionOption: DependencyResolutionOption.TryFromExpanded); ResolverClient client = TestHelpers.GetTestClient(clientType, options); - var result = await client.ResolveAsync(dtmi); + IDictionary result = await client.ResolveAsync(dtmi); Assert.True(result.Keys.Count == expectedDtmis.Length); foreach (var id in expectedDtmis) @@ -251,7 +258,7 @@ public async Task ResolveSingleModelTryFromExpanded(string dtmi, string expected dtmi, repoType == "local" ? client.RepositoryUri.AbsolutePath : client.RepositoryUri.AbsoluteUri, fromExpanded: true); - _logger.ValidateLog(StandardStrings.FetchingContent(expectedPath), LogLevel.Trace, Times.Once()); + _logger.ValidateLog(ServiceStringss.FetchingContent(expectedPath), LogLevel.Trace, Times.Once()); */ } @@ -284,13 +291,13 @@ public async Task ResolveMultipleModelsTryFromExpandedPartial(string dtmisExpand /* string expandedModelPath = DtmiConventions.DtmiToQualifiedPath(expandedDtmis[0], localClient.RepositoryUri.AbsolutePath, fromExpanded: true); - _logger.ValidateLog(StandardStrings.FetchingContent(expandedModelPath), LogLevel.Trace, Times.Once()); + _logger.ValidateLog(ServiceStrings.FetchingContent(expandedModelPath), LogLevel.Trace, Times.Once()); foreach (string dtmi in nonExpandedDtmis) { string expectedPath = DtmiConventions.DtmiToQualifiedPath(dtmi, localClient.RepositoryUri.AbsolutePath, fromExpanded: true); - _logger.ValidateLog(StandardStrings.FetchingContent(expectedPath), LogLevel.Trace, Times.Once()); - _logger.ValidateLog(StandardStrings.ErrorAccessLocalRepositoryModel(expectedPath), LogLevel.Warning, Times.Once()); + _logger.ValidateLog(ServiceStrings.FetchingContent(expectedPath), LogLevel.Trace, Times.Once()); + _logger.ValidateLog(ServiceStrings.ErrorAccessLocalRepositoryModel(expectedPath), LogLevel.Warning, Times.Once()); } */ } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestHelpers.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestHelpers.cs index a1e3e3020fb77..a77011f35f10c 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestHelpers.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestHelpers.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using NUnit.Framework; using System; using System.IO; using System.Reflection; @@ -12,6 +11,7 @@ namespace Azure.Iot.ModelsRepository.Tests public class TestHelpers { private static readonly string s_fallbackTestRemoteRepo = "https://devicemodels.azure.com/"; + public enum ClientType { Local, @@ -36,9 +36,14 @@ public static string ParseRootDtmiFromJson(string json) public static ResolverClient GetTestClient(ClientType clientType, ResolverClientOptions clientOptions = null) { if (clientType == ClientType.Local) + { return new ResolverClient(TestLocalModelRepository, clientOptions); + } + if (clientType == ClientType.Remote) + { return new ResolverClient(TestRemoteModelRepository, clientOptions); + } throw new ArgumentException(); } From bfed0353153be1365ae18b16a152210617ca86a0 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 10:02:50 -0800 Subject: [PATCH 02/16] Update RepositoryHandler.cs --- .../Azure.Iot.ModelsRepository/src/RepositoryHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs index 3cb242a293456..417ce52678040 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs @@ -44,7 +44,7 @@ public async Task> ProcessAsync(IEnumerable if (!DtmiConventions.IsDtmi(dtmi)) { ResolverEventSource.Shared.InvalidDtmiInput(dtmi); - string invalidArgMsg = string.Format(CultureInfo.InvariantCulture, ServiceStrings.InvalidDtmiFormat, dtmi); + string invalidArgMsg = string.Format(CultureInfo.CurrentCulture, ServiceStrings.InvalidDtmiFormat, dtmi); throw new ResolverException(dtmi, invalidArgMsg, new ArgumentException(invalidArgMsg)); } From 04ce0753448b20eb72159203087c84686882e7cf Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 10:14:18 -0800 Subject: [PATCH 03/16] Update CultureInfo to use CurrentCulture --- .../Azure.Iot.ModelsRepository/src/DtmiConventions.cs | 6 ++++-- .../src/Fetchers/FetchResult.cs | 4 +++- .../src/Fetchers/RemoteModelFetcher.cs | 2 +- .../Azure.Iot.ModelsRepository/src/RepositoryHandler.cs | 2 +- .../Azure.Iot.ModelsRepository/src/ResolverException.cs | 8 ++++---- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs index 3d1034d0c1fa2..79c271b565c69 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs @@ -22,7 +22,7 @@ public static string DtmiToQualifiedPath(string dtmi, string basePath, bool from if (dtmiPath == null) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, StandardStrings.InvalidDtmiFormat, dtmi)); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, StandardStrings.InvalidDtmiFormat, dtmi)); } if (!basePath.EndsWith("/", StringComparison.InvariantCultureIgnoreCase)) @@ -34,7 +34,9 @@ public static string DtmiToQualifiedPath(string dtmi, string basePath, bool from if (fromExpanded) { - fullyQualifiedPath = fullyQualifiedPath.Replace(ModelRepositoryConstants.JsonFileExtension, ModelRepositoryConstants.ExpandedJsonFileExtension); + fullyQualifiedPath = fullyQualifiedPath.Replace( + ModelRepositoryConstants.JsonFileExtension, + ModelRepositoryConstants.ExpandedJsonFileExtension); } return fullyQualifiedPath; diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs index 0fbf0796aff23..02810bdd872cc 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs @@ -9,6 +9,8 @@ internal class FetchResult public string Path { get; set; } - public bool FromExpanded => Path.EndsWith(ModelRepositoryConstants.ExpandedJsonFileExtension, System.StringComparison.InvariantCultureIgnoreCase); + public bool FromExpanded => Path.EndsWith( + ModelRepositoryConstants.ExpandedJsonFileExtension, + System.StringComparison.InvariantCultureIgnoreCase); } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs index 54ec00e129af3..8030a91ee0a74 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs @@ -57,7 +57,7 @@ public async Task FetchAsync(string dtmi, Uri repositoryUri, Cancel } ResolverEventSource.Shared.ErrorFetchingModelContent(tryContentPath); - remoteFetchError = string.Format(CultureInfo.InvariantCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); + remoteFetchError = string.Format(CultureInfo.CurrentCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); } throw new RequestFailedException(remoteFetchError); diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs index 417ce52678040..b8c8d68be760b 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs @@ -98,7 +98,7 @@ public async Task> ProcessAsync(IEnumerable if (!parsedDtmi.Equals(targetDtmi, StringComparison.Ordinal)) { ResolverEventSource.Shared.IncorrectDtmiCasing(targetDtmi, parsedDtmi); - string formatErrorMsg = string.Format(CultureInfo.InvariantCulture, ServiceStrings.IncorrectDtmiCasing, targetDtmi, parsedDtmi); + string formatErrorMsg = string.Format(CultureInfo.CurrentCulture, ServiceStrings.IncorrectDtmiCasing, targetDtmi, parsedDtmi); throw new ResolverException(targetDtmi, formatErrorMsg, new FormatException(formatErrorMsg)); } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs index 9cdcc0c519404..bb51bf71ce68a 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs @@ -16,7 +16,7 @@ public class ResolverException : Exception /// /// public ResolverException(string dtmi) - : base(string.Format(CultureInfo.InvariantCulture, ServiceStrings.GenericResolverError, dtmi)) + : base(string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi)) { } @@ -26,7 +26,7 @@ public ResolverException(string dtmi) /// /// public ResolverException(string dtmi, string message) - : base($"{string.Format(CultureInfo.InvariantCulture, ServiceStrings.GenericResolverError, dtmi)} {message}") + : base($"{string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi)} {message}") { } @@ -36,7 +36,7 @@ public ResolverException(string dtmi, string message) /// /// public ResolverException(string dtmi, Exception innerException) - : base(string.Format(CultureInfo.InvariantCulture, ServiceStrings.GenericResolverError, dtmi), innerException) + : base(string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi), innerException) { } @@ -47,7 +47,7 @@ public ResolverException(string dtmi, Exception innerException) /// /// public ResolverException(string dtmi, string message, Exception innerException) - : base($"{string.Format(CultureInfo.InvariantCulture, ServiceStrings.GenericResolverError, dtmi)} {message}", innerException) + : base($"{string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi)} {message}", innerException) { } } From 651835b7a35bf7f148e6992e928dbe069984dcf6 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 10:23:07 -0800 Subject: [PATCH 04/16] Formatting updates --- .../src/ModelRepositoryConstants.cs | 2 +- .../src/ResolverEventSource.cs | 4 +--- .../src/ResolverException.cs | 16 ++++------------ 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs index 3207eecec89e1..c4a160fa3d71d 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs @@ -5,11 +5,11 @@ namespace Azure.Iot.ModelsRepository { internal static class ModelRepositoryConstants { + // Set EventSource name to package name replacing '.' with '-' public const string ModelRepositoryEventSourceName = "Azure-Iot-ModelsRepository"; // File Extensions public const string JsonFileExtension = ".json"; - public const string ExpandedJsonFileExtension = ".expanded.json"; } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs index 3bb1c9319562c..56bfbf5789e25 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs @@ -10,7 +10,6 @@ namespace Azure.Iot.ModelsRepository [EventSource(Name = EventSourceName)] internal sealed class ResolverEventSource : EventSource { - // Set EventSource name to package name replacing . with - private const string EventSourceName = ModelRepositoryConstants.ModelRepositoryEventSourceName; // Event ids defined as constants to makes it easy to keep track of them @@ -26,8 +25,7 @@ internal sealed class ResolverEventSource : EventSource public static ResolverEventSource Shared { get; } = new ResolverEventSource(); private ResolverEventSource() - : base( - EventSourceName, + : base(EventSourceName, EventSourceSettings.Default, AzureEventSourceListener.TraitName, AzureEventSourceListener.TraitValue) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs index bb51bf71ce68a..854175e401323 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverException.cs @@ -16,9 +16,7 @@ public class ResolverException : Exception /// /// public ResolverException(string dtmi) - : base(string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi)) - { - } + : base(string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi)) { } /// /// TODO: Paymaun: Exception comments. @@ -26,9 +24,7 @@ public ResolverException(string dtmi) /// /// public ResolverException(string dtmi, string message) - : base($"{string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi)} {message}") - { - } + : base($"{string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi)} {message}") { } /// /// TODO: Paymaun: Exception comments. @@ -36,9 +32,7 @@ public ResolverException(string dtmi, string message) /// /// public ResolverException(string dtmi, Exception innerException) - : base(string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi), innerException) - { - } + : base(string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi), innerException) { } /// /// TODO: Paymaun: Exception comments. @@ -47,8 +41,6 @@ public ResolverException(string dtmi, Exception innerException) /// /// public ResolverException(string dtmi, string message, Exception innerException) - : base($"{string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi)} {message}", innerException) - { - } + : base($"{string.Format(CultureInfo.CurrentCulture, ServiceStrings.GenericResolverError, dtmi)} {message}", innerException) { } } } From 5aec2b6aa0cd42c5dd9952d5391d773f461fccaf Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 11:53:15 -0800 Subject: [PATCH 05/16] Add ClientDiagnostics - Add sync methods. --- .../src/Azure.Iot.ModelsRepository.csproj | 34 ++++ .../src/Fetchers/LocalModelFetcher.cs | 2 +- .../src/Fetchers/RemoteModelFetcher.cs | 190 +++++++++++++++--- .../src/RepositoryHandler.cs | 7 +- .../src/ResolverClient.cs | 42 ++-- .../tests/ClientTests.cs | 2 - .../tests/TestHelpers.cs | 5 + 7 files changed, 229 insertions(+), 53 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj index 6f871b87c2c29..5b0121a2b4c93 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj @@ -35,5 +35,39 @@ + + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs index 65cf7518c775f..9c23343866a34 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs @@ -52,7 +52,7 @@ public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cance } ResolverEventSource.Shared.ErrorFetchingModelContent(tryContentPath); - fnfError = string.Format(CultureInfo.InvariantCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); + fnfError = string.Format(CultureInfo.CurrentCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); } throw new FileNotFoundException(fnfError); diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs index 8030a91ee0a74..50d7184494225 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs @@ -16,20 +16,111 @@ namespace Azure.Iot.ModelsRepository.Fetchers internal class RemoteModelFetcher : IModelFetcher { private readonly HttpPipeline _pipeline; + private readonly ClientDiagnostics _clientDiagnostics; private readonly bool _tryExpanded; - public RemoteModelFetcher(ResolverClientOptions clientOptions) + public RemoteModelFetcher(ClientDiagnostics clientDiagnostics, ResolverClientOptions clientOptions) { _pipeline = CreatePipeline(clientOptions); _tryExpanded = clientOptions.DependencyResolution == DependencyResolutionOption.TryFromExpanded; + _clientDiagnostics = clientDiagnostics; } public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + using DiagnosticScope scope = _clientDiagnostics.CreateScope("RemoteModelFetcher.Fetch"); + scope.Start(); + try + { + Queue work = PrepareWork(dtmi, repositoryUri); + + string remoteFetchError = string.Empty; + + while (work.Count != 0 && !cancellationToken.IsCancellationRequested) + { + string tryContentPath = work.Dequeue(); + + using DiagnosticScope innerScope = _clientDiagnostics.CreateScope( + string.Format( + CultureInfo.CurrentCulture, + ServiceStrings.FetchingModelContent, + tryContentPath)); + + innerScope.Start(); + + try + { + string content = EvaluatePath(tryContentPath, cancellationToken); + return new FetchResult() + { + Definition = content, + Path = tryContentPath + }; + } + catch (Exception ex) + { + innerScope.Failed(ex); + remoteFetchError = string.Format(CultureInfo.CurrentCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); + } + } + + throw new RequestFailedException(remoteFetchError); + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } } public async Task FetchAsync(string dtmi, Uri repositoryUri, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope("RemoteModelFetcher.Fetch"); + scope.Start(); + try + { + Queue work = PrepareWork(dtmi, repositoryUri); + + string remoteFetchError = string.Empty; + + while (work.Count != 0 && !cancellationToken.IsCancellationRequested) + { + string tryContentPath = work.Dequeue(); + + using DiagnosticScope innerScope = _clientDiagnostics.CreateScope( + string.Format( + CultureInfo.CurrentCulture, + ServiceStrings.FetchingModelContent, + tryContentPath)); + + innerScope.Start(); + + try + { + string content = await EvaluatePathAsync(tryContentPath, cancellationToken).ConfigureAwait(false); + return new FetchResult() + { + Definition = content, + Path = tryContentPath + }; + } + catch (Exception ex) + { + innerScope.Failed(ex); + remoteFetchError = string.Format(CultureInfo.CurrentCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); + } + } + + throw new RequestFailedException(remoteFetchError); + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + private Queue PrepareWork(string dtmi, Uri repositoryUri) { Queue work = new Queue(); @@ -40,50 +131,89 @@ public async Task FetchAsync(string dtmi, Uri repositoryUri, Cancel work.Enqueue(GetPath(dtmi, repositoryUri, false)); - string remoteFetchError = string.Empty; - while (work.Count != 0 && !cancellationToken.IsCancellationRequested) + return work; + } + + private static string GetPath(string dtmi, Uri repositoryUri, bool expanded = false) + { + string absoluteUri = repositoryUri.AbsoluteUri; + return DtmiConventions.DtmiToQualifiedPath(dtmi, absoluteUri, expanded); + } + + + private string EvaluatePath(string path, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope("RemoteModelFetcher.EvaluatePath"); + scope.Start(); + + try { - string tryContentPath = work.Dequeue(); - ResolverEventSource.Shared.FetchingModelContent(tryContentPath); + using HttpMessage message = CreateGetRequest(path); - string content = await EvaluatePathAsync(tryContentPath, cancellationToken).ConfigureAwait(false); - if (!string.IsNullOrEmpty(content)) + _pipeline.Send(message, cancellationToken); + + if (message.Response.Status >= 200 && message.Response.Status <= 299) { - return new FetchResult() - { - Definition = content, - Path = tryContentPath - }; + return GetContent(message.Response.ContentStream); + } + else + { + throw _clientDiagnostics.CreateRequestFailedException(message.Response); } - - ResolverEventSource.Shared.ErrorFetchingModelContent(tryContentPath); - remoteFetchError = string.Format(CultureInfo.CurrentCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); } - - throw new RequestFailedException(remoteFetchError); + catch (Exception ex) + { + scope.Failed(ex); + throw; + } } - private static string GetPath(string dtmi, Uri repositoryUri, bool expanded = false) + private async Task EvaluatePathAsync(string path, CancellationToken cancellationToken = default) { - string absoluteUri = repositoryUri.AbsoluteUri; - return DtmiConventions.DtmiToQualifiedPath(dtmi, absoluteUri, expanded); + using DiagnosticScope scope = _clientDiagnostics.CreateScope("RemoteModelFetcher.EvaluatePath"); + scope.Start(); + + try + { + using HttpMessage message = CreateGetRequest(path); + + await _pipeline.SendAsync(message, cancellationToken).ConfigureAwait(false); + + if (message.Response.Status >= 200 && message.Response.Status <= 299) + { + return await GetContentAsync(message.Response.ContentStream, cancellationToken).ConfigureAwait(false); + } + else + { + throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + } + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } } - private async Task EvaluatePathAsync(string path, CancellationToken cancellationToken) + private HttpMessage CreateGetRequest(string path) { - Request request = _pipeline.CreateRequest(); + HttpMessage message = _pipeline.CreateMessage(); + Request request = message.Request; request.Method = RequestMethod.Get; - request.Uri = new RequestUriBuilder(); - request.Uri.Reset(new Uri(path)); + var uri = new RequestUriBuilder(); + uri.Reset(new Uri(path)); + request.Uri = uri; - Response response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false); + return message; + } - if (response.Status >= 200 && response.Status <= 299) + private static string GetContent(Stream content) + { + using (JsonDocument json = JsonDocument.Parse(content)) { - return await GetContentAsync(response.ContentStream, cancellationToken).ConfigureAwait(false); + JsonElement root = json.RootElement; + return root.GetRawText(); } - - return null; } private static async Task GetContentAsync(Stream content, CancellationToken cancellationToken) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs index b8c8d68be760b..529ac3f966f06 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Azure.Core.Pipeline; using Azure.Iot.ModelsRepository.Fetchers; using System; using System.Collections.Generic; @@ -14,17 +15,19 @@ internal class RepositoryHandler { private readonly IModelFetcher _modelFetcher; private readonly Guid _clientId; + private readonly ClientDiagnostics _clientDiagnostics; public Uri RepositoryUri { get; } public ResolverClientOptions ClientOptions { get; } - public RepositoryHandler(Uri repositoryUri, ResolverClientOptions options = null) + public RepositoryHandler(Uri repositoryUri, ClientDiagnostics clientdiagnostics, ResolverClientOptions options = null) { ClientOptions = options ?? new ResolverClientOptions(); RepositoryUri = repositoryUri; + _clientDiagnostics = clientdiagnostics; _modelFetcher = repositoryUri.Scheme == "file" ? _modelFetcher = new LocalModelFetcher(ClientOptions) - : _modelFetcher = new RemoteModelFetcher(ClientOptions); + : _modelFetcher = new RemoteModelFetcher(_clientDiagnostics, ClientOptions); _clientId = Guid.NewGuid(); ResolverEventSource.Shared.InitFetcher(_clientId, repositoryUri.Scheme); } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs index a3f6e896758c1..5fbc02ff2b528 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs @@ -6,6 +6,8 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; +using Azure.Core; +using Azure.Core.Pipeline; namespace Azure.Iot.ModelsRepository { @@ -17,12 +19,13 @@ public class ResolverClient { internal const string DefaultRepository = "https://devicemodels.azure.com"; private readonly RepositoryHandler _repositoryHandler; + private readonly ClientDiagnostics _clientDiagnostics; /// /// Initializes the ResolverClient with default client options while pointing to /// the Azure IoT Plug and Play Model repository https://devicemodels.azure.com for resolution. /// - public ResolverClient() : this(new Uri(DefaultRepository), null) { } + public ResolverClient() : this(new Uri(DefaultRepository), new ResolverClientOptions()) { } /// /// Initializes the ResolverClient with default client options while pointing to @@ -31,7 +34,7 @@ public ResolverClient() : this(new Uri(DefaultRepository), null) { } /// /// The model repository Uri value. This can be a remote endpoint or local directory. /// - public ResolverClient(Uri repositoryUri) : this(repositoryUri, null) { } + public ResolverClient(Uri repositoryUri) : this(repositoryUri, new ResolverClientOptions()) { } /// /// Initializes the ResolverClient with custom client while pointing to @@ -42,21 +45,6 @@ public ResolverClient(Uri repositoryUri) : this(repositoryUri, null) { } /// public ResolverClient(ResolverClientOptions options) : this(new Uri(DefaultRepository), options) { } - /// - /// Initializes the ResolverClient with custom client while pointing to - /// a custom for resolution. - /// - /// - /// The model repository Uri. This can be a remote endpoint or local directory. - /// - /// - /// ResolverClientOptions to configure resolution and client behavior. - /// - public ResolverClient(Uri repositoryUri, ResolverClientOptions options) - { - _repositoryHandler = new RepositoryHandler(repositoryUri, options); - } - /// /// Initializes the ResolverClient with default client options while pointing to /// a custom for resolution. @@ -64,7 +52,7 @@ public ResolverClient(Uri repositoryUri, ResolverClientOptions options) /// /// The model repository Uri in string format. This can be a remote endpoint or local directory. /// - public ResolverClient(string repositoryUriStr) : this(repositoryUriStr, null) { } + public ResolverClient(string repositoryUriStr) : this(repositoryUriStr, new ResolverClientOptions()) { } /// /// Initializes the ResolverClient with custom client while pointing to @@ -79,6 +67,24 @@ public ResolverClient(string repositoryUriStr) : this(repositoryUriStr, null) { public ResolverClient(string repositoryUriStr, ResolverClientOptions options) : this(new Uri(repositoryUriStr), options) { } + /// + /// Initializes the ResolverClient with custom client while pointing to + /// a custom for resolution. + /// + /// + /// The model repository Uri. This can be a remote endpoint or local directory. + /// + /// + /// ResolverClientOptions to configure resolution and client behavior. + /// + public ResolverClient(Uri repositoryUri, ResolverClientOptions options) + { + Argument.AssertNotNull(options, nameof(options)); + + _clientDiagnostics = new ClientDiagnostics(options); + _repositoryHandler = new RepositoryHandler(repositoryUri, _clientDiagnostics, options); + } + /// /// Resolves a model definition identified by and optionally its dependencies. /// diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs index 78a2102d07f56..aff871d3c72c4 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs @@ -24,11 +24,9 @@ public void CtorOverloads() Assert.AreEqual(remoteUri, new ResolverClient(remoteUri).RepositoryUri); Assert.AreEqual(remoteUri, new ResolverClient(remoteUri, options).RepositoryUri); - Assert.AreEqual(remoteUri, new ResolverClient(remoteUri, null).RepositoryUri); Assert.AreEqual(remoteUri, new ResolverClient(remoteUriStr).RepositoryUri); Assert.AreEqual(remoteUri, new ResolverClient(remoteUriStr, options).RepositoryUri); - Assert.AreEqual(remoteUri, new ResolverClient(remoteUriStr, null).RepositoryUri); string localUriStr = TestHelpers.TestLocalModelRepository; Uri localUri = new Uri(localUriStr); diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestHelpers.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestHelpers.cs index a77011f35f10c..f1d0297e832bb 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestHelpers.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestHelpers.cs @@ -35,6 +35,11 @@ public static string ParseRootDtmiFromJson(string json) public static ResolverClient GetTestClient(ClientType clientType, ResolverClientOptions clientOptions = null) { + if (clientOptions == null) + { + clientOptions = new ResolverClientOptions(); + } + if (clientType == ClientType.Local) { return new ResolverClient(TestLocalModelRepository, clientOptions); From 57f86a4c6db8b40324236103c817fedcd08787b1 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 11:59:42 -0800 Subject: [PATCH 06/16] Remove extra line --- .../src/Fetchers/RemoteModelFetcher.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs index 50d7184494225..89b9e91b1a634 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs @@ -140,7 +140,6 @@ private static string GetPath(string dtmi, Uri repositoryUri, bool expanded = fa return DtmiConventions.DtmiToQualifiedPath(dtmi, absoluteUri, expanded); } - private string EvaluatePath(string path, CancellationToken cancellationToken = default) { using DiagnosticScope scope = _clientDiagnostics.CreateScope("RemoteModelFetcher.EvaluatePath"); From e54f75229048001046dc59882b81378c1828feb7 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 13:42:49 -0800 Subject: [PATCH 07/16] Add clientDiagnostics to LocalModelFetcher --- .../src/Fetchers/LocalModelFetcher.cs | 54 ++++++++----- .../src/Fetchers/RemoteModelFetcher.cs | 77 ++++++++----------- .../src/RepositoryHandler.cs | 14 ++-- .../src/ResolverEventSource.cs | 2 +- 4 files changed, 74 insertions(+), 73 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs index 9c23343866a34..e9e466ac9bf9c 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs @@ -8,15 +8,18 @@ using System.Collections.Generic; using System.Threading; using System.Globalization; +using Azure.Core.Pipeline; namespace Azure.Iot.ModelsRepository.Fetchers { internal class LocalModelFetcher : IModelFetcher { private readonly bool _tryExpanded; + private readonly ClientDiagnostics _clientDiagnostics; - public LocalModelFetcher(ResolverClientOptions clientOptions) + public LocalModelFetcher(ClientDiagnostics clientDiagnostics, ResolverClientOptions clientOptions) { + _clientDiagnostics = clientDiagnostics; _tryExpanded = clientOptions.DependencyResolution == DependencyResolutionOption.TryFromExpanded; } @@ -27,35 +30,46 @@ public Task FetchAsync(string dtmi, Uri repositoryUri, Cancellation public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cancellationToken = default) { - var work = new Queue(); + using DiagnosticScope scope = _clientDiagnostics.CreateScope("LocalModelFetcher.Fetch"); + scope.Start(); - if (_tryExpanded) + try { - work.Enqueue(GetPath(dtmi, repositoryUri, true)); - } + var work = new Queue(); - work.Enqueue(GetPath(dtmi, repositoryUri, false)); + if (_tryExpanded) + { + work.Enqueue(GetPath(dtmi, repositoryUri, true)); + } - string fnfError = string.Empty; - while (work.Count != 0 && !cancellationToken.IsCancellationRequested) - { - string tryContentPath = work.Dequeue(); - ResolverEventSource.Shared.FetchingModelContent(tryContentPath); + work.Enqueue(GetPath(dtmi, repositoryUri, false)); - if (File.Exists(tryContentPath)) + string fnfError = string.Empty; + while (work.Count != 0 && !cancellationToken.IsCancellationRequested) { - return new FetchResult() + string tryContentPath = work.Dequeue(); + ResolverEventSource.Instance.FetchingModelContent(tryContentPath); + + if (File.Exists(tryContentPath)) { - Definition = File.ReadAllText(tryContentPath, Encoding.UTF8), - Path = tryContentPath - }; + return new FetchResult + { + Definition = File.ReadAllText(tryContentPath, Encoding.UTF8), + Path = tryContentPath + }; + } + + ResolverEventSource.Instance.ErrorFetchingModelContent(tryContentPath); + fnfError = string.Format(CultureInfo.CurrentCulture, ServiceStrings.ErrorFetchingModelContent, tryContentPath); } - ResolverEventSource.Shared.ErrorFetchingModelContent(tryContentPath); - fnfError = string.Format(CultureInfo.CurrentCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); + throw new RequestFailedException(fnfError, new FileNotFoundException(fnfError)); + } + catch (Exception ex) + { + scope.Failed(ex); + throw; } - - throw new FileNotFoundException(fnfError); } private static string GetPath(string dtmi, Uri repositoryUri, bool expanded = false) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs index 89b9e91b1a634..31933d2c793d0 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs @@ -36,30 +36,24 @@ public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cance string remoteFetchError = string.Empty; - while (work.Count != 0 && !cancellationToken.IsCancellationRequested) + while (work.Count != 0) { - string tryContentPath = work.Dequeue(); - - using DiagnosticScope innerScope = _clientDiagnostics.CreateScope( - string.Format( - CultureInfo.CurrentCulture, - ServiceStrings.FetchingModelContent, - tryContentPath)); + cancellationToken.ThrowIfCancellationRequested(); - innerScope.Start(); + string tryContentPath = work.Dequeue(); + ResolverEventSource.Instance.FetchingModelContent(tryContentPath); try { string content = EvaluatePath(tryContentPath, cancellationToken); - return new FetchResult() + return new FetchResult { Definition = content, Path = tryContentPath }; } - catch (Exception ex) + catch (Exception) { - innerScope.Failed(ex); remoteFetchError = string.Format(CultureInfo.CurrentCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); } } @@ -83,17 +77,12 @@ public async Task FetchAsync(string dtmi, Uri repositoryUri, Cancel string remoteFetchError = string.Empty; - while (work.Count != 0 && !cancellationToken.IsCancellationRequested) + while (work.Count != 0) { - string tryContentPath = work.Dequeue(); + cancellationToken.ThrowIfCancellationRequested(); - using DiagnosticScope innerScope = _clientDiagnostics.CreateScope( - string.Format( - CultureInfo.CurrentCulture, - ServiceStrings.FetchingModelContent, - tryContentPath)); - - innerScope.Start(); + string tryContentPath = work.Dequeue(); + ResolverEventSource.Instance.FetchingModelContent(tryContentPath); try { @@ -104,9 +93,8 @@ public async Task FetchAsync(string dtmi, Uri repositoryUri, Cancel Path = tryContentPath }; } - catch (Exception ex) + catch (Exception) { - innerScope.Failed(ex); remoteFetchError = string.Format(CultureInfo.CurrentCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); } } @@ -151,13 +139,14 @@ private string EvaluatePath(string path, CancellationToken cancellationToken = d _pipeline.Send(message, cancellationToken); - if (message.Response.Status >= 200 && message.Response.Status <= 299) - { - return GetContent(message.Response.ContentStream); - } - else + switch (message.Response.Status) { - throw _clientDiagnostics.CreateRequestFailedException(message.Response); + case 200: + { + return GetContent(message.Response.ContentStream); + } + default: + throw _clientDiagnostics.CreateRequestFailedException(message.Response); } } catch (Exception ex) @@ -178,13 +167,14 @@ private async Task EvaluatePathAsync(string path, CancellationToken canc await _pipeline.SendAsync(message, cancellationToken).ConfigureAwait(false); - if (message.Response.Status >= 200 && message.Response.Status <= 299) - { - return await GetContentAsync(message.Response.ContentStream, cancellationToken).ConfigureAwait(false); - } - else + switch (message.Response.Status) { - throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + case 200: + { + return await GetContentAsync(message.Response.ContentStream, cancellationToken).ConfigureAwait(false); + } + default: + throw _clientDiagnostics.CreateRequestFailedException(message.Response); } } catch (Exception ex) @@ -208,20 +198,17 @@ private HttpMessage CreateGetRequest(string path) private static string GetContent(Stream content) { - using (JsonDocument json = JsonDocument.Parse(content)) - { - JsonElement root = json.RootElement; - return root.GetRawText(); - } + using JsonDocument json = JsonDocument.Parse(content); + JsonElement root = json.RootElement; + return root.GetRawText(); } private static async Task GetContentAsync(Stream content, CancellationToken cancellationToken) { - using (JsonDocument json = await JsonDocument.ParseAsync(content, default, cancellationToken).ConfigureAwait(false)) - { - JsonElement root = json.RootElement; - return root.GetRawText(); - } + using JsonDocument json = await JsonDocument.ParseAsync(content, default, cancellationToken).ConfigureAwait(false); + + JsonElement root = json.RootElement; + return root.GetRawText(); } private static HttpPipeline CreatePipeline(ResolverClientOptions options) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs index 529ac3f966f06..7782dcaa76111 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs @@ -26,10 +26,10 @@ public RepositoryHandler(Uri repositoryUri, ClientDiagnostics clientdiagnostics, RepositoryUri = repositoryUri; _clientDiagnostics = clientdiagnostics; _modelFetcher = repositoryUri.Scheme == "file" - ? _modelFetcher = new LocalModelFetcher(ClientOptions) + ? _modelFetcher = new LocalModelFetcher(_clientDiagnostics, ClientOptions) : _modelFetcher = new RemoteModelFetcher(_clientDiagnostics, ClientOptions); _clientId = Guid.NewGuid(); - ResolverEventSource.Shared.InitFetcher(_clientId, repositoryUri.Scheme); + ResolverEventSource.Instance.InitFetcher(_clientId, repositoryUri.Scheme); } public async Task> ProcessAsync(string dtmi, CancellationToken cancellationToken) @@ -46,7 +46,7 @@ public async Task> ProcessAsync(IEnumerable { if (!DtmiConventions.IsDtmi(dtmi)) { - ResolverEventSource.Shared.InvalidDtmiInput(dtmi); + ResolverEventSource.Instance.InvalidDtmiInput(dtmi); string invalidArgMsg = string.Format(CultureInfo.CurrentCulture, ServiceStrings.InvalidDtmiFormat, dtmi); throw new ResolverException(dtmi, invalidArgMsg, new ArgumentException(invalidArgMsg)); } @@ -59,10 +59,10 @@ public async Task> ProcessAsync(IEnumerable string targetDtmi = toProcessModels.Dequeue(); if (processedModels.ContainsKey(targetDtmi)) { - ResolverEventSource.Shared.SkippingPreprocessedDtmi(targetDtmi); + ResolverEventSource.Instance.SkippingPreprocessedDtmi(targetDtmi); continue; } - ResolverEventSource.Shared.ProcessingDtmi(targetDtmi); + ResolverEventSource.Instance.ProcessingDtmi(targetDtmi); FetchResult result = await FetchAsync(targetDtmi, cancellationToken).ConfigureAwait(false); if (result.FromExpanded) @@ -88,7 +88,7 @@ public async Task> ProcessAsync(IEnumerable if (dependencies.Count > 0) { - ResolverEventSource.Shared.DiscoveredDependencies(string.Join("\", \"", dependencies)); + ResolverEventSource.Instance.DiscoveredDependencies(string.Join("\", \"", dependencies)); } foreach (string dep in dependencies) @@ -100,7 +100,7 @@ public async Task> ProcessAsync(IEnumerable string parsedDtmi = metadata.Id; if (!parsedDtmi.Equals(targetDtmi, StringComparison.Ordinal)) { - ResolverEventSource.Shared.IncorrectDtmiCasing(targetDtmi, parsedDtmi); + ResolverEventSource.Instance.IncorrectDtmiCasing(targetDtmi, parsedDtmi); string formatErrorMsg = string.Format(CultureInfo.CurrentCulture, ServiceStrings.IncorrectDtmiCasing, targetDtmi, parsedDtmi); throw new ResolverException(targetDtmi, formatErrorMsg, new FormatException(formatErrorMsg)); } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs index 56bfbf5789e25..242e13038bf9a 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs @@ -22,7 +22,7 @@ internal sealed class ResolverEventSource : EventSource private const int ErrorFetchingModelContentEventId = 4004; private const int IncorrectDtmiCasingEventId = 4006; - public static ResolverEventSource Shared { get; } = new ResolverEventSource(); + public static ResolverEventSource Instance { get; } = new ResolverEventSource(); private ResolverEventSource() : base(EventSourceName, From 901cf6d4c2b99a6bc4a9d9d8112d042741e818a0 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 13:45:40 -0800 Subject: [PATCH 08/16] Throw if cancellation is requested. --- .../src/DependencyResolutionOption.cs | 2 ++ .../src/Fetchers/LocalModelFetcher.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs index c1a52d404f247..5d570a5378de0 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs @@ -12,10 +12,12 @@ public enum DependencyResolutionOption /// Do not process external dependencies. /// Disabled, + /// /// Enable external dependencies. /// Enabled, + /// /// Try to get external dependencies using .expanded.json. /// diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs index e9e466ac9bf9c..f8030984cf5d0 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs @@ -47,6 +47,8 @@ public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cance string fnfError = string.Empty; while (work.Count != 0 && !cancellationToken.IsCancellationRequested) { + cancellationToken.ThrowIfCancellationRequested(); + string tryContentPath = work.Dequeue(); ResolverEventSource.Instance.FetchingModelContent(tryContentPath); From c753287e9ae09e17dd92b82734f4f16c91b18b77 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 13:52:04 -0800 Subject: [PATCH 09/16] Update LocalModelFetcher.cs --- .../src/Fetchers/LocalModelFetcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs index f8030984cf5d0..496062d3660ad 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs @@ -45,7 +45,7 @@ public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cance work.Enqueue(GetPath(dtmi, repositoryUri, false)); string fnfError = string.Empty; - while (work.Count != 0 && !cancellationToken.IsCancellationRequested) + while (work.Count != 0) { cancellationToken.ThrowIfCancellationRequested(); From b4efb9afd52ec3856c8bfdce0dcd00e378a13e5c Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 13:56:50 -0800 Subject: [PATCH 10/16] Update ResolverEventSource.cs --- .../Azure.Iot.ModelsRepository/src/ResolverEventSource.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs index 242e13038bf9a..a9b84c3ae67e6 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverEventSource.cs @@ -13,6 +13,10 @@ internal sealed class ResolverEventSource : EventSource private const string EventSourceName = ModelRepositoryConstants.ModelRepositoryEventSourceName; // Event ids defined as constants to makes it easy to keep track of them + // Consider EventSource name, Guid, Event Id and parameters as public API and follow the appropriate versioning rules. + // More information on EventSource and Azure guidelines: + // https://azure.github.io/azure-sdk/dotnet_implementation.html#eventsource + private const int InitFetcherEventId = 1000; private const int ProcessingDtmiEventId = 2000; private const int FetchingModelContentEventId = 2001; From 30f1817ab61086f46ad2856f6e695dfe0c55a586 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 16:01:37 -0800 Subject: [PATCH 11/16] Add Async APIs to the ResolverClient --- .../src/Fetchers/LocalModelFetcher.cs | 2 +- .../src/ModelQuery.cs | 30 +++-- .../src/RepositoryHandler.cs | 113 ++++++++++++++++-- .../src/ResolverClient.cs | 38 +++++- .../tests/ModelQueryTests.cs | 4 +- 5 files changed, 159 insertions(+), 28 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs index 496062d3660ad..404df3984e317 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs @@ -65,7 +65,7 @@ public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cance fnfError = string.Format(CultureInfo.CurrentCulture, ServiceStrings.ErrorFetchingModelContent, tryContentPath); } - throw new RequestFailedException(fnfError, new FileNotFoundException(fnfError)); + throw new FileNotFoundException(fnfError); } catch (Exception ex) { diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs index ddbea223d2cb5..05c9389880948 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs @@ -138,7 +138,7 @@ public IList GetComponentSchemas() return componentSchemas; } - public async Task> ListToDictAsync() + public Dictionary ListToDict() { Dictionary result = new Dictionary(); @@ -152,18 +152,14 @@ public async Task> ListToDictAsync() { if (element.ValueKind == JsonValueKind.Object) { - using (MemoryStream stream = new MemoryStream()) - { - await JsonSerializer.SerializeAsync(stream, element).ConfigureAwait(false); - stream.Position = 0; + using MemoryStream stream = WriteJsonElementToStream(element); - using (StreamReader streamReader = new StreamReader(stream)) - { - string serialized = await streamReader.ReadToEndAsync().ConfigureAwait(false); + using (StreamReader streamReader = new StreamReader(stream)) + { + string serialized = streamReader.ReadToEnd(); - string id = new ModelQuery(serialized).GetId(); - result.Add(id, serialized); - } + string id = new ModelQuery(serialized).GetId(); + result.Add(id, serialized); } } } @@ -172,5 +168,17 @@ public async Task> ListToDictAsync() return result; } + + private static MemoryStream WriteJsonElementToStream(JsonElement item) + { + var memoryStream = new MemoryStream(); + using var writer = new Utf8JsonWriter(memoryStream); + + item.WriteTo(writer); + writer.Flush(); + memoryStream.Seek(0, SeekOrigin.Begin); + + return memoryStream; + } } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs index 7782dcaa76111..40047a593776e 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs @@ -37,37 +37,100 @@ public async Task> ProcessAsync(string dtmi, Cancell return await ProcessAsync(new List() { dtmi }, cancellationToken).ConfigureAwait(false); } - public async Task> ProcessAsync(IEnumerable dtmis, CancellationToken cancellationToken) + public IDictionary Process(string dtmi, CancellationToken cancellationToken) + { + return Process(new List() { dtmi }, cancellationToken); + } + + public IDictionary Process(IEnumerable dtmis, CancellationToken cancellationToken) { Dictionary processedModels = new Dictionary(); - Queue toProcessModels = new Queue(); + Queue toProcessModels = PrepareWork(dtmis); - foreach (string dtmi in dtmis) + while (toProcessModels.Count != 0) { - if (!DtmiConventions.IsDtmi(dtmi)) + cancellationToken.ThrowIfCancellationRequested(); + + string targetDtmi = toProcessModels.Dequeue(); + if (processedModels.ContainsKey(targetDtmi)) { - ResolverEventSource.Instance.InvalidDtmiInput(dtmi); - string invalidArgMsg = string.Format(CultureInfo.CurrentCulture, ServiceStrings.InvalidDtmiFormat, dtmi); - throw new ResolverException(dtmi, invalidArgMsg, new ArgumentException(invalidArgMsg)); + ResolverEventSource.Instance.SkippingPreprocessedDtmi(targetDtmi); + continue; } - toProcessModels.Enqueue(dtmi); + ResolverEventSource.Instance.ProcessingDtmi(targetDtmi); + + FetchResult result = Fetch(targetDtmi, cancellationToken); + + if (result.FromExpanded) + { + Dictionary expanded = new ModelQuery(result.Definition).ListToDict(); + + foreach (KeyValuePair kvp in expanded) + { + if (!processedModels.ContainsKey(kvp.Key)) + { + processedModels.Add(kvp.Key, kvp.Value); + } + } + + continue; + } + + ModelMetadata metadata = new ModelQuery(result.Definition).GetMetadata(); + + if (ClientOptions.DependencyResolution >= DependencyResolutionOption.Enabled) + { + IList dependencies = metadata.Dependencies; + + if (dependencies.Count > 0) + { + ResolverEventSource.Instance.DiscoveredDependencies(string.Join("\", \"", dependencies)); + } + + foreach (string dep in dependencies) + { + toProcessModels.Enqueue(dep); + } + } + + string parsedDtmi = metadata.Id; + if (!parsedDtmi.Equals(targetDtmi, StringComparison.Ordinal)) + { + ResolverEventSource.Instance.IncorrectDtmiCasing(targetDtmi, parsedDtmi); + string formatErrorMsg = string.Format(CultureInfo.CurrentCulture, ServiceStrings.IncorrectDtmiCasing, targetDtmi, parsedDtmi); + throw new ResolverException(targetDtmi, formatErrorMsg, new FormatException(formatErrorMsg)); + } + + processedModels.Add(targetDtmi, result.Definition); } - while (toProcessModels.Count != 0 && !cancellationToken.IsCancellationRequested) + return processedModels; + } + + public async Task> ProcessAsync(IEnumerable dtmis, CancellationToken cancellationToken) + { + Dictionary processedModels = new Dictionary(); + Queue toProcessModels = PrepareWork(dtmis); + + while (toProcessModels.Count != 0) { + cancellationToken.ThrowIfCancellationRequested(); + string targetDtmi = toProcessModels.Dequeue(); if (processedModels.ContainsKey(targetDtmi)) { ResolverEventSource.Instance.SkippingPreprocessedDtmi(targetDtmi); continue; } + ResolverEventSource.Instance.ProcessingDtmi(targetDtmi); FetchResult result = await FetchAsync(targetDtmi, cancellationToken).ConfigureAwait(false); + if (result.FromExpanded) { - Dictionary expanded = await new ModelQuery(result.Definition).ListToDictAsync().ConfigureAwait(false); + Dictionary expanded = new ModelQuery(result.Definition).ListToDict(); foreach (KeyValuePair kvp in expanded) { @@ -122,5 +185,35 @@ private async Task FetchAsync(string dtmi, CancellationToken cancel throw new ResolverException(dtmi, ex.Message, ex); } } + + private FetchResult Fetch(string dtmi, CancellationToken cancellationToken) + { + try + { + return _modelFetcher.Fetch(dtmi, RepositoryUri, cancellationToken); + } + catch (Exception ex) + { + throw new ResolverException(dtmi, ex.Message, ex); + } + } + + private static Queue PrepareWork(IEnumerable dtmis) + { + Queue toProcessModels = new Queue(); + foreach (string dtmi in dtmis) + { + if (!DtmiConventions.IsDtmi(dtmi)) + { + ResolverEventSource.Instance.InvalidDtmiInput(dtmi); + string invalidArgMsg = string.Format(CultureInfo.CurrentCulture, ServiceStrings.InvalidDtmiFormat, dtmi); + throw new ResolverException(dtmi, invalidArgMsg, new ArgumentException(invalidArgMsg)); + } + + toProcessModels.Enqueue(dtmi); + } + + return toProcessModels; + } } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs index 5fbc02ff2b528..0f92de67d605e 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs @@ -97,15 +97,30 @@ public ResolverClient(Uri repositoryUri, ResolverClientOptions options) /// The cancellationToken. [SuppressMessage( "Usage", - "AZC0004:DO provide both asynchronous and synchronous variants for all service methods.", + "AZC0015:Unexpected client method return type.", Justification = "")] + public virtual async Task> ResolveAsync(string dtmi, CancellationToken cancellationToken = default) + { + return await _repositoryHandler.ProcessAsync(dtmi, cancellationToken).ConfigureAwait(false); + } + + /// + /// Resolves a model definition identified by and optionally its dependencies. + /// + /// + /// An IDictionary containing the model definition(s) where the key is the dtmi + /// and the value is the raw model definition string. + /// + /// Thrown when a resolution failure occurs. + /// A well-formed DTDL model Id. For example 'dtmi:com:example:Thermostat;1'. + /// The cancellationToken. [SuppressMessage( "Usage", "AZC0015:Unexpected client method return type.", Justification = "")] - public virtual async Task> ResolveAsync(string dtmi, CancellationToken cancellationToken = default) + public virtual IDictionary Resolve(string dtmi, CancellationToken cancellationToken = default) { - return await _repositoryHandler.ProcessAsync(dtmi, cancellationToken).ConfigureAwait(false); + return _repositoryHandler.Process(dtmi, cancellationToken); } /// @@ -118,13 +133,28 @@ public virtual async Task> ResolveAsync(string dtmi, /// Thrown when a resolution failure occurs. /// A collection of well-formed DTDL model Ids. /// The cancellationToken. - [SuppressMessage("Usage", "AZC0004:DO provide both asynchronous and synchronous variants for all service methods.", Justification = "")] [SuppressMessage("Usage", "AZC0015:Unexpected client method return type.", Justification = "")] public virtual async Task> ResolveAsync(IEnumerable dtmis, CancellationToken cancellationToken = default) { return await _repositoryHandler.ProcessAsync(dtmis, cancellationToken).ConfigureAwait(false); } + /// + /// Resolves a collection of model definitions identified by and optionally their dependencies. + /// + /// + /// An IDictionary containing the model definition(s) where the key is the dtmi + /// and the value is the raw model definition string. + /// + /// Thrown when a resolution failure occurs. + /// A collection of well-formed DTDL model Ids. + /// The cancellationToken. + [SuppressMessage("Usage", "AZC0015:Unexpected client method return type.", Justification = "")] + public virtual IDictionary Resolve(IEnumerable dtmis, CancellationToken cancellationToken = default) + { + return _repositoryHandler.Process(dtmis, cancellationToken); + } + /// /// Evaluates whether an input is valid. /// diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelQueryTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelQueryTests.cs index 674ee96b31c6b..1f828351a1740 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelQueryTests.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelQueryTests.cs @@ -163,13 +163,13 @@ public void GetModelDependencies(string id, string extends, string contents, str } [Test] - public async Task ListToDictAsync() + public void ListToDict() { string testRepoPath = TestHelpers.TestLocalModelRepository; string expandedContent = File.ReadAllText( $"{testRepoPath}/dtmi/com/example/temperaturecontroller-1.expanded.json", Encoding.UTF8); ModelQuery query = new ModelQuery(expandedContent); - Dictionary transformResult = await query.ListToDictAsync(); + Dictionary transformResult = query.ListToDict(); // Assert KPI's for TemperatureController;1. // Ensure transform of expanded content to dictionary is what we'd expect. From 63204bb906cce8c95154c3abb0b4a1cae6259757 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 16:04:55 -0800 Subject: [PATCH 12/16] Update ModelQuery.cs --- .../src/ModelQuery.cs | 129 ++++++++---------- 1 file changed, 60 insertions(+), 69 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs index 05c9389880948..3c1dd6f722658 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Text.Json; -using System.Threading.Tasks; namespace Azure.Iot.ModelsRepository { @@ -29,16 +28,14 @@ public ModelMetadata GetMetadata() public string GetId() { - using (JsonDocument document = JsonDocument.Parse(_content, _parseOptions)) - { - JsonElement _root = document.RootElement; + using JsonDocument document = JsonDocument.Parse(_content, _parseOptions); + JsonElement _root = document.RootElement; - if (_root.ValueKind == JsonValueKind.Object && _root.TryGetProperty("@id", out JsonElement id)) + if (_root.ValueKind == JsonValueKind.Object && _root.TryGetProperty("@id", out JsonElement id)) + { + if (id.ValueKind == JsonValueKind.String) { - if (id.ValueKind == JsonValueKind.String) - { - return id.GetString(); - } + return id.GetString(); } } @@ -49,33 +46,31 @@ public IList GetExtends() { List dependencies = new List(); - using (JsonDocument document = JsonDocument.Parse(_content, _parseOptions)) - { - JsonElement _root = document.RootElement; + using JsonDocument document = JsonDocument.Parse(_content, _parseOptions); + JsonElement _root = document.RootElement; - if (_root.ValueKind == JsonValueKind.Object && _root.TryGetProperty("extends", out JsonElement extends)) + if (_root.ValueKind == JsonValueKind.Object && _root.TryGetProperty("extends", out JsonElement extends)) + { + if (extends.ValueKind == JsonValueKind.Array) { - 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()); + } + 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); } } - else if (extends.ValueKind == JsonValueKind.String) - { - dependencies.Add(extends.GetString()); - } + } + else if (extends.ValueKind == JsonValueKind.String) + { + dependencies.Add(extends.GetString()); } } @@ -87,44 +82,42 @@ public IList GetComponentSchemas() { List componentSchemas = new List(); - using (JsonDocument document = JsonDocument.Parse(_content, _parseOptions)) - { - JsonElement _root = document.RootElement; + using JsonDocument document = JsonDocument.Parse(_content, _parseOptions); + JsonElement _root = document.RootElement; - if (_root.ValueKind == JsonValueKind.Object && _root.TryGetProperty("contents", out JsonElement contents)) + if (_root.ValueKind == JsonValueKind.Object && _root.TryGetProperty("contents", out JsonElement contents)) + { + if (contents.ValueKind == JsonValueKind.Array) { - if (contents.ValueKind == JsonValueKind.Array) + foreach (JsonElement element in contents.EnumerateArray()) { - foreach (JsonElement element in contents.EnumerateArray()) + if (element.TryGetProperty("@type", out JsonElement type)) { - if (element.TryGetProperty("@type", out JsonElement type)) + if (type.ValueKind == JsonValueKind.String && type.GetString() == "Component") { - if (type.ValueKind == JsonValueKind.String && type.GetString() == "Component") + if (element.TryGetProperty("schema", out JsonElement schema)) { - if (element.TryGetProperty("schema", out JsonElement schema)) + if (schema.ValueKind == JsonValueKind.String) { - if (schema.ValueKind == JsonValueKind.String) - { - componentSchemas.Add(schema.GetString()); - } - else if (schema.ValueKind == JsonValueKind.Array) + componentSchemas.Add(schema.GetString()); + } + else if (schema.ValueKind == JsonValueKind.Array) + { + foreach (JsonElement schemaElement in schema.EnumerateArray()) { - foreach (JsonElement schemaElement in schema.EnumerateArray()) + if (schemaElement.ValueKind == JsonValueKind.String) { - if (schemaElement.ValueKind == JsonValueKind.String) - { - componentSchemas.Add(schemaElement.GetString()); - } + componentSchemas.Add(schemaElement.GetString()); } } - else if (schema.ValueKind == JsonValueKind.Object) + } + else if (schema.ValueKind == JsonValueKind.Object) + { + if (schema.TryGetProperty("extends", out JsonElement schemaObjExtends)) { - if (schema.TryGetProperty("extends", out JsonElement schemaObjExtends)) + if (schemaObjExtends.ValueKind == JsonValueKind.String) { - if (schemaObjExtends.ValueKind == JsonValueKind.String) - { - componentSchemas.Add(schemaObjExtends.GetString()); - } + componentSchemas.Add(schemaObjExtends.GetString()); } } } @@ -142,25 +135,23 @@ public Dictionary ListToDict() { Dictionary result = new Dictionary(); - using (JsonDocument document = JsonDocument.Parse(_content, _parseOptions)) - { - JsonElement _root = document.RootElement; + using JsonDocument document = JsonDocument.Parse(_content, _parseOptions); + JsonElement _root = document.RootElement; - if (_root.ValueKind == JsonValueKind.Array) + if (_root.ValueKind == JsonValueKind.Array) + { + foreach (JsonElement element in _root.EnumerateArray()) { - foreach (JsonElement element in _root.EnumerateArray()) + if (element.ValueKind == JsonValueKind.Object) { - if (element.ValueKind == JsonValueKind.Object) - { - using MemoryStream stream = WriteJsonElementToStream(element); + using MemoryStream stream = WriteJsonElementToStream(element); - using (StreamReader streamReader = new StreamReader(stream)) - { - string serialized = streamReader.ReadToEnd(); + using (StreamReader streamReader = new StreamReader(stream)) + { + string serialized = streamReader.ReadToEnd(); - string id = new ModelQuery(serialized).GetId(); - result.Add(id, serialized); - } + string id = new ModelQuery(serialized).GetId(); + result.Add(id, serialized); } } } From 9d0385966bb9bc7c8c01092c4a77cc50048a1c4c Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 16:41:06 -0800 Subject: [PATCH 13/16] Make DefaultModelRepository public. --- .../src/ModelRepositoryConstants.cs | 2 ++ .../Azure.Iot.ModelsRepository/src/ResolverClient.cs | 10 +++++++--- .../Azure.Iot.ModelsRepository/tests/ClientTests.cs | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs index c4a160fa3d71d..5351b4b1407d2 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs @@ -8,6 +8,8 @@ internal static class ModelRepositoryConstants // Set EventSource name to package name replacing '.' with '-' public const string ModelRepositoryEventSourceName = "Azure-Iot-ModelsRepository"; + public const string DefaultModelRepository = "https://devicemodels.azure.com"; + // File Extensions public const string JsonFileExtension = ".json"; public const string ExpandedJsonFileExtension = ".expanded.json"; diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs index 0f92de67d605e..f870619902533 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs @@ -17,7 +17,6 @@ namespace Azure.Iot.ModelsRepository /// public class ResolverClient { - internal const string DefaultRepository = "https://devicemodels.azure.com"; private readonly RepositoryHandler _repositoryHandler; private readonly ClientDiagnostics _clientDiagnostics; @@ -25,7 +24,7 @@ public class ResolverClient /// Initializes the ResolverClient with default client options while pointing to /// the Azure IoT Plug and Play Model repository https://devicemodels.azure.com for resolution. /// - public ResolverClient() : this(new Uri(DefaultRepository), new ResolverClientOptions()) { } + public ResolverClient() : this(new Uri(DefaultModelRepository), new ResolverClientOptions()) { } /// /// Initializes the ResolverClient with default client options while pointing to @@ -43,7 +42,7 @@ public class ResolverClient /// /// ResolverClientOptions to configure resolution and client behavior. /// - public ResolverClient(ResolverClientOptions options) : this(new Uri(DefaultRepository), options) { } + public ResolverClient(ResolverClientOptions options) : this(new Uri(DefaultModelRepository), options) { } /// /// Initializes the ResolverClient with default client options while pointing to @@ -169,5 +168,10 @@ public virtual IDictionary Resolve(IEnumerable dtmis, Ca /// Gets the ResolverClientOptions associated with the ResolverClient instance. /// public ResolverClientOptions ClientOptions => _repositoryHandler.ClientOptions; + + /// + /// Azure Device Models Repository used by default. + /// + public static string DefaultModelRepository => ModelRepositoryConstants.DefaultModelRepository; } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs index aff871d3c72c4..38c482c77110b 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs @@ -18,9 +18,9 @@ public void CtorOverloads() ResolverClientOptions options = new ResolverClientOptions(); - Assert.AreEqual(new Uri(ResolverClient.DefaultRepository), new ResolverClient().RepositoryUri); - Assert.AreEqual($"{ResolverClient.DefaultRepository}/", new ResolverClient().RepositoryUri.AbsoluteUri); - Assert.AreEqual(new Uri(ResolverClient.DefaultRepository), new ResolverClient(options).RepositoryUri); + Assert.AreEqual(new Uri(ResolverClient.DefaultModelRepository), new ResolverClient().RepositoryUri); + Assert.AreEqual($"{ResolverClient.DefaultModelRepository}/", new ResolverClient().RepositoryUri.AbsoluteUri); + Assert.AreEqual(new Uri(ResolverClient.DefaultModelRepository), new ResolverClient(options).RepositoryUri); Assert.AreEqual(remoteUri, new ResolverClient(remoteUri).RepositoryUri); Assert.AreEqual(remoteUri, new ResolverClient(remoteUri, options).RepositoryUri); From b1eaeaeea15289037d1e7a0fbe404038bba656d5 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 16:51:50 -0800 Subject: [PATCH 14/16] Use async bool pattern. --- .../src/RepositoryHandler.cs | 82 ++++--------------- 1 file changed, 17 insertions(+), 65 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs index 40047a593776e..5d2e24026f396 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs @@ -34,15 +34,25 @@ public RepositoryHandler(Uri repositoryUri, ClientDiagnostics clientdiagnostics, public async Task> ProcessAsync(string dtmi, CancellationToken cancellationToken) { - return await ProcessAsync(new List() { dtmi }, cancellationToken).ConfigureAwait(false); + return await ProcessAsync(new List() { dtmi }, true, cancellationToken).ConfigureAwait(false); } public IDictionary Process(string dtmi, CancellationToken cancellationToken) { - return Process(new List() { dtmi }, cancellationToken); + return ProcessAsync(new List() { dtmi }, false, cancellationToken).EnsureCompleted(); + } + + public Task> ProcessAsync(IEnumerable dtmis, CancellationToken cancellationToken) + { + return ProcessAsync(dtmis, true, cancellationToken); } public IDictionary Process(IEnumerable dtmis, CancellationToken cancellationToken) + { + return ProcessAsync(dtmis, false, cancellationToken).EnsureCompleted(); + } + + private async Task> ProcessAsync(IEnumerable dtmis, bool async, CancellationToken cancellationToken) { Dictionary processedModels = new Dictionary(); Queue toProcessModels = PrepareWork(dtmis); @@ -59,75 +69,17 @@ public IDictionary Process(IEnumerable dtmis, Cancellati } ResolverEventSource.Instance.ProcessingDtmi(targetDtmi); + FetchResult result; - FetchResult result = Fetch(targetDtmi, cancellationToken); - - if (result.FromExpanded) - { - Dictionary expanded = new ModelQuery(result.Definition).ListToDict(); - - foreach (KeyValuePair kvp in expanded) - { - if (!processedModels.ContainsKey(kvp.Key)) - { - processedModels.Add(kvp.Key, kvp.Value); - } - } - - continue; - } - - ModelMetadata metadata = new ModelQuery(result.Definition).GetMetadata(); - - if (ClientOptions.DependencyResolution >= DependencyResolutionOption.Enabled) - { - IList dependencies = metadata.Dependencies; - - if (dependencies.Count > 0) - { - ResolverEventSource.Instance.DiscoveredDependencies(string.Join("\", \"", dependencies)); - } - - foreach (string dep in dependencies) - { - toProcessModels.Enqueue(dep); - } - } - - string parsedDtmi = metadata.Id; - if (!parsedDtmi.Equals(targetDtmi, StringComparison.Ordinal)) + if (async) { - ResolverEventSource.Instance.IncorrectDtmiCasing(targetDtmi, parsedDtmi); - string formatErrorMsg = string.Format(CultureInfo.CurrentCulture, ServiceStrings.IncorrectDtmiCasing, targetDtmi, parsedDtmi); - throw new ResolverException(targetDtmi, formatErrorMsg, new FormatException(formatErrorMsg)); + result = await FetchAsync(targetDtmi, cancellationToken).ConfigureAwait(false); } - - processedModels.Add(targetDtmi, result.Definition); - } - - return processedModels; - } - - public async Task> ProcessAsync(IEnumerable dtmis, CancellationToken cancellationToken) - { - Dictionary processedModels = new Dictionary(); - Queue toProcessModels = PrepareWork(dtmis); - - while (toProcessModels.Count != 0) - { - cancellationToken.ThrowIfCancellationRequested(); - - string targetDtmi = toProcessModels.Dequeue(); - if (processedModels.ContainsKey(targetDtmi)) + else { - ResolverEventSource.Instance.SkippingPreprocessedDtmi(targetDtmi); - continue; + result = Fetch(targetDtmi, cancellationToken); } - ResolverEventSource.Instance.ProcessingDtmi(targetDtmi); - - FetchResult result = await FetchAsync(targetDtmi, cancellationToken).ConfigureAwait(false); - if (result.FromExpanded) { Dictionary expanded = new ModelQuery(result.Definition).ListToDict(); From 9d8c3d2d8822e632d322fc13c13a384f6172273f Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 17:02:04 -0800 Subject: [PATCH 15/16] Make ModelSRepository plural --- .../src/ModelRepositoryConstants.cs | 2 +- .../Azure.Iot.ModelsRepository/src/ResolverClient.cs | 6 +++--- .../Azure.Iot.ModelsRepository/tests/ClientTests.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs index 5351b4b1407d2..def1f4040019f 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs @@ -8,7 +8,7 @@ internal static class ModelRepositoryConstants // Set EventSource name to package name replacing '.' with '-' public const string ModelRepositoryEventSourceName = "Azure-Iot-ModelsRepository"; - public const string DefaultModelRepository = "https://devicemodels.azure.com"; + public const string DefaultModelsRepository = "https://devicemodels.azure.com"; // File Extensions public const string JsonFileExtension = ".json"; diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs index f870619902533..5ac245960703c 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ResolverClient.cs @@ -24,7 +24,7 @@ public class ResolverClient /// Initializes the ResolverClient with default client options while pointing to /// the Azure IoT Plug and Play Model repository https://devicemodels.azure.com for resolution. /// - public ResolverClient() : this(new Uri(DefaultModelRepository), new ResolverClientOptions()) { } + public ResolverClient() : this(new Uri(DefaultModelsRepository), new ResolverClientOptions()) { } /// /// Initializes the ResolverClient with default client options while pointing to @@ -42,7 +42,7 @@ public class ResolverClient /// /// ResolverClientOptions to configure resolution and client behavior. /// - public ResolverClient(ResolverClientOptions options) : this(new Uri(DefaultModelRepository), options) { } + public ResolverClient(ResolverClientOptions options) : this(new Uri(DefaultModelsRepository), options) { } /// /// Initializes the ResolverClient with default client options while pointing to @@ -172,6 +172,6 @@ public virtual IDictionary Resolve(IEnumerable dtmis, Ca /// /// Azure Device Models Repository used by default. /// - public static string DefaultModelRepository => ModelRepositoryConstants.DefaultModelRepository; + public static string DefaultModelsRepository => ModelRepositoryConstants.DefaultModelsRepository; } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs index 38c482c77110b..e0096a6ee5f71 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs @@ -18,9 +18,9 @@ public void CtorOverloads() ResolverClientOptions options = new ResolverClientOptions(); - Assert.AreEqual(new Uri(ResolverClient.DefaultModelRepository), new ResolverClient().RepositoryUri); - Assert.AreEqual($"{ResolverClient.DefaultModelRepository}/", new ResolverClient().RepositoryUri.AbsoluteUri); - Assert.AreEqual(new Uri(ResolverClient.DefaultModelRepository), new ResolverClient(options).RepositoryUri); + Assert.AreEqual(new Uri(ResolverClient.DefaultModelsRepository), new ResolverClient().RepositoryUri); + Assert.AreEqual($"{ResolverClient.DefaultModelsRepository}/", new ResolverClient().RepositoryUri.AbsoluteUri); + Assert.AreEqual(new Uri(ResolverClient.DefaultModelsRepository), new ResolverClient(options).RepositoryUri); Assert.AreEqual(remoteUri, new ResolverClient(remoteUri).RepositoryUri); Assert.AreEqual(remoteUri, new ResolverClient(remoteUri, options).RepositoryUri); From bb10fd2a555d7543209ff03272e0efa660a4cf65 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Tue, 16 Feb 2021 17:55:17 -0800 Subject: [PATCH 16/16] Address comments --- .../src/ModelQuery.cs | 10 ++++------ .../src/RepositoryHandler.cs | 20 +++++++------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs index 3c1dd6f722658..c1b0c33615d42 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs @@ -146,13 +146,11 @@ public Dictionary ListToDict() { using MemoryStream stream = WriteJsonElementToStream(element); - using (StreamReader streamReader = new StreamReader(stream)) - { - string serialized = streamReader.ReadToEnd(); + using StreamReader streamReader = new StreamReader(stream); + string serialized = streamReader.ReadToEnd(); - string id = new ModelQuery(serialized).GetId(); - result.Add(id, serialized); - } + string id = new ModelQuery(serialized).GetId(); + result.Add(id, serialized); } } } diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs index 5d2e24026f396..c2ae5f54a6e25 100644 --- a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs @@ -34,12 +34,12 @@ public RepositoryHandler(Uri repositoryUri, ClientDiagnostics clientdiagnostics, public async Task> ProcessAsync(string dtmi, CancellationToken cancellationToken) { - return await ProcessAsync(new List() { dtmi }, true, cancellationToken).ConfigureAwait(false); + return await ProcessAsync(new List { dtmi }, true, cancellationToken).ConfigureAwait(false); } public IDictionary Process(string dtmi, CancellationToken cancellationToken) { - return ProcessAsync(new List() { dtmi }, false, cancellationToken).EnsureCompleted(); + return ProcessAsync(new List { dtmi }, false, cancellationToken).EnsureCompleted(); } public Task> ProcessAsync(IEnumerable dtmis, CancellationToken cancellationToken) @@ -54,7 +54,7 @@ public IDictionary Process(IEnumerable dtmis, Cancellati private async Task> ProcessAsync(IEnumerable dtmis, bool async, CancellationToken cancellationToken) { - Dictionary processedModels = new Dictionary(); + var processedModels = new Dictionary(); Queue toProcessModels = PrepareWork(dtmis); while (toProcessModels.Count != 0) @@ -69,16 +69,10 @@ private async Task> ProcessAsync(IEnumerable } ResolverEventSource.Instance.ProcessingDtmi(targetDtmi); - FetchResult result; - if (async) - { - result = await FetchAsync(targetDtmi, cancellationToken).ConfigureAwait(false); - } - else - { - result = Fetch(targetDtmi, cancellationToken); - } + FetchResult result = async + ? await FetchAsync(targetDtmi, cancellationToken).ConfigureAwait(false) + : Fetch(targetDtmi, cancellationToken); if (result.FromExpanded) { @@ -152,7 +146,7 @@ private FetchResult Fetch(string dtmi, CancellationToken cancellationToken) private static Queue PrepareWork(IEnumerable dtmis) { - Queue toProcessModels = new Queue(); + var toProcessModels = new Queue(); foreach (string dtmi in dtmis) { if (!DtmiConventions.IsDtmi(dtmi))