diff --git a/src/Tools/Extensions.ApiDescription.Client/src/CSharpIdentifier.cs b/src/Tools/Extensions.ApiDescription.Client/src/CSharpIdentifier.cs
index b1ee4f7c0c..c7d2d13e49 100644
--- a/src/Tools/Extensions.ApiDescription.Client/src/CSharpIdentifier.cs
+++ b/src/Tools/Extensions.ApiDescription.Client/src/CSharpIdentifier.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Globalization;
diff --git a/src/Tools/Extensions.ApiDescription.Client/src/GetOpenApiReferenceMetadata.cs b/src/Tools/Extensions.ApiDescription.Client/src/GetOpenApiReferenceMetadata.cs
index e9fe7b53a3..b7894e0bf0 100644
--- a/src/Tools/Extensions.ApiDescription.Client/src/GetOpenApiReferenceMetadata.cs
+++ b/src/Tools/Extensions.ApiDescription.Client/src/GetOpenApiReferenceMetadata.cs
@@ -63,8 +63,7 @@ namespace Microsoft.Extensions.ApiDescription.Client
"OpenApiReference" :
"OpenApiProjectReference";
- Log.LogError(
- Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", "OpenApiReference", item.ItemSpec));
+ Log.LogError(Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", type, item.ItemSpec));
continue;
}
diff --git a/src/Tools/Extensions.ApiDescription.Client/src/Properties/AssemblyInfo.cs b/src/Tools/Extensions.ApiDescription.Client/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..5d2f75d233
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.Extensions.ApiDescription.Client.Tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Tools/Extensions.ApiDescription.Client/src/build/Microsoft.Extensions.ApiDescription.Client.targets b/src/Tools/Extensions.ApiDescription.Client/src/build/Microsoft.Extensions.ApiDescription.Client.targets
index c1f5eee677..af590565c6 100644
--- a/src/Tools/Extensions.ApiDescription.Client/src/build/Microsoft.Extensions.ApiDescription.Client.targets
+++ b/src/Tools/Extensions.ApiDescription.Client/src/build/Microsoft.Extensions.ApiDescription.Client.targets
@@ -48,7 +48,9 @@
-
+
+ %(_Temporary.OriginalItemSpec)
+
<_Temporary Remove="@(_Temporary)" />
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/CSharpIdentifierTest.cs b/src/Tools/Extensions.ApiDescription.Client/test/CSharpIdentifierTest.cs
new file mode 100644
index 0000000000..d99b3a55d6
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/CSharpIdentifierTest.cs
@@ -0,0 +1,107 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.Extensions.ApiDescription.Client
+{
+ public class CSharpIdentifierTest
+ {
+ [Theory]
+ [InlineData('a')]
+ [InlineData('Q')]
+ [InlineData('\u2164')] // UnicodeCategory.LetterNumber (roman numeral five)
+ [InlineData('_')]
+ [InlineData('9')]
+ [InlineData('\u0303')] // UnicodeCategory.NonSpacingMark (combining tilde)
+ [InlineData('\u09CB')] // UnicodeCategory.SpacingCombiningMark (Bengali vowel sign O)
+ [InlineData('\uFE4F')] // UnicodeCategory.ConnectorPunctuation (wavy low line)
+ [InlineData('\u2062')] // UnicodeCategory.Format (invisible times)
+ public void IsIdentifierPart_ReturnsTrue_WhenItShould(char character)
+ {
+ // Arrange and Act
+ var result = CSharpIdentifier.IsIdentifierPart(character);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Theory]
+ [InlineData('/')]
+ [InlineData('-')]
+ [InlineData('\u20DF')] // UnicodeCategory.EnclosingMark (combining enclosing diamond)
+ [InlineData('\u2005')] // UnicodeCategory.SpaceSeparator (four-per-em space)
+ [InlineData('\u0096')] // UnicodeCategory.Control (start of guarded area)
+ [InlineData('\uFF1C')] // UnicodeCategory.MathSymbol (fullwidth less-than sign)
+ public void IsIdentifierPart_ReturnsFalse_WhenItShould(char character)
+ {
+ // Arrange and Act
+ var result = CSharpIdentifier.IsIdentifierPart(character);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ // Output length is one longer than input in these cases.
+ [Theory]
+ [InlineData("9", "_9")]
+ [InlineData("\u0303", "_\u0303")] // UnicodeCategory.NonSpacingMark (combining tilde)
+ [InlineData("\u09CB", "_\u09CB")] // UnicodeCategory.SpacingCombiningMark (Bengali vowel sign O)
+ [InlineData("\uFE4F", "_\uFE4F")] // UnicodeCategory.ConnectorPunctuation (wavy low line)
+ [InlineData("\u2062", "_\u2062")] // UnicodeCategory.Format (invisible times)
+ public void SanitizeIdentifier_AddsUnderscore_WhenItShould(string input, string expectdOutput)
+ {
+ // Arrange and Act
+ var output = CSharpIdentifier.SanitizeIdentifier(input);
+
+ // Assert
+ Assert.Equal(expectdOutput, output);
+ }
+
+ [Theory]
+ [InlineData("a", "a")]
+ [InlineData("Q", "Q")]
+ [InlineData("\u2164", "\u2164")]
+ [InlineData("_", "_")]
+ public void SanitizeIdentifier_DoesNotAddUnderscore_WhenValidStartCharacter(string input, string expectdOutput)
+ {
+ // Arrange and Act
+ var output = CSharpIdentifier.SanitizeIdentifier(input);
+
+ // Assert
+ Assert.Equal(expectdOutput, output);
+ }
+
+ [Theory]
+ [InlineData("/", "_")]
+ [InlineData("-", "_")]
+ [InlineData("\u20DF", "_")] // UnicodeCategory.EnclosingMark (combining enclosing diamond)
+ [InlineData("\u2005", "_")] // UnicodeCategory.SpaceSeparator (four-per-em space)
+ [InlineData("\u0096", "_")] // UnicodeCategory.Control (start of guarded area)
+ [InlineData("\uFF1C", "_")] // UnicodeCategory.MathSymbol (fullwidth less-than sign)
+ public void SanitizeIdentifier_DoesNotAddUnderscore_WhenInvalidCharacter(string input, string expectdOutput)
+ {
+ // Arrange and Act
+ var output = CSharpIdentifier.SanitizeIdentifier(input);
+
+ // Assert
+ Assert.Equal(expectdOutput, output);
+ }
+
+ [Theory]
+ [InlineData("a/", "a_")]
+ [InlineData("aa-bb", "aa_bb")]
+ [InlineData("aa\u20DF\u20DF", "aa__")] // UnicodeCategory.EnclosingMark (combining enclosing diamond)
+ [InlineData("aa\u2005bb\u2005cc", "aa_bb_cc")] // UnicodeCategory.SpaceSeparator (four-per-em space)
+ [InlineData("aa\u0096\u0096bb", "aa__bb")] // UnicodeCategory.Control (start of guarded area)
+ [InlineData("aa\uFF1C\uFF1C\uFF1Cbb", "aa___bb")] // UnicodeCategory.MathSymbol (fullwidth less-than sign)
+ public void SanitizeIdentifier_ReplacesInvalidCharacters_WhenNotFirst(string input, string expectdOutput)
+ {
+ // Arrange and Act
+ var output = CSharpIdentifier.SanitizeIdentifier(input);
+
+ // Assert
+ Assert.Equal(expectdOutput, output);
+ }
+ }
+}
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/GetCurrentOpenApiReferenceTest.cs b/src/Tools/Extensions.ApiDescription.Client/test/GetCurrentOpenApiReferenceTest.cs
new file mode 100644
index 0000000000..1b6abc9f27
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/GetCurrentOpenApiReferenceTest.cs
@@ -0,0 +1,57 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Microsoft.Extensions.ApiDescription.Client
+{
+ public class GetCurrentOpenApiReferenceTest
+ {
+ [Fact]
+ public void Execute_ReturnsExpectedItem()
+ {
+ // Arrange
+ string input = "Identity=../files/azureMonitor.json|ClassName=azureMonitorClient|" +
+ "CodeGenerator=NSwagCSharp|Namespace=ConsoleClient|Options=|OutputPath=" +
+ "C:\\dd\\dnx\\AspNetCore\\artifacts\\obj\\ConsoleClient\\azureMonitorClient.cs|" +
+ "OriginalItemSpec=../files/azureMonitor.json|FirstForGenerator=true";
+ var task = new GetCurrentOpenApiReference
+ {
+ Input = input,
+ };
+
+ string expectedIdentity = "../files/azureMonitor.json";
+ IDictionary expectedMetadata = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", "azureMonitorClient" },
+ { "CodeGenerator", "NSwagCSharp" },
+ { "FirstForGenerator", "true" },
+ { "Namespace", "ConsoleClient" },
+ { "Options", "" },
+ { "OriginalItemSpec", expectedIdentity },
+ { "OutputPath", "C:\\dd\\dnx\\AspNetCore\\artifacts\\obj\\ConsoleClient\\azureMonitorClient.cs" },
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.True(result);
+ Assert.False(task.Log.HasLoggedErrors);
+ var output = Assert.Single(task.Outputs);
+ Assert.Equal(expectedIdentity, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+
+ // The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata, orderedMetadata);
+ }
+ }
+}
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/GetOpenApiReferenceMetadataTest.cs b/src/Tools/Extensions.ApiDescription.Client/test/GetOpenApiReferenceMetadataTest.cs
new file mode 100644
index 0000000000..7bc5cc7fb9
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/GetOpenApiReferenceMetadataTest.cs
@@ -0,0 +1,576 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.Build.Utilities;
+using Xunit;
+
+namespace Microsoft.Extensions.ApiDescription.Client
+{
+ public class GetOpenApiReferenceMetadataTest
+ {
+ [Fact]
+ public void Execute_AddsExpectedMetadata()
+ {
+ // Arrange
+ var identity = Path.Combine("TestProjects", "files", "NSwag.json");
+ var @namespace = "Console.Client";
+ var outputPath = Path.Combine("obj", "NSwagClient.cs");
+ var inputMetadata = new Dictionary { { "CodeGenerator", "NSwagCSharp" } };
+ var task = new GetOpenApiReferenceMetadata
+ {
+ Extension = ".cs",
+ Inputs = new[] { new TaskItem(identity, inputMetadata) },
+ Namespace = @namespace,
+ OutputDirectory = "obj",
+ };
+
+ IDictionary expectedMetadata = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", "NSwagClient" },
+ { "CodeGenerator", "NSwagCSharp" },
+ { "FirstForGenerator", "true" },
+ { "Namespace", @namespace },
+ { "OriginalItemSpec", identity },
+ { "OutputPath", outputPath },
+ {
+ "SerializedMetadata",
+ $"Identity={identity}|CodeGenerator=NSwagCSharp|" +
+ $"OriginalItemSpec={identity}|FirstForGenerator=true|" +
+ $"OutputPath={outputPath}|ClassName=NSwagClient|Namespace={@namespace}"
+ },
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.True(result);
+ Assert.False(task.Log.HasLoggedErrors);
+ var output = Assert.Single(task.Outputs);
+ Assert.Equal(identity, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+
+ // The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata, orderedMetadata);
+ }
+
+ [Fact]
+ public void Execute_DoesNotOverrideClassName()
+ {
+ // Arrange
+ var identity = Path.Combine("TestProjects", "files", "NSwag.json");
+ var className = "ThisIsClassy";
+ var @namespace = "Console.Client";
+ var outputPath = Path.Combine("obj", $"NSwagClient.cs");
+ var inputMetadata = new Dictionary
+ {
+ { "CodeGenerator", "NSwagCSharp" },
+ { "ClassName", className },
+ };
+
+ var task = new GetOpenApiReferenceMetadata
+ {
+ Extension = ".cs",
+ Inputs = new[] { new TaskItem(identity, inputMetadata) },
+ Namespace = @namespace,
+ OutputDirectory = "obj",
+ };
+
+ IDictionary expectedMetadata = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", className },
+ { "CodeGenerator", "NSwagCSharp" },
+ { "FirstForGenerator", "true" },
+ { "Namespace", @namespace },
+ { "OriginalItemSpec", identity },
+ { "OutputPath", outputPath },
+ {
+ "SerializedMetadata",
+ $"Identity={identity}|CodeGenerator=NSwagCSharp|" +
+ $"ClassName={className}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
+ $"OutputPath={outputPath}|Namespace={@namespace}"
+ },
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.True(result);
+ Assert.False(task.Log.HasLoggedErrors);
+ var output = Assert.Single(task.Outputs);
+ Assert.Equal(identity, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+
+ // The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata, orderedMetadata);
+ }
+
+ [Fact]
+ public void Execute_DoesNotOverrideNamespace()
+ {
+ // Arrange
+ var defaultNamespace = "Console.Client";
+ var identity = Path.Combine("TestProjects", "files", "NSwag.json");
+ var @namespace = "NotConsole.NotClient";
+ var outputPath = Path.Combine("obj", "NSwagClient.cs");
+ var inputMetadata = new Dictionary
+ {
+ { "CodeGenerator", "NSwagCSharp" },
+ { "Namespace", @namespace },
+ };
+
+ var task = new GetOpenApiReferenceMetadata
+ {
+ Extension = ".cs",
+ Inputs = new[] { new TaskItem(identity, inputMetadata) },
+ Namespace = defaultNamespace,
+ OutputDirectory = "obj",
+ };
+
+ IDictionary expectedMetadata = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", "NSwagClient" },
+ { "CodeGenerator", "NSwagCSharp" },
+ { "FirstForGenerator", "true" },
+ { "Namespace", @namespace },
+ { "OriginalItemSpec", identity },
+ { "OutputPath", outputPath },
+ {
+ "SerializedMetadata",
+ $"Identity={identity}|CodeGenerator=NSwagCSharp|" +
+ $"Namespace={@namespace}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
+ $"OutputPath={outputPath}|ClassName=NSwagClient"
+ },
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.True(result);
+ Assert.False(task.Log.HasLoggedErrors);
+ var output = Assert.Single(task.Outputs);
+ Assert.Equal(identity, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+
+ // The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata, orderedMetadata);
+ }
+
+ [Fact]
+ public void Execute_DoesNotOverrideOutputPath_IfRooted()
+ {
+ // Arrange
+ var identity = Path.Combine("TestProjects", "files", "NSwag.json");
+ var className = "ThisIsClassy";
+ var @namespace = "Console.Client";
+ var outputPath = Path.Combine(Path.GetTempPath(), $"{className}.cs");
+ var inputMetadata = new Dictionary
+ {
+ { "CodeGenerator", "NSwagCSharp" },
+ { "OutputPath", outputPath }
+ };
+
+ var task = new GetOpenApiReferenceMetadata
+ {
+ Extension = ".cs",
+ Inputs = new[] { new TaskItem(identity, inputMetadata) },
+ Namespace = @namespace,
+ OutputDirectory = "bin",
+ };
+
+ IDictionary expectedMetadata = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", className },
+ { "CodeGenerator", "NSwagCSharp" },
+ { "FirstForGenerator", "true" },
+ { "Namespace", @namespace },
+ { "OriginalItemSpec", identity },
+ { "OutputPath", outputPath },
+ {
+ "SerializedMetadata",
+ $"Identity={identity}|CodeGenerator=NSwagCSharp|" +
+ $"OutputPath={outputPath}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
+ $"ClassName={className}|Namespace={@namespace}"
+ },
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.True(result);
+ Assert.False(task.Log.HasLoggedErrors);
+ var output = Assert.Single(task.Outputs);
+ Assert.Equal(identity, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+
+ // The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata, orderedMetadata);
+ }
+
+ [Fact]
+ public void Execute_LogsError_IfCodeGeneratorMissing()
+ {
+ // Arrange
+ var identity1 = Path.Combine("TestProjects", "files", "NSwag.json");
+ var identity2 = Path.Combine("TestProjects", "files", "swashbuckle.json");
+ var error1 = Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", "OpenApiReference", identity1);
+ var error2 = Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", "OpenApiProjectReference", identity2);
+ var @namespace = "Console.Client";
+ var inputMetadata1 = new Dictionary
+ {
+ { "ExtraMetadata", "this is extra" },
+ };
+ var inputMetadata2 = new Dictionary
+ {
+ { "Options", "-quiet" },
+ { "SourceProject", "ConsoleProject.csproj" },
+ };
+
+ var buildEngine = new MockBuildEngine();
+ var task = new GetOpenApiReferenceMetadata
+ {
+ BuildEngine = buildEngine,
+ Extension = ".cs",
+ Inputs = new[]
+ {
+ new TaskItem(identity1, inputMetadata1),
+ new TaskItem(identity2, inputMetadata2),
+ },
+ Namespace = @namespace,
+ OutputDirectory = "obj",
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.False(result);
+ Assert.True(task.Log.HasLoggedErrors);
+ Assert.Equal(2, buildEngine.Errors);
+ Assert.Equal(0, buildEngine.Messages);
+ Assert.Equal(0, buildEngine.Warnings);
+ Assert.Contains(error1, buildEngine.Log, StringComparison.OrdinalIgnoreCase);
+ Assert.Contains(error2, buildEngine.Log, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void Execute_LogsError_IfOutputPathDuplicated()
+ {
+ // Arrange
+ var identity = Path.Combine("TestProjects", "files", "NSwag.json");
+ var codeGenerator = "NSwagCSharp";
+ var error = Resources.FormatDuplicateFileOutputPaths(Path.Combine("obj", "NSwagClient.cs"));
+ var @namespace = "Console.Client";
+ var inputMetadata1 = new Dictionary
+ {
+ { "CodeGenerator", codeGenerator },
+ { "ExtraMetadata", "this is extra" },
+ };
+ var inputMetadata2 = new Dictionary
+ {
+ { "CodeGenerator", codeGenerator },
+ { "Options", "-quiet" },
+ };
+
+ var buildEngine = new MockBuildEngine();
+ var task = new GetOpenApiReferenceMetadata
+ {
+ BuildEngine = buildEngine,
+ Extension = ".cs",
+ Inputs = new[]
+ {
+ new TaskItem(identity, inputMetadata1),
+ new TaskItem(identity, inputMetadata2),
+ },
+ Namespace = @namespace,
+ OutputDirectory = "obj",
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.False(result);
+ Assert.True(task.Log.HasLoggedErrors);
+ Assert.Equal(1, buildEngine.Errors);
+ Assert.Equal(0, buildEngine.Messages);
+ Assert.Equal(0, buildEngine.Warnings);
+ Assert.Contains(error, buildEngine.Log, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void Execute_SetsClassName_BasedOnOutputPath()
+ {
+ // Arrange
+ var identity = Path.Combine("TestProjects", "files", "NSwag.json");
+ var className = "ThisIsClassy";
+ var @namespace = "Console.Client";
+ var outputPath = $"{className}.cs";
+ var expectedOutputPath = Path.Combine("bin", outputPath);
+ var inputMetadata = new Dictionary
+ {
+ { "CodeGenerator", "NSwagCSharp" },
+ { "OutputPath", outputPath }
+ };
+
+ var task = new GetOpenApiReferenceMetadata
+ {
+ Extension = ".cs",
+ Inputs = new[] { new TaskItem(identity, inputMetadata) },
+ Namespace = @namespace,
+ OutputDirectory = "bin",
+ };
+
+ IDictionary expectedMetadata = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", className },
+ { "CodeGenerator", "NSwagCSharp" },
+ { "FirstForGenerator", "true" },
+ { "Namespace", @namespace },
+ { "OriginalItemSpec", identity },
+ { "OutputPath", expectedOutputPath },
+ {
+ "SerializedMetadata",
+ $"Identity={identity}|CodeGenerator=NSwagCSharp|" +
+ $"OutputPath={expectedOutputPath}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
+ $"ClassName={className}|Namespace={@namespace}"
+ },
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.True(result);
+ Assert.False(task.Log.HasLoggedErrors);
+ var output = Assert.Single(task.Outputs);
+ Assert.Equal(identity, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+
+ // The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata, orderedMetadata);
+ }
+
+ [Theory]
+ [InlineData("aa-bb.cs", "aa_bb")]
+ [InlineData("aa.bb.cc.ts", "aa_bb_cc")]
+ [InlineData("aa\u20DF\u20DF.tsx", "aa__")] // UnicodeCategory.EnclosingMark (combining enclosing diamond)
+ [InlineData("aa\u2005bb\u2005cc.cs", "aa_bb_cc")] // UnicodeCategory.SpaceSeparator (four-per-em space)
+ [InlineData("aa\u0096\u0096bb.cs", "aa__bb")] // UnicodeCategory.Control (start of guarded area)
+ [InlineData("aa\uFF1C\uFF1C\uFF1Cbb.cs", "aa___bb")] // UnicodeCategory.MathSymbol (fullwidth less-than sign)
+ public void Execute_SetsClassName_BasedOnSanitizedOutputPath(string outputPath, string className)
+ {
+ // Arrange
+ var identity = Path.Combine("TestProjects", "files", "NSwag.json");
+ var @namespace = "Console.Client";
+ var expectedOutputPath = Path.Combine("bin", outputPath);
+ var inputMetadata = new Dictionary
+ {
+ { "CodeGenerator", "NSwagCSharp" },
+ { "OutputPath", outputPath }
+ };
+
+ var task = new GetOpenApiReferenceMetadata
+ {
+ Extension = ".cs",
+ Inputs = new[] { new TaskItem(identity, inputMetadata) },
+ Namespace = @namespace,
+ OutputDirectory = "bin",
+ };
+
+ IDictionary expectedMetadata = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", className },
+ { "CodeGenerator", "NSwagCSharp" },
+ { "FirstForGenerator", "true" },
+ { "Namespace", @namespace },
+ { "OriginalItemSpec", identity },
+ { "OutputPath", expectedOutputPath },
+ {
+ "SerializedMetadata",
+ $"Identity={identity}|CodeGenerator=NSwagCSharp|" +
+ $"OutputPath={expectedOutputPath}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
+ $"ClassName={className}|Namespace={@namespace}"
+ },
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.True(result);
+ Assert.False(task.Log.HasLoggedErrors);
+ var output = Assert.Single(task.Outputs);
+ Assert.Equal(identity, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+
+ // The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata, orderedMetadata);
+ }
+
+ [Fact]
+ public void Execute_SetsFirstForGenerator_UsesCorrectExtension()
+ {
+ // Arrange
+ var identity12 = Path.Combine("TestProjects", "files", "NSwag.json");
+ var identity3 = Path.Combine("TestProjects", "files", "swashbuckle.json");
+ var className12 = "NSwagClient";
+ var className3 = "swashbuckleClient";
+ var codeGenerator13 = "NSwagCSharp";
+ var codeGenerator2 = "NSwagTypeScript";
+ var inputMetadata1 = new Dictionary { { "CodeGenerator", codeGenerator13 } };
+ var inputMetadata2 = new Dictionary { { "CodeGenerator", codeGenerator2 } };
+ var inputMetadata3 = new Dictionary { { "CodeGenerator", codeGenerator13 } };
+ var @namespace = "Console.Client";
+ var outputPath1 = Path.Combine("obj", $"{className12}.cs");
+ var outputPath2 = Path.Combine("obj", $"{className12}.ts");
+ var outputPath3 = Path.Combine("obj", $"{className3}.cs");
+
+ var task = new GetOpenApiReferenceMetadata
+ {
+ Extension = ".cs",
+ Inputs = new[]
+ {
+ new TaskItem(identity12, inputMetadata1),
+ new TaskItem(identity12, inputMetadata2),
+ new TaskItem(identity3, inputMetadata3),
+ },
+ Namespace = @namespace,
+ OutputDirectory = "obj",
+ };
+
+ IDictionary expectedMetadata1 = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", className12 },
+ { "CodeGenerator", codeGenerator13 },
+ { "FirstForGenerator", "true" },
+ { "Namespace", @namespace },
+ { "OriginalItemSpec", identity12 },
+ { "OutputPath", outputPath1 },
+ {
+ "SerializedMetadata",
+ $"Identity={identity12}|CodeGenerator={codeGenerator13}|" +
+ $"OriginalItemSpec={identity12}|FirstForGenerator=true|" +
+ $"OutputPath={outputPath1}|ClassName={className12}|Namespace={@namespace}"
+ },
+ };
+ IDictionary expectedMetadata2 = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", className12 },
+ { "CodeGenerator", codeGenerator2 },
+ { "FirstForGenerator", "true" },
+ { "Namespace", @namespace },
+ { "OriginalItemSpec", identity12 },
+ { "OutputPath", outputPath2 },
+ {
+ "SerializedMetadata",
+ $"Identity={identity12}|CodeGenerator={codeGenerator2}|" +
+ $"OriginalItemSpec={identity12}|FirstForGenerator=true|" +
+ $"OutputPath={outputPath2}|ClassName={className12}|Namespace={@namespace}"
+ },
+ };
+ IDictionary expectedMetadata3 = new SortedDictionary(StringComparer.Ordinal)
+ {
+ { "ClassName", className3 },
+ { "CodeGenerator", codeGenerator13 },
+ { "FirstForGenerator", "false" },
+ { "Namespace", @namespace },
+ { "OriginalItemSpec", identity3 },
+ { "OutputPath", outputPath3 },
+ {
+ "SerializedMetadata",
+ $"Identity={identity3}|CodeGenerator={codeGenerator13}|" +
+ $"OriginalItemSpec={identity3}|FirstForGenerator=false|" +
+ $"OutputPath={outputPath3}|ClassName={className3}|Namespace={@namespace}"
+ },
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.True(result);
+ Assert.False(task.Log.HasLoggedErrors);
+ Assert.Collection(
+ task.Outputs,
+ output =>
+ {
+ Assert.Equal(identity12, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata1, orderedMetadata);
+ },
+ output =>
+ {
+ Assert.Equal(identity12, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata2, orderedMetadata);
+ },
+ output =>
+ {
+ Assert.Equal(identity3, output.ItemSpec);
+ var metadata = Assert.IsAssignableFrom>(output.CloneCustomMetadata());
+ var orderedMetadata = new SortedDictionary(StringComparer.Ordinal);
+ foreach (var key in metadata.Keys)
+ {
+ orderedMetadata.Add(key, metadata[key]);
+ }
+
+ Assert.Equal(expectedMetadata3, orderedMetadata);
+ });
+ }
+ }
+}
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/Microsoft.Extensions.ApiDescription.Client.Tests.csproj b/src/Tools/Extensions.ApiDescription.Client/test/Microsoft.Extensions.ApiDescription.Client.Tests.csproj
new file mode 100644
index 0000000000..c6a5144e61
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/Microsoft.Extensions.ApiDescription.Client.Tests.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netcoreapp3.0
+ $(DefaultItemExcludes);TestProjects\**\*
+ ApiDescriptionClientTests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/MockBuildEngine.cs b/src/Tools/Extensions.ApiDescription.Client/test/MockBuildEngine.cs
new file mode 100644
index 0000000000..a78d71ef5b
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/MockBuildEngine.cs
@@ -0,0 +1,102 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.Build.Framework;
+
+// Inspired by https://github.com/microsoft/msbuild/blob/master/src/Utilities.UnitTests/MockEngine.cs
+namespace Microsoft.Extensions.ApiDescription.Client
+{
+ internal sealed class MockBuildEngine : IBuildEngine3
+ {
+ private readonly StringBuilder _log = new StringBuilder();
+
+ public bool IsRunningMultipleNodes => false;
+
+ public bool ContinueOnError => false;
+
+ public string ProjectFileOfTaskNode => string.Empty;
+
+ public int LineNumberOfTaskNode => 0;
+
+ public int ColumnNumberOfTaskNode => 0;
+
+ internal MessageImportance MinimumMessageImportance { get; set; } = MessageImportance.Low;
+
+ internal int Messages { set; get; }
+
+ internal int Warnings { set; get; }
+
+ internal int Errors { set; get; }
+
+ internal string Log => _log.ToString();
+
+ public bool BuildProjectFile(
+ string projectFileName,
+ string[] targetNames,
+ IDictionary globalProperties,
+ IDictionary targetOutputs) => false;
+
+ public bool BuildProjectFile(
+ string projectFileName,
+ string[] targetNames,
+ IDictionary globalProperties,
+ IDictionary targetOutputs,
+ string toolsVersion) => false;
+
+ public bool BuildProjectFilesInParallel(
+ string[] projectFileNames,
+ string[] targetNames,
+ IDictionary[] globalProperties,
+ IDictionary[] targetOutputsPerProject,
+ string[] toolsVersion,
+ bool useResultsCache,
+ bool unloadProjectsOnCompletion) => false;
+
+ public BuildEngineResult BuildProjectFilesInParallel(
+ string[] projectFileNames,
+ string[] targetNames,
+ IDictionary[] globalProperties,
+ IList[] undefineProperties,
+ string[] toolsVersion,
+ bool includeTargetOutputs) => new BuildEngineResult(false, null);
+
+ public void LogErrorEvent(BuildErrorEventArgs eventArgs)
+ {
+ _log.AppendLine(eventArgs.Message);
+ Errors++;
+ }
+
+ public void LogWarningEvent(BuildWarningEventArgs eventArgs)
+ {
+ _log.AppendLine(eventArgs.Message);
+ Warnings++;
+ }
+
+ public void LogCustomEvent(CustomBuildEventArgs eventArgs)
+ {
+ _log.AppendLine(eventArgs.Message);
+ }
+
+ public void LogMessageEvent(BuildMessageEventArgs eventArgs)
+ {
+ // Only record the message if it is above the minimum importance. MessageImportance enum has higher values
+ // for lower importance.
+ if (eventArgs.Importance <= MinimumMessageImportance)
+ {
+ _log.AppendLine(eventArgs.Message);
+ Messages++;
+ }
+ }
+
+ public void Reacquire()
+ {
+ }
+
+ public void Yield()
+ {
+ }
+ }
+}
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/ConsoleClient/ConsoleClient.csproj b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/ConsoleClient/ConsoleClient.csproj
new file mode 100644
index 0000000000..642c7353b6
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/ConsoleClient/ConsoleClient.csproj
@@ -0,0 +1,38 @@
+
+
+
+
+ Exe
+ netcoreapp3.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+ <_Files Include="../../Microsoft.Extensions.ApiDescription.Client.*"
+ Exclude="../../Microsoft.Extensions.ApiDescription.Client.Tests.*" />
+
+
+
+
+
+
+
+
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/ConsoleClient/Program.cs b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/ConsoleClient/Program.cs
new file mode 100644
index 0000000000..9e223fb7c0
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/ConsoleClient/Program.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace ConsoleClient
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ Console.WriteLine("Hello World!");
+ }
+ }
+}
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/build/Fakes.targets b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/build/Fakes.targets
new file mode 100644
index 0000000000..78597f2ac6
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/build/Fakes.targets
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ <_Metadata>'%(CurrentOpenApiReference.FullPath)'
+ <_Metadata>$(_Metadata) Class: '%(CurrentOpenApiReference.Namespace).%(CurrentOpenApiReference.ClassName)'
+ <_Metadata>$(_Metadata) FirstForGenerator: '%(CurrentOpenApiReference.FirstForGenerator)'
+ <_Metadata>$(_Metadata) Options: '%(CurrentOpenApiReference.Options)'
+ <_Metadata>$(_Metadata) OutputPath: '%(CurrentOpenApiReference.OutputPath)'
+
+
+
+
+
+
+
+
+
+ <_Metadata>'%(FullPath)' Class: '%(Namespace).%(ClassName)'
+ <_Metadata>$(_Metadata) FirstForGenerator: '%(FirstForGenerator)' Options: '%(Options)'
+ <_Metadata>$(_Metadata) OutputPath: '%(OutputPath)'
+
+
+
+
+
+
+
+
+
+ <_Metadata>'%(FullPath)' Class: '%(Namespace).%(ClassName)'
+ <_Metadata>$(_Metadata) FirstForGenerator: '%(FirstForGenerator)' Options: '%(Options)'
+ <_Metadata>$(_Metadata) OutputPath: '%(OutputPath)'
+
+
+
+
+
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/files/NSwag.json b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/files/NSwag.json
new file mode 100644
index 0000000000..f8ba05b6f7
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/files/NSwag.json
@@ -0,0 +1,183 @@
+{
+ "x-generator": "NSwag v11.18.6.0 (NJsonSchema v9.10.67.0 (Newtonsoft.Json v11.0.0.0))",
+ "swagger": "2.0",
+ "info": {
+ "title": "My Title",
+ "version": "1.0.0"
+ },
+ "host": "localhost:5000",
+ "schemes": [
+ "http"
+ ],
+ "consumes": [
+ "application/json-patch+json",
+ "application/json",
+ "text/json",
+ "application/*+json",
+ "application/xml",
+ "text/xml",
+ "application/*+xml"
+ ],
+ "produces": [
+ "text/plain",
+ "application/json",
+ "text/json",
+ "application/xml",
+ "text/xml"
+ ],
+ "paths": {
+ "/api/Values/matches": {
+ "post": {
+ "tags": [
+ "Values"
+ ],
+ "operationId": "Values_Matches",
+ "consumes": [
+ "application/json-patch+json",
+ "application/json",
+ "text/json",
+ "application/*+json",
+ "application/xml",
+ "text/xml",
+ "application/*+xml"
+ ],
+ "parameters": [
+ {
+ "name": "possibilities",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "x-nullable": true
+ },
+ {
+ "type": "integer",
+ "name": "comparisonType",
+ "in": "query",
+ "x-schema": {
+ "$ref": "#/definitions/StringComparison"
+ },
+ "x-nullable": false,
+ "enum": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "x-nullable": true,
+ "description": "",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/Values": {
+ "get": {
+ "tags": [
+ "Values"
+ ],
+ "operationId": "Values_Get",
+ "responses": {
+ "200": {
+ "x-nullable": true,
+ "description": "",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "Values"
+ ],
+ "operationId": "Values_Post",
+ "consumes": [
+ "application/xml"
+ ],
+ "parameters": [
+ {
+ "name": "value",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "type": "string"
+ },
+ "x-nullable": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": ""
+ }
+ }
+ }
+ },
+ "/api/Values/too": {
+ "get": {
+ "tags": [
+ "Values"
+ ],
+ "operationId": "Values_GetToo",
+ "responses": {
+ "200": {
+ "x-nullable": true,
+ "description": "",
+ "schema": {
+ "$ref": "#/definitions/Model"
+ }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "StringComparison": {
+ "type": "integer",
+ "description": "",
+ "x-enumNames": [
+ "CurrentCulture",
+ "CurrentCultureIgnoreCase",
+ "InvariantCulture",
+ "InvariantCultureIgnoreCase",
+ "Ordinal",
+ "OrdinalIgnoreCase"
+ ],
+ "enum": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ ]
+ },
+ "Model": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "userName": {
+ "type": "string"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/files/azureMonitor.json b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/files/azureMonitor.json
new file mode 100644
index 0000000000..a63e1f5f2a
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/files/azureMonitor.json
@@ -0,0 +1 @@
+{"swagger":"2.0","info":{"title":"Microsoft Insights API","version":"2018-04-16","description":"Azure Monitor client to create/update/delete Scheduled Query Rules"},"host":"management.azure.com","schemes":["https"],"consumes":["application/json"],"produces":["application/json"],"security":[{"azure_auth":["user_impersonation"]}],"securityDefinitions":{"azure_auth":{"type":"oauth2","authorizationUrl":"https://login.microsoftonline.com/common/oauth2/authorize","flow":"implicit","description":"Azure Active Directory OAuth2 Flow","scopes":{"user_impersonation":"impersonate your user account"}}},"paths":{"/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/microsoft.insights/scheduledQueryRules/{ruleName}":{"put":{"description":"Creates or updates an log search rule.","tags":["scheduledQueryRules"],"operationId":"ScheduledQueryRules_CreateOrUpdate","parameters":[{"$ref":"#/parameters/SubscriptionIdParameter"},{"$ref":"#/parameters/ResourceGroupNameParameter"},{"$ref":"#/parameters/RuleNameParameter"},{"$ref":"#/parameters/ApiVersionParameter"},{"name":"parameters","in":"body","required":true,"schema":{"$ref":"#/definitions/LogSearchRuleResource"},"description":"The parameters of the rule to create or update."}],"responses":{"default":{"description":"Error response describing why the operation failed.","schema":{"$ref":"#/definitions/ErrorResponse"}},"200":{"description":"Successful request to update an Log Search rule","schema":{"$ref":"#/definitions/LogSearchRuleResource"}},"201":{"description":"Created alert rule","schema":{"$ref":"#/definitions/LogSearchRuleResource"}}},"x-ms-examples":{"Create or Update rule - AletringAction":{"$ref":"./examples/createOrUpdateScheduledQueryRules.json"},"Create or Update rule - LogToMetricAction":{"$ref":"./examples/createOrUpdateScheduledQueryRule-LogToMetricAction.json"}}},"get":{"description":"Gets an Log Search rule","tags":["scheduledQueryRules"],"operationId":"ScheduledQueryRules_Get","parameters":[{"$ref":"#/parameters/ResourceGroupNameParameter"},{"$ref":"#/parameters/RuleNameParameter"},{"$ref":"#/parameters/ApiVersionParameter"},{"$ref":"#/parameters/SubscriptionIdParameter"}],"responses":{"default":{"description":"Error response describing why the operation failed.","schema":{"$ref":"#/definitions/ErrorResponse"}},"200":{"description":"Successful request to get a Log Search rule","schema":{"$ref":"#/definitions/LogSearchRuleResource"}}},"x-ms-examples":{"Get rule":{"$ref":"./examples/getScheduledQueryRules.json"}}},"patch":{"tags":["scheduledQueryRules"],"description":"Update log search Rule.","operationId":"ScheduledQueryRules_Update","parameters":[{"$ref":"#/parameters/SubscriptionIdParameter"},{"$ref":"#/parameters/ResourceGroupNameParameter"},{"$ref":"#/parameters/RuleNameParameter"},{"$ref":"#/parameters/ApiVersionParameter"},{"name":"parameters","in":"body","required":true,"schema":{"$ref":"#/definitions/LogSearchRuleResourcePatch"},"description":"The parameters of the rule to update."}],"responses":{"default":{"description":"Error response describing why the operation failed.","schema":{"$ref":"#/definitions/ErrorResponse"}},"200":{"description":"Successful request to update an Log Search rule","schema":{"$ref":"#/definitions/LogSearchRuleResource"}}},"x-ms-examples":{"Patch Log Search Rule":{"$ref":"./examples/patchScheduledQueryRules.json"}}},"delete":{"description":"Deletes a Log Search rule","tags":["scheduledQueryRules"],"operationId":"ScheduledQueryRules_Delete","parameters":[{"$ref":"#/parameters/ResourceGroupNameParameter"},{"$ref":"#/parameters/RuleNameParameter"},{"$ref":"#/parameters/ApiVersionParameter"},{"$ref":"#/parameters/SubscriptionIdParameter"}],"responses":{"default":{"description":"Error response describing why the operation failed.","schema":{"$ref":"#/definitions/ErrorResponse"}},"200":{"description":"Successful request to delete a Log Search rule"},"204":{"description":"No Content. Resource not found"}},"x-ms-examples":{"Delete rule":{"$ref":"./examples/deleteScheduledQueryRules.json"}}}},"/subscriptions/{subscriptionId}/providers/microsoft.insights/scheduledQueryRules":{"get":{"tags":["scheduledQueryRules"],"operationId":"ScheduledQueryRules_ListBySubscription","description":"List the Log Search rules within a subscription group.","parameters":[{"$ref":"#/parameters/ApiVersionParameter"},{"$ref":"#/parameters/FilterParameter"},{"$ref":"#/parameters/SubscriptionIdParameter"}],"responses":{"default":{"description":"Error response describing why the operation failed.","schema":{"$ref":"#/definitions/ErrorResponse"}},"200":{"description":"Successful request for a list of alert rules","schema":{"$ref":"#/definitions/LogSearchRuleResourceCollection"}}},"x-ms-pageable":{"nextLinkName":null},"x-ms-examples":{"List rules":{"$ref":"./examples/listScheduledQueryRules.json"}},"x-ms-odata":"#/definitions/LogSearchRuleResource"}},"/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/microsoft.insights/scheduledQueryRules":{"get":{"tags":["scheduledQueryRules"],"operationId":"ScheduledQueryRules_ListByResourceGroup","description":"List the Log Search rules within a resource group.","parameters":[{"$ref":"#/parameters/ResourceGroupNameParameter"},{"$ref":"#/parameters/ApiVersionParameter"},{"$ref":"#/parameters/FilterParameter"},{"$ref":"#/parameters/SubscriptionIdParameter"}],"responses":{"default":{"description":"Error response describing why the operation failed.","schema":{"$ref":"#/definitions/ErrorResponse"}},"200":{"description":"Successful request for a list of alert rules","schema":{"$ref":"#/definitions/LogSearchRuleResourceCollection"}}},"x-ms-pageable":{"nextLinkName":null},"x-ms-examples":{"List rules":{"$ref":"./examples/listScheduledQueryRules.json"}},"x-ms-odata":"#/definitions/LogSearchRuleResource"}}},"definitions":{"Resource":{"properties":{"id":{"type":"string","readOnly":true,"description":"Azure resource Id"},"name":{"type":"string","readOnly":true,"description":"Azure resource name"},"type":{"type":"string","readOnly":true,"description":"Azure resource type"},"location":{"type":"string","description":"Resource location","x-ms-mutability":["create","read"]},"tags":{"additionalProperties":{"type":"string"},"description":"Resource tags"}},"required":["location"],"x-ms-azure-resource":true,"description":"An azure resource object"},"LogSearchRuleResource":{"type":"object","allOf":[{"$ref":"#/definitions/Resource"}],"required":["properties"],"properties":{"properties":{"x-ms-client-flatten":true,"$ref":"#/definitions/LogSearchRule","description":"The rule properties of the resource."}},"description":"The Log Search Rule resource."},"LogSearchRuleResourcePatch":{"properties":{"tags":{"additionalProperties":{"type":"string"},"description":"Resource tags"},"properties":{"x-ms-client-flatten":true,"$ref":"#/definitions/LogSearchRulePatch","description":"The log search rule properties of the resource."}},"description":"The log search rule resource for patch operations."},"LogSearchRuleResourceCollection":{"properties":{"value":{"type":"array","items":{"$ref":"#/definitions/LogSearchRuleResource"},"description":"The values for the Log Search Rule resources."}},"description":"Represents a collection of Log Search rule resources."},"Source":{"type":"object","description":"Specifies the log search query.","properties":{"query":{"type":"string","description":"Log search query. Required for action type - AlertingAction"},"authorizedResources":{"type":"array","items":{"type":"string"},"description":"List of Resource referred into query"},"dataSourceId":{"type":"string","description":"The resource uri over which log search query is to be run."},"queryType":{"$ref":"#/definitions/QueryType","description":"Set value to 'ResultCount' ."}},"required":["dataSourceId"]},"Schedule":{"type":"object","description":"Defines how often to run the search and the time interval.","properties":{"frequencyInMinutes":{"type":"integer","format":"int32","description":"frequency (in minutes) at which rule condition should be evaluated."},"timeWindowInMinutes":{"type":"integer","format":"int32","description":"Time window for which data needs to be fetched for query (should be greater than or equal to frequencyInMinutes)."}},"required":["frequencyInMinutes","timeWindowInMinutes"]},"TriggerCondition":{"description":"The condition that results in the Log Search rule.","properties":{"thresholdOperator":{"$ref":"#/definitions/ConditionalOperator","description":"Evaluation operation for rule - 'GreaterThan' or 'LessThan."},"threshold":{"description":"Result or count threshold based on which rule should be triggered.","format":"double","type":"number"},"metricTrigger":{"$ref":"#/definitions/LogMetricTrigger","description":"Trigger condition for metric query rule"}},"required":["thresholdOperator","threshold"],"type":"object"},"AzNsActionGroup":{"type":"object","properties":{"actionGroup":{"type":"array","description":"Azure Action Group reference.","items":{"type":"string"}},"emailSubject":{"type":"string","description":"Custom subject override for all email ids in Azure action group"},"customWebhookPayload":{"type":"string","description":"Custom payload to be sent for all webook URI in Azure action group"}},"description":"Azure action group"},"LogMetricTrigger":{"type":"object","properties":{"thresholdOperator":{"$ref":"#/definitions/ConditionalOperator","description":"Evaluation operation for Metric -'GreaterThan' or 'LessThan' or 'Equal'."},"threshold":{"format":"double","type":"number","description":"The threshold of the metric trigger."},"metricTriggerType":{"$ref":"#/definitions/MetricTriggerType","description":"Metric Trigger Type - 'Consecutive' or 'Total'"},"metricColumn":{"type":"string","description":"Evaluation of metric on a particular column"}},"description":"A log metrics trigger descriptor."},"ConditionalOperator":{"type":"string","enum":["GreaterThan","LessThan","Equal"],"x-ms-enum":{"name":"ConditionalOperator","modelAsString":true},"description":"Result Condition Evaluation criteria. Supported Values - 'GreaterThan' or 'LessThan' or 'Equal'."},"MetricTriggerType":{"type":"string","enum":["Consecutive","Total"],"x-ms-enum":{"name":"metricTriggerType","modelAsString":true},"description":"Metric Trigger Evaluation Type"},"AlertSeverity":{"type":"string","enum":["0","1","2","3","4"],"x-ms-enum":{"name":"AlertSeverity","modelAsString":true},"description":"Severity Level of Alert"},"QueryType":{"type":"string","enum":["ResultCount"],"x-ms-enum":{"name":"QueryType","modelAsString":true},"description":"Set value to 'ResultAcount'"},"LogSearchRule":{"description":"Log Search Rule Definition","properties":{"description":{"type":"string","description":"The description of the Log Search rule."},"enabled":{"type":"string","description":"The flag which indicates whether the Log Search rule is enabled. Value should be true or false","enum":["true","false"],"x-ms-enum":{"name":"enabled","modelAsString":true}},"lastUpdatedTime":{"readOnly":true,"type":"string","format":"date-time","description":"Last time the rule was updated in IS08601 format."},"provisioningState":{"readOnly":true,"type":"string","enum":["Succeeded","Deploying","Canceled","Failed"],"x-ms-enum":{"name":"provisioningState","modelAsString":true},"description":"Provisioning state of the scheduledquery rule"},"source":{"$ref":"#/definitions/Source","description":"Data Source against which rule will Query Data"},"schedule":{"$ref":"#/definitions/Schedule","description":"Schedule (Frequnecy, Time Window) for rule. Required for action type - AlertingAction"},"action":{"$ref":"#/definitions/Action","description":"Action needs to be taken on rule execution."}},"required":["source","action"]},"LogSearchRulePatch":{"description":"Log Search Rule Definition for Patching","properties":{"enabled":{"type":"string","description":"The flag which indicates whether the Log Search rule is enabled. Value should be true or false","enum":["true","false"],"x-ms-enum":{"name":"enabled","modelAsString":true}}}},"Action":{"type":"object","discriminator":"odata.type","properties":{"odata.type":{"type":"string","description":"Specifies the action. Supported values - AlertingAction, LogToMetricAction"}},"required":["odata.type"],"description":"Action descriptor."},"AlertingAction":{"description":"Specifiy action need to be taken when rule type is Alert","x-ms-discriminator-value":"Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction","type":"object","allOf":[{"$ref":"#/definitions/Action"}],"properties":{"severity":{"$ref":"#/definitions/AlertSeverity","description":"Severity of the alert"},"aznsAction":{"$ref":"#/definitions/AzNsActionGroup","description":"Azure action group reference."},"throttlingInMin":{"type":"integer","format":"int32","description":"time (in minutes) for which Alerts should be throttled or suppressed."},"trigger":{"$ref":"#/definitions/TriggerCondition","description":"The trigger condition that results in the alert rule being."}},"required":["aznsAction","trigger","severity"]},"Dimension":{"type":"object","description":"Specifies the criteria for converting log to metric.","properties":{"name":{"type":"string","description":"Name of the dimension"},"operator":{"type":"string","description":"Operator for dimension values","enum":["Include"],"x-ms-enum":{"name":"operator","modelAsString":true}},"values":{"type":"array","items":{"type":"string"},"description":"List of dimension values"}},"required":["name","operator","values"]},"Criteria":{"type":"object","description":"Specifies the criteria for converting log to metric.","properties":{"metricName":{"type":"string","description":"Name of the metric"},"dimensions":{"type":"array","items":{"$ref":"#/definitions/Dimension"},"description":"List of Dimensions for creating metric"}},"required":["metricName"]},"LogToMetricAction":{"description":"Specifiy action need to be taken when rule type is converting log to metric","x-ms-discriminator-value":"Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.LogToMetricAction","type":"object","allOf":[{"$ref":"#/definitions/Action"}],"properties":{"criteria":{"$ref":"#/definitions/Criteria","description":"Severity of the alert"}},"required":["criteria"]},"ErrorResponse":{"description":"Describes the format of Error response.","type":"object","properties":{"code":{"description":"Error code","type":"string"},"message":{"description":"Error message indicating why the operation failed.","type":"string"}}}},"parameters":{"SubscriptionIdParameter":{"name":"subscriptionId","in":"path","required":true,"type":"string","description":"The Azure subscription Id."},"ApiVersionParameter":{"name":"api-version","in":"query","required":true,"type":"string","description":"Client Api Version."},"ResourceGroupNameParameter":{"name":"resourceGroupName","in":"path","required":true,"type":"string","description":"The name of the resource group.","x-ms-parameter-location":"method"},"RuleNameParameter":{"name":"ruleName","in":"path","required":true,"type":"string","description":"The name of the rule.","x-ms-parameter-location":"method"},"FilterParameter":{"name":"$filter","in":"query","required":false,"type":"string","description":"The filter to apply on the operation. For more information please see https://msdn.microsoft.com/en-us/library/azure/dn931934.aspx","x-ms-parameter-location":"method"}}}
\ No newline at end of file
diff --git a/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/files/swashbuckle.json b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/files/swashbuckle.json
new file mode 100644
index 0000000000..57b917e213
--- /dev/null
+++ b/src/Tools/Extensions.ApiDescription.Client/test/TestProjects/files/swashbuckle.json
@@ -0,0 +1,155 @@
+{
+ "swagger": "2.0",
+ "info": {
+ "version": "and so is version",
+ "title": "title is required"
+ },
+ "paths": {
+ "/api/Values/matches": {
+ "post": {
+ "tags": [
+ "Values"
+ ],
+ "operationId": "ApiValuesMatchesPost",
+ "consumes": [
+ "application/json-patch+json",
+ "application/json",
+ "text/json",
+ "application/*+json",
+ "application/xml",
+ "text/xml",
+ "application/*+xml"
+ ],
+ "produces": [
+ "text/plain",
+ "application/json",
+ "text/json",
+ "application/xml",
+ "text/xml"
+ ],
+ "parameters": [
+ {
+ "name": "possibilities",
+ "in": "body",
+ "required": false,
+ "schema": {
+ "uniqueItems": false,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "name": "comparisonType",
+ "in": "query",
+ "required": false,
+ "type": "integer",
+ "format": "int32",
+ "enum": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "schema": {
+ "uniqueItems": false,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/Values": {
+ "get": {
+ "tags": [
+ "Values"
+ ],
+ "operationId": "ApiValuesGet",
+ "consumes": [],
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "schema": {
+ "uniqueItems": false,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "Values"
+ ],
+ "operationId": "ApiValuesPost",
+ "consumes": [
+ "application/xml"
+ ],
+ "produces": [],
+ "parameters": [
+ {
+ "name": "value",
+ "in": "body",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success"
+ }
+ }
+ }
+ },
+ "/api/Values/too": {
+ "get": {
+ "tags": [
+ "Values"
+ ],
+ "operationId": "ApiValuesTooGet",
+ "consumes": [],
+ "produces": [
+ "application/xml"
+ ],
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "schema": {
+ "$ref": "#/definitions/Model"
+ }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "Model": {
+ "type": "object",
+ "properties": {
+ "userName": {
+ "type": "string"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tools/Tools.sln b/src/Tools/Tools.sln
index 1de511f5cc..f25c3fc982 100644
--- a/src/Tools/Tools.sln
+++ b/src/Tools/Tools.sln
@@ -23,22 +23,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.dotnet-openapi",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-microsoft.openapi.Tests", "Microsoft.dotnet-openapi\test\dotnet-microsoft.openapi.Tests.csproj", "{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions.ApiDescription.Client", "Extensions.ApiDescription.Client", "{78610083-1FCE-47F5-AB4D-AF0E1313B351}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A0D46647-EF66-456E-9F79-134985E7445E}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client", "Extensions.ApiDescription.Client\src\Microsoft.Extensions.ApiDescription.Client.csproj", "{B29B2627-3604-4FDB-A976-EF1E077F5316}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions.ApiDescription.Server", "Extensions.ApiDescription.Server", "{003EA860-5DFC-40AE-87C0-9D21BB2C68D7}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4110117E-3C28-4064-A7A3-B112BD6F8CB9}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "dotnet-getdocument\src\dotnet-getdocument.csproj", "{160A445F-7E1F-430D-9403-41F7F6F4A16E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Server", "Extensions.ApiDescription.Server\src\Microsoft.Extensions.ApiDescription.Server.csproj", "{233119FC-E4C1-421C-89AE-1A445C5A947F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "GetDocumentInsider\src\GetDocumentInsider.csproj", "{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client.Tests", "Extensions.ApiDescription.Client\test\Microsoft.Extensions.ApiDescription.Client.Tests.csproj", "{2C62584B-EC31-40C8-819B-E46334645AE5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -53,38 +49,6 @@ Global
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Release|Any CPU.Build.0 = Release|Any CPU
- {0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Release|Any CPU.Build.0 = Release|Any CPU
- {7BBDBDA2-299F-4C36-8338-23C525901DE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7BBDBDA2-299F-4C36-8338-23C525901DE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7BBDBDA2-299F-4C36-8338-23C525901DE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7BBDBDA2-299F-4C36-8338-23C525901DE0}.Release|Any CPU.Build.0 = Release|Any CPU
- {1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Release|Any CPU.Build.0 = Release|Any CPU
- {15FB0E39-1A28-4325-AD3C-76352516C80D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {15FB0E39-1A28-4325-AD3C-76352516C80D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {15FB0E39-1A28-4325-AD3C-76352516C80D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {15FB0E39-1A28-4325-AD3C-76352516C80D}.Release|Any CPU.Build.0 = Release|Any CPU
- {B29B2627-3604-4FDB-A976-EF1E077F5316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B29B2627-3604-4FDB-A976-EF1E077F5316}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B29B2627-3604-4FDB-A976-EF1E077F5316}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B29B2627-3604-4FDB-A976-EF1E077F5316}.Release|Any CPU.Build.0 = Release|Any CPU
- {160A445F-7E1F-430D-9403-41F7F6F4A16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {160A445F-7E1F-430D-9403-41F7F6F4A16E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {160A445F-7E1F-430D-9403-41F7F6F4A16E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {160A445F-7E1F-430D-9403-41F7F6F4A16E}.Release|Any CPU.Build.0 = Release|Any CPU
- {233119FC-E4C1-421C-89AE-1A445C5A947F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {233119FC-E4C1-421C-89AE-1A445C5A947F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {233119FC-E4C1-421C-89AE-1A445C5A947F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {233119FC-E4C1-421C-89AE-1A445C5A947F}.Release|Any CPU.Build.0 = Release|Any CPU
- {EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Release|Any CPU.Build.0 = Release|Any CPU
{98550159-E04E-44EB-A969-E5BF12444B94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98550159-E04E-44EB-A969-E5BF12444B94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98550159-E04E-44EB-A969-E5BF12444B94}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -109,17 +73,31 @@ Global
{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B29B2627-3604-4FDB-A976-EF1E077F5316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B29B2627-3604-4FDB-A976-EF1E077F5316}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B29B2627-3604-4FDB-A976-EF1E077F5316}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B29B2627-3604-4FDB-A976-EF1E077F5316}.Release|Any CPU.Build.0 = Release|Any CPU
+ {160A445F-7E1F-430D-9403-41F7F6F4A16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {160A445F-7E1F-430D-9403-41F7F6F4A16E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {160A445F-7E1F-430D-9403-41F7F6F4A16E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {160A445F-7E1F-430D-9403-41F7F6F4A16E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {233119FC-E4C1-421C-89AE-1A445C5A947F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {233119FC-E4C1-421C-89AE-1A445C5A947F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {233119FC-E4C1-421C-89AE-1A445C5A947F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {233119FC-E4C1-421C-89AE-1A445C5A947F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2C62584B-EC31-40C8-819B-E46334645AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2C62584B-EC31-40C8-819B-E46334645AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2C62584B-EC31-40C8-819B-E46334645AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2C62584B-EC31-40C8-819B-E46334645AE5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {A0D46647-EF66-456E-9F79-134985E7445E} = {78610083-1FCE-47F5-AB4D-AF0E1313B351}
- {B29B2627-3604-4FDB-A976-EF1E077F5316} = {A0D46647-EF66-456E-9F79-134985E7445E}
- {4110117E-3C28-4064-A7A3-B112BD6F8CB9} = {003EA860-5DFC-40AE-87C0-9D21BB2C68D7}
- {160A445F-7E1F-430D-9403-41F7F6F4A16E} = {4110117E-3C28-4064-A7A3-B112BD6F8CB9}
- {233119FC-E4C1-421C-89AE-1A445C5A947F} = {4110117E-3C28-4064-A7A3-B112BD6F8CB9}
- {EB63AECB-B898-475D-90F7-FE61F9C1CCC6} = {4110117E-3C28-4064-A7A3-B112BD6F8CB9}
{E16F10C8-5FC3-420B-AB33-D6E5CBE89B75} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C} = {2C485EAF-E4DE-4D14-8AE1-330641E17D44}
{98550159-E04E-44EB-A969-E5BF12444B94} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
@@ -128,6 +106,12 @@ Global
{25F8DCC4-4571-42F7-BA0F-5C2D5A802297} = {2C485EAF-E4DE-4D14-8AE1-330641E17D44}
{C806041C-30F2-4B27-918A-5FF3576B833B} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F} = {2C485EAF-E4DE-4D14-8AE1-330641E17D44}
+ {B29B2627-3604-4FDB-A976-EF1E077F5316} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
+ {003EA860-5DFC-40AE-87C0-9D21BB2C68D7} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
+ {160A445F-7E1F-430D-9403-41F7F6F4A16E} = {003EA860-5DFC-40AE-87C0-9D21BB2C68D7}
+ {233119FC-E4C1-421C-89AE-1A445C5A947F} = {003EA860-5DFC-40AE-87C0-9D21BB2C68D7}
+ {EB63AECB-B898-475D-90F7-FE61F9C1CCC6} = {003EA860-5DFC-40AE-87C0-9D21BB2C68D7}
+ {2C62584B-EC31-40C8-819B-E46334645AE5} = {2C485EAF-E4DE-4D14-8AE1-330641E17D44}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EC668D8E-97B9-4758-9E5C-2E5DD6B9137B}