diff --git a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs index 81f1ff97..f1b38fb9 100644 --- a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs +++ b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -235,27 +236,44 @@ private string GetRuntimePath(string functionAppDirectory, bool appendBin) // This method relies on the dependency manifest file to find the function app runtime dll file. // It can be either .deps.json or function.deps.json. In most cases, at least the // function.deps.json should exist, but in case no manifest exists, it will throw the exception. + // In case there are multiple .deps.json files, the root project will be picked, based on the + // dependencies mentioned in the .deps.json files. private async Task GetRuntimeFilenameAsync(string functionAppDirectory) { var files = Directory.GetFiles(functionAppDirectory, "*.deps.json", SearchOption.AllDirectories); - var file = files.FirstOrDefault(); - if (file.IsNullOrWhiteSpace()) + if (!files.Any()) { throw new InvalidOperationException("Invalid function app directory"); } + var dependencyManifests = new List(); + foreach (var file in files) + { + dependencyManifests.Add(await GetDependencyManifestAsync(file)); + } + + var runtimes = dependencyManifests + .Select(manifest => manifest.Targets[manifest.RuntimeTarget.Name].First()) + .Select(target => new + { + Name = target.Key.Split('/').First(), + FileName = target.Value.Runtime.First().Key, + Dependencies = target.Value.Dependencies.Keys + }); + + var referencedRuntimes = runtimes.SelectMany(d => d.Dependencies); + return runtimes.FirstOrDefault(r => !referencedRuntimes.Contains(r.Name))?.FileName; + } + + private static async Task GetDependencyManifestAsync(string file) + { var serialised = default(string); using (var reader = File.OpenText(file)) { serialised = await reader.ReadToEndAsync(); } - var manifesto = JsonConvert.DeserializeObject(serialised); - var runtimeTarget = manifesto.RuntimeTarget.Name; - var runtimes = manifesto.Targets[runtimeTarget].Values; - var runtime = runtimes.First().Runtime.First().Key; - - return runtime; + return JsonConvert.DeserializeObject(serialised); } private Assembly GetAssembly(object instance) diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs index 53007b79..937a3b9d 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs @@ -1,11 +1,11 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; @@ -236,27 +236,44 @@ private string GetRuntimePath(string functionAppDirectory, bool appendBin) // This method relies on the dependency manifest file to find the function app runtime dll file. // It can be either .deps.json or function.deps.json. In most cases, at least the // function.deps.json should exist, but in case no manifest exists, it will throw the exception. + // In case there are multiple .deps.json files, the root project will be picked, based on the + // dependencies mentioned in the .deps.json files. private async Task GetRuntimeFilenameAsync(string functionAppDirectory) { var files = Directory.GetFiles(functionAppDirectory, "*.deps.json", SearchOption.AllDirectories); - var file = files.FirstOrDefault(); - if (file.IsNullOrWhiteSpace()) + if (!files.Any()) { throw new InvalidOperationException("Invalid function app directory"); } + var dependencyManifests = new List(); + foreach (var file in files) + { + dependencyManifests.Add(await GetDependencyManifestAsync(file)); + } + + var runtimes = dependencyManifests + .Select(manifest => manifest.Targets[manifest.RuntimeTarget.Name].First()) + .Select(target => new + { + Name = target.Key.Split('/').First(), + FileName = target.Value.Runtime.First().Key, + Dependencies = target.Value.Dependencies.Keys + }); + + var referencedRuntimes = runtimes.SelectMany(d => d.Dependencies); + return runtimes.FirstOrDefault(r => !referencedRuntimes.Contains(r.Name))?.FileName; + } + + private static async Task GetDependencyManifestAsync(string file) + { var serialised = default(string); using (var reader = File.OpenText(file)) { serialised = await reader.ReadToEndAsync(); } - var manifesto = JsonConvert.DeserializeObject(serialised); - var runtimeTarget = manifesto.RuntimeTarget.Name; - var runtimes = manifesto.Targets[runtimeTarget].Values; - var runtime = runtimes.First().Runtime.First().Key; - - return runtime; + return JsonConvert.DeserializeObject(serialised); } private Assembly GetAssembly(object instance) diff --git a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests.csproj b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests.csproj index 8f2712c7..66585192 100644 --- a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests.csproj +++ b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests.csproj @@ -36,6 +36,9 @@ Always + + Always + diff --git a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs index f809b56f..3df8352f 100644 --- a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs +++ b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs @@ -31,6 +31,18 @@ public async Task Given_Type_When_Initiated_Then_It_Should_Return_ApplicationAss assembly.DefinedTypes.Select(p => p.FullName).Should().Contain(ti.FullName); } + [TestMethod] + public async Task Given_Type_With_Referenced_Project_When_Initiated_Then_It_Should_Return_ApplicationAssemblyOfRootAssembly() + { + var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName; + var context = new OpenApiHttpTriggerContext(); + + var assembly = (await context.SetApplicationAssemblyAsync(location, false)) + .ApplicationAssembly; + + assembly.FullName.Should().Be(typeof(OpenApiHttpTriggerContextTests).Assembly.FullName); + } + [DataTestMethod] [DataRow(typeof(IOpenApiHttpTriggerContext))] [DataRow(typeof(OpenApiHttpTriggerContext))] diff --git a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/TestData/FakeDependencyFile.deps.json b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/TestData/FakeDependencyFile.deps.json new file mode 100644 index 00000000..78486474 --- /dev/null +++ b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/TestData/FakeDependencyFile.deps.json @@ -0,0 +1,22 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v5.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v5.0": { + "Microsoft.Azure.Functions.Worker.Extensions.OpenApi/1.0.0": { + "dependencies": { + "Microsoft.Azure.Core.NewtonsoftJson": "1.0.0", + "Microsoft.Azure.Functions.Worker.Core": "1.1.0", + "Microsoft.Azure.Functions.Worker.Extensions.Http": "3.0.12", + "Microsoft.Azure.WebJobs.Extensions.OpenApi.Core": "1.0.0" + }, + "runtime": { + "Microsoft.Azure.Functions.Worker.Extensions.OpenApi.dll": {} + } + } + } + } +} diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.csproj b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.csproj index 97b062dc..85c5ddec 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.csproj +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.csproj @@ -37,6 +37,9 @@ Always + + Always + diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs index 39c242c3..c4950ee7 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs @@ -30,6 +30,18 @@ public async Task Given_Type_When_Initiated_Then_It_Should_Return_ApplicationAss assembly.DefinedTypes.Select(p => p.FullName).Should().Contain(ti.FullName); } + [TestMethod] + public async Task Given_Type_With_Referenced_Project_When_Initiated_Then_It_Should_Return_ApplicationAssemblyOfRootAssembly() + { + var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName; + var context = new OpenApiHttpTriggerContext(); + + var assembly = (await context.SetApplicationAssemblyAsync(location, false)) + .ApplicationAssembly; + + assembly.FullName.Should().Be(typeof(OpenApiHttpTriggerContextTests).Assembly.FullName); + } + [DataTestMethod] [DataRow(typeof(IOpenApiHttpTriggerContext))] [DataRow(typeof(OpenApiHttpTriggerContext))] diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/TestData/FakeDependencyFile.deps.json b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/TestData/FakeDependencyFile.deps.json new file mode 100644 index 00000000..706124f6 --- /dev/null +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/TestData/FakeDependencyFile.deps.json @@ -0,0 +1,22 @@ +{ + "runtimeTarget": { + "name": ".NETStandard,Version=v2.0/", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETStandard,Version=v2.0": {}, + ".NETStandard,Version=v2.0/": { + "Microsoft.Azure.WebJobs.Extensions.OpenApi/1.0.0": { + "dependencies": { + "Microsoft.Azure.WebJobs.Extensions.OpenApi.Core": "1.0.0", + "Microsoft.Azure.WebJobs.Script.Abstractions": "1.0.0-preview", + "NETStandard.Library": "2.0.3" + }, + "runtime": { + "Microsoft.Azure.WebJobs.Extensions.OpenApi.dll": {} + } + } + } + } +}