Skip to content

Commit 4254688

Browse files
authored
Rework DtmiConventions in models repository SDK (Azure#19427)
Highlights - Reworked DtmiToQualifiedPath to revolve around Uri. Changed name to GetModelUri. - Model FileFetcher will use LocalPath from the Uri object. - Update of tests & samples.
1 parent f9b5676 commit 4254688

File tree

28 files changed

+165
-179
lines changed

28 files changed

+165
-179
lines changed

sdk/modelsrepository/Azure.Iot.ModelsRepository/api/Azure.Iot.ModelsRepository.netstandard2.0.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace Azure.Iot.ModelsRepository
22
{
33
public static partial class DtmiConventions
44
{
5-
public static string DtmiToQualifiedPath(string dtmi, string basePath, bool fromExpanded = false) { throw null; }
5+
public static System.Uri GetModelUri(string dtmi, System.Uri repositoryUri, bool expanded = false) { throw null; }
66
public static bool IsValidDtmi(string dtmi) { throw null; }
77
}
88
public enum ModelDependencyResolution

sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/DtmiConventionsSamples.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,29 @@ public static void IsValidDtmi()
2222
#endregion Snippet:ModelsRepositorySamplesDtmiConventionsIsValidDtmi
2323
}
2424

25-
public static void GetDtmiToQualifiedPath()
25+
public static void GetModelUri()
2626
{
27-
#region Snippet:ModelsRepositorySamplesDtmiConventionsGetDtmiToQualifiedPath
27+
#region Snippet:ModelsRepositorySamplesDtmiConventionsGetModelUri
2828

2929
// This snippet shows obtaining a fully qualified path to a model file.
3030

3131
// Local repository example
32-
string localRepository = "/path/to/repository";
32+
Uri localRepositoryUri = new Uri("file:///path/to/repository/");
3333
string fullyQualifiedModelPath =
34-
DtmiConventions.DtmiToQualifiedPath("dtmi:com:example:Thermostat;1", localRepository);
34+
DtmiConventions.GetModelUri("dtmi:com:example:Thermostat;1", localRepositoryUri).AbsolutePath;
3535

3636
// Prints '/path/to/repository/dtmi/com/example/thermostat-1.json'
3737
Console.WriteLine(fullyQualifiedModelPath);
3838

3939
// Remote repository example
40-
string remoteRepository = "https://contoso.com/models";
40+
Uri remoteRepositoryUri = new Uri("https://contoso.com/models/");
4141
fullyQualifiedModelPath =
42-
DtmiConventions.DtmiToQualifiedPath("dtmi:com:example:Thermostat;1", remoteRepository);
42+
DtmiConventions.GetModelUri("dtmi:com:example:Thermostat;1", remoteRepositoryUri).AbsoluteUri;
4343

4444
// Prints 'https://contoso.com/models/dtmi/com/example/thermostat-1.json'
4545
Console.WriteLine(fullyQualifiedModelPath);
4646

47-
#endregion Snippet:ModelsRepositorySamplesDtmiConventionsGetDtmiToQualifiedPath
47+
#endregion Snippet:ModelsRepositorySamplesDtmiConventionsGetModelUri
4848
}
4949
}
5050
}

sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static async Task Main(string[] args)
3535

3636
// DtmiConventions utility samples
3737
DtmiConventionsSamples.IsValidDtmi();
38-
DtmiConventionsSamples.GetDtmiToQualifiedPath();
38+
DtmiConventionsSamples.GetModelUri();
3939
}
4040
}
4141
}

sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/readme.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ IReadOnlyDictionary<Dtmi, DTEntityInfo> parseResult = await parser.ParseAsync(mo
134134
Console.WriteLine($"{dtmi} resolved in {models.Count} interfaces with {parseResult.Count} entities.");
135135
```
136136

137-
## DtmiConventions utility
137+
## DtmiConventions utility functions
138138

139-
The IoT Models Repository applies a set of conventions for organizing digital twin models. This package exposes a utility class
140-
called `DtmiConventions` which exposes functions supporting these conventions. These same functions are used throughout the client.
139+
The IoT Models Repository applies a set of conventions for organizing digital twin models. This package exposes a class
140+
called `DtmiConventions` which exposes utility functions supporting these conventions. These same functions are used throughout the client.
141141

142142
```C# Snippet:ModelsRepositorySamplesDtmiConventionsIsValidDtmi
143143
// This snippet shows how to validate a given DTMI string is well-formed.
@@ -149,21 +149,21 @@ DtmiConventions.IsValidDtmi("dtmi:com:example:Thermostat;1");
149149
DtmiConventions.IsValidDtmi("dtmi:com:example:Thermostat");
150150
```
151151

152-
```C# Snippet:ModelsRepositorySamplesDtmiConventionsGetDtmiToQualifiedPath
152+
```C# Snippet:ModelsRepositorySamplesDtmiConventionsGetModelUri
153153
// This snippet shows obtaining a fully qualified path to a model file.
154154
155155
// Local repository example
156-
string localRepository = "/path/to/repository";
156+
Uri localRepositoryUri = new Uri("file:///path/to/repository/");
157157
string fullyQualifiedModelPath =
158-
DtmiConventions.DtmiToQualifiedPath("dtmi:com:example:Thermostat;1", localRepository);
158+
DtmiConventions.GetModelUri("dtmi:com:example:Thermostat;1", localRepositoryUri).AbsolutePath;
159159

160160
// Prints '/path/to/repository/dtmi/com/example/thermostat-1.json'
161161
Console.WriteLine(fullyQualifiedModelPath);
162162

163163
// Remote repository example
164-
string remoteRepository = "https://contoso.com/models";
164+
Uri remoteRepositoryUri = new Uri("https://contoso.com/models/");
165165
fullyQualifiedModelPath =
166-
DtmiConventions.DtmiToQualifiedPath("dtmi:com:example:Thermostat;1", remoteRepository);
166+
DtmiConventions.GetModelUri("dtmi:com:example:Thermostat;1", remoteRepositoryUri).AbsoluteUri;
167167

168168
// Prints 'https://contoso.com/models/dtmi/com/example/thermostat-1.json'
169169
Console.WriteLine(fullyQualifiedModelPath);

sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ public static class DtmiConventions
2828
public static bool IsValidDtmi(string dtmi) => !string.IsNullOrEmpty(dtmi) && s_validDtmiRegex.IsMatch(dtmi);
2929

3030
/// <summary>
31-
/// Produces a fully qualified path to a model file.
31+
/// Get the URI object representing a digital twin model file in a target model repository.
3232
/// </summary>
3333
/// <exception cref="ArgumentException">Thrown when a given DTMI string is malformed.</exception>
3434
/// <param name="dtmi">A well-formed DTMI. For example 'dtmi:com:example:Thermostat;1'.</param>
35-
/// <param name="basePath">The base path in which a transformed DTMI to path will be appended to.</param>
36-
/// <param name="fromExpanded">Indicates whether the produced path should be for the expanded model definition.</param>
37-
public static string DtmiToQualifiedPath(string dtmi, string basePath, bool fromExpanded = false)
35+
/// <param name="repositoryUri">The repository URI in which a transformed DTMI to path will be combined with.</param>
36+
/// <param name="expanded">Indicates whether the produced path should be for the expanded model definition.</param>
37+
public static Uri GetModelUri(string dtmi, Uri repositoryUri, bool expanded = false)
3838
{
3939
string dtmiPath = DtmiToPath(dtmi);
4040

@@ -43,24 +43,20 @@ public static string DtmiToQualifiedPath(string dtmi, string basePath, bool from
4343
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, StandardStrings.InvalidDtmiFormat, dtmi));
4444
}
4545

46-
// Normalize directory seperators
47-
basePath = basePath.Replace("\\", "/");
48-
49-
if (!basePath.EndsWith("/", StringComparison.InvariantCultureIgnoreCase))
46+
if (expanded)
5047
{
51-
basePath += "/";
48+
dtmiPath = dtmiPath.Replace(
49+
ModelsRepositoryConstants.JsonFileExtension,
50+
ModelsRepositoryConstants.ExpandedJsonFileExtension);
5251
}
5352

54-
string fullyQualifiedPath = $"{basePath}{dtmiPath}";
55-
56-
if (fromExpanded)
53+
UriBuilder repositoryUriBuilder = new UriBuilder(repositoryUri);
54+
if (!repositoryUriBuilder.Path.EndsWith("/", StringComparison.InvariantCultureIgnoreCase))
5755
{
58-
fullyQualifiedPath = fullyQualifiedPath.Replace(
59-
ModelsRepositoryConstants.JsonFileExtension,
60-
ModelsRepositoryConstants.ExpandedJsonFileExtension);
56+
repositoryUriBuilder.Path += "/";
6157
}
6258

63-
return fullyQualifiedPath;
59+
return new Uri(repositoryUriBuilder.Uri, dtmiPath);
6460
}
6561

6662
internal static string DtmiToPath(string dtmi) => IsValidDtmi(dtmi) ? $"{dtmi.ToLowerInvariant().Replace(":", "/").Replace(";", "-")}.json" : null;

sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FileModelFetcher.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ public FetchResult Fetch(string dtmi, Uri repositoryUri, ModelDependencyResoluti
4040
var work = new Queue<string>();
4141
if (dependencyResolution == ModelDependencyResolution.TryFromExpanded)
4242
{
43-
work.Enqueue(GetPath(dtmi, repositoryUri, true));
43+
work.Enqueue(DtmiConventions.GetModelUri(dtmi, repositoryUri, true).LocalPath);
4444
}
4545

46-
work.Enqueue(GetPath(dtmi, repositoryUri, false));
46+
work.Enqueue(DtmiConventions.GetModelUri(dtmi, repositoryUri, false).LocalPath);
4747

4848
string fnfError = string.Empty;
4949
while (work.Count != 0)
@@ -76,11 +76,5 @@ public FetchResult Fetch(string dtmi, Uri repositoryUri, ModelDependencyResoluti
7676
throw;
7777
}
7878
}
79-
80-
private static string GetPath(string dtmi, Uri repositoryUri, bool expanded = false)
81-
{
82-
string registryPath = repositoryUri.AbsolutePath;
83-
return DtmiConventions.DtmiToQualifiedPath(dtmi, registryPath, expanded);
84-
}
8579
}
8680
}

sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/HttpModelFetcher.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,20 +149,14 @@ private static Queue<string> PrepareWork(string dtmi, Uri repositoryUri, bool tr
149149

150150
if (tryExpanded)
151151
{
152-
work.Enqueue(GetPath(dtmi, repositoryUri, true));
152+
work.Enqueue(DtmiConventions.GetModelUri(dtmi, repositoryUri, true).AbsoluteUri);
153153
}
154154

155-
work.Enqueue(GetPath(dtmi, repositoryUri, false));
155+
work.Enqueue(DtmiConventions.GetModelUri(dtmi, repositoryUri, false).AbsoluteUri);
156156

157157
return work;
158158
}
159159

160-
private static string GetPath(string dtmi, Uri repositoryUri, bool expanded = false)
161-
{
162-
string absoluteUri = repositoryUri.AbsoluteUri;
163-
return DtmiConventions.DtmiToQualifiedPath(dtmi, absoluteUri, expanded);
164-
}
165-
166160
private string EvaluatePath(string path, CancellationToken cancellationToken = default)
167161
{
168162
using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(HttpModelFetcher)}.{nameof(EvaluatePath)}");

sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryConstants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal static class ModelsRepositoryConstants
1313
// File Extensions
1414
public const string JsonFileExtension = ".json";
1515
public const string ExpandedJsonFileExtension = ".expanded.json";
16-
public const string File = "file";
16+
public const string UriFileSchema = "file";
1717

1818
/// <summary>
1919
/// The ModelRepositoryConstants.ModelProperties class contains DTDL v2 property names and property values

sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public RepositoryHandler(Uri repositoryUri, ClientDiagnostics clientDiagnostics,
2626

2727
_clientOptions = options;
2828
_clientDiagnostics = clientDiagnostics;
29-
_modelFetcher = repositoryUri.Scheme == ModelsRepositoryConstants.File
29+
_modelFetcher = repositoryUri.Scheme == ModelsRepositoryConstants.UriFileSchema
3030
? _modelFetcher = new FileModelFetcher(_clientDiagnostics)
3131
: _modelFetcher = new HttpModelFetcher(_clientDiagnostics, _clientOptions);
3232
_clientId = Guid.NewGuid();

sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/DtmiConventionsTests.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,30 @@ public void DtmiToPath(string dtmi, string expectedPath)
2222

2323
[TestCase("dtmi:com:example:Thermostat;1", "https://localhost/repository/", "https://localhost/repository/dtmi/com/example/thermostat-1.json")]
2424
[TestCase("dtmi:com:example:Thermostat;1", "https://localhost/REPOSITORY", "https://localhost/REPOSITORY/dtmi/com/example/thermostat-1.json")]
25-
[TestCase("dtmi:com:example:Thermostat;1", "/path/to/repository/", "/path/to/repository/dtmi/com/example/thermostat-1.json")]
26-
[TestCase("dtmi:com:example:Thermostat;1", "/path/to/RepoSitory", "/path/to/RepoSitory/dtmi/com/example/thermostat-1.json")]
27-
[TestCase("dtmi:com:example:Thermostat;1", "C:\\path\\to\\repository", "C:/path/to/repository/dtmi/com/example/thermostat-1.json")]
28-
[TestCase("dtmi:com:example:Thermostat:1", "https://localhost/repository", null)]
29-
[TestCase("dtmi:com:example:Thermostat:1", "/path/to/repository/", null)]
30-
[TestCase("dtmi:com:example:Thermostat;1", "", "/dtmi/com/example/thermostat-1.json")]
31-
public void DtmiToQualifiedPath(string dtmi, string repository, string expectedPath)
25+
[TestCase("dtmi:com:example:Thermostat;1", "file:///path/to/repository/", "file:///path/to/repository/dtmi/com/example/thermostat-1.json")]
26+
[TestCase("dtmi:com:example:Thermostat;1", "file://path/to/RepoSitory", "file://path/to/RepoSitory/dtmi/com/example/thermostat-1.json")]
27+
[TestCase("dtmi:com:example:Thermostat;1", "C:\\path\\to\\repository\\", "file:///C:/path/to/repository/dtmi/com/example/thermostat-1.json")]
28+
[TestCase("dtmi:com:example:Thermostat:1", "https://localhost/repository/", null)]
29+
[TestCase("dtmi:com:example:Thermostat:1", "file://path/to/repository/", null)]
30+
[TestCase("dtmi:com:example:Thermostat;1", "\\\\server\\repository", "file://server/repository/dtmi/com/example/thermostat-1.json")]
31+
public void GetModelUri(string dtmi, string repository, string expectedUri)
3232
{
33-
if (string.IsNullOrEmpty(expectedPath))
33+
Uri repositoryUri = new Uri(repository);
34+
if (string.IsNullOrEmpty(expectedUri))
3435
{
35-
Action act = () => DtmiConventions.DtmiToQualifiedPath(dtmi, repository);
36+
Action act = () => DtmiConventions.GetModelUri(dtmi, repositoryUri);
3637
act.Should().Throw<ArgumentException>().WithMessage(string.Format(StandardStrings.InvalidDtmiFormat, dtmi));
3738
return;
3839
}
3940

40-
string modelPath = DtmiConventions.DtmiToQualifiedPath(dtmi, repository);
41-
modelPath.Should().Be(expectedPath);
41+
Uri modelUri = DtmiConventions.GetModelUri(dtmi, repositoryUri);
42+
modelUri.AbsoluteUri.Should().Be(expectedUri);
4243

43-
string expectedExpandedPath = expectedPath.Replace(
44+
string expectedExpandedUri = expectedUri.Replace(
4445
ModelsRepositoryConstants.JsonFileExtension, ModelsRepositoryConstants.ExpandedJsonFileExtension);
45-
string expandedModelPath = DtmiConventions.DtmiToQualifiedPath(dtmi, repository, true);
46-
expandedModelPath.Should().Be(expectedExpandedPath);
46+
47+
Uri expandedModelUri = DtmiConventions.GetModelUri(dtmi, repositoryUri, true);
48+
expandedModelUri.Should().Be(expectedExpandedUri);
4749
}
4850

4951
[TestCase("dtmi:com:example:Thermostat;1", true)]

0 commit comments

Comments
 (0)