diff --git a/docs/openapi-core.md b/docs/openapi-core.md index e2706190..c0405f42 100644 --- a/docs/openapi-core.md +++ b/docs/openapi-core.md @@ -66,7 +66,7 @@ If the above conditions are met, add the following key to your `local.settings.j To generate an OpenAPI document, [OpenApiInfo object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#infoObject) needs to be defined. ***It's totally optional***, but if you want, you can implement the `IOpenApiConfigurationOptions` interface within your Azure Functions project to provide OpenAPI metadata like below: ```csharp -public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions +public class MyOpenApiConfigurationOptions : IOpenApiConfigurationOptions { public OpenApiInfo Info { get; set; } = new OpenApiInfo() { @@ -102,7 +102,7 @@ It's often required for the API app to have more than one base URL, with differe Alternatively, add `OpenApiServer` details to the `Servers` property like: ```csharp -public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions +public class MyOpenApiConfigurationOptions : IOpenApiConfigurationOptions { ... @@ -127,7 +127,7 @@ public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions The default version of OpenAPI document rendered is V2 (AKA Swagger). However, you can override the default rendering behaviour by implementing the `OpenApiVersion` property. ```csharp -public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions +public class MyOpenApiConfigurationOptions : IOpenApiConfigurationOptions { ... @@ -145,7 +145,7 @@ public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions If you want to force either HTTP or HTTPS, configure the following properties on the `IOpenApiConfigurationOptions` interface. ```csharp -public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions +public class MyOpenApiConfigurationOptions : IOpenApiConfigurationOptions { ... @@ -198,65 +198,14 @@ public class MyOpenApiConfigurationOptions : DefaultOpenApiConfigurationOptions } ``` -### Dynamically configuring `IOpenApiConfigurationOptions` at Runtime ### -There may be instances where you want to configure the `IOpenApiConfigurationOptions` at runtime. +### Injecting `OpenApiConfigurationOptions` during Startup ### -To dynamically modify your configuration options at runtime you can use the following examples: +You may want to inject the `OpenApiConfigurationOptions` instance during startup: -#### In of Proc #### +* [in-proc worker](./openapi-in-proc.md#injecting-openapiconfigurationoptions-during-startup) +* [out-of-proc worker](./openapi-out-of-proc.md#injecting-openapiconfigurationoptions-during-startup) -```csharp -public override void Configure(IFunctionsHostBuilder builder) -{ - var fixture = new Fixture(); - builder.Services.AddSingleton(fixture); - - // Example: If you want to change the configuration during startup of your function you can use the following code: - services.AddSingleton(x => - { - return new OpenApiConfigurationOptions() - { - Info = new Microsoft.OpenApi.Models.OpenApiInfo - { - Title = "A dynamic title generated at runtime", - Description = "Dynamic Open API information at runtime" - } - }; - }); -} -``` - -#### Out of Proc #### - -```csharp -public static void Main() - { - var host = new HostBuilder() - .ConfigureFunctionsWorkerDefaults(worker => worker.UseNewtonsoftJson()) - .ConfigureOpenApi() - .ConfigureServices(services => services.AddSingleton()) - .ConfigureServices(services => { - services.AddSingleton(); - - // Example: If you want to change the configuration during startup of your function you can use the following code: - services.AddSingleton(x => - { - return new OpenApiConfigurationOptions() - { - Info = new Microsoft.OpenApi.Models.OpenApiInfo - { - Title = "A dynamic title generated at runtime", - Description = "Dynamic Open API information at runtime" - } - }; - }); - }) - .Build(); - - host.Run(); - } -``` ## Swagger UI Customisation ## diff --git a/docs/openapi-in-proc.md b/docs/openapi-in-proc.md index 42884a7c..7a74391f 100644 --- a/docs/openapi-in-proc.md +++ b/docs/openapi-in-proc.md @@ -31,3 +31,52 @@ For detailed getting started document, find this [Enable OpenAPI Endpoints on Az ## Advanced Configuration in General ## If you look for the advanced configurations in general, please find the document, [Advanced Configurations for OpenAPI Extension](./openapi.md) + + +### Injecting `OpenApiConfigurationOptions` during Startup ### + +You may want to inject the `OpenApiConfigurationOptions` instance during startup, through the `Startup.cs` class. Here's the example: + +```csharp +[assembly: FunctionsStartup(typeof(MyFunctionApp.Startup))] +namespace MyFunctionApp +{ + public class Startup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { + builder.Services.AddSingleton(_ => + { + var options = new OpenApiConfigurationOptions() + { + Info = new OpenApiInfo() + { + Version = "1.0.0", + Title = "Swagger Petstore", + Description = "This is a sample server Petstore API designed by [http://swagger.io](http://swagger.io).", + TermsOfService = new Uri("https://github.com/Azure/azure-functions-openapi-extension"), + Contact = new OpenApiContact() + { + Name = "Enquiry", + Email = "azfunc-openapi@microsoft.com", + Url = new Uri("https://github.com/Azure/azure-functions-openapi-extension/issues"), + }, + License = new OpenApiLicense() + { + Name = "MIT", + Url = new Uri("http://opensource.org/licenses/MIT"), + } + }, + Servers = DefaultOpenApiConfigurationOptions.GetHostNames(), + OpenApiVersion = OpenApiVersionType.V2, + IncludeRequestingHostName = true, + ForceHttps = false, + ForceHttp = false, + }; + + return options; + }); + } + } +} +``` diff --git a/docs/openapi-out-of-proc.md b/docs/openapi-out-of-proc.md index 66854e2b..98322f7d 100644 --- a/docs/openapi-out-of-proc.md +++ b/docs/openapi-out-of-proc.md @@ -25,3 +25,62 @@ For detailed getting started document, find this [Enable OpenAPI Endpoints on Az ## Advanced Configuration in General ## If you look for the advanced configurations in general, please find the document, [Advanced Configurations for OpenAPI Extension](./openapi.md) + + +### Injecting `OpenApiConfigurationOptions` during Startup ### + +You may want to inject the `OpenApiConfigurationOptions` instance during startup, through the `Program.cs` class. Here's the example: + +```csharp +namespace MyFunctionApp +{ + public class Program + { + public static void Main() + { + var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults(worker => worker.UseNewtonsoftJson()) + .ConfigureOpenApi() + /* ⬇️⬇️⬇️ Add this ⬇️⬇️⬇️ */ + .ConfigureServices(services => + { + services.AddSingleton(_ => + { + var options = new OpenApiConfigurationOptions() + { + Info = new OpenApiInfo() + { + Version = "1.0.0", + Title = "Swagger Petstore", + Description = "This is a sample server Petstore API designed by [http://swagger.io](http://swagger.io).", + TermsOfService = new Uri("https://github.com/Azure/azure-functions-openapi-extension"), + Contact = new OpenApiContact() + { + Name = "Enquiry", + Email = "azfunc-openapi@microsoft.com", + Url = new Uri("https://github.com/Azure/azure-functions-openapi-extension/issues"), + }, + License = new OpenApiLicense() + { + Name = "MIT", + Url = new Uri("http://opensource.org/licenses/MIT"), + } + }, + Servers = DefaultOpenApiConfigurationOptions.GetHostNames(), + OpenApiVersion = OpenApiVersionType.V2, + IncludeRequestingHostName = true, + ForceHttps = false, + ForceHttp = false, + }; + + return options; + }); + }) + /* ⬆️⬆️⬆️ Add this ⬆️⬆️⬆️ */ + .Build(); + + host.Run(); + } + } +} +``` diff --git a/docs/openapi.md b/docs/openapi.md index 63cf118a..ac407a99 100644 --- a/docs/openapi.md +++ b/docs/openapi.md @@ -27,7 +27,7 @@ As a default, all endpoints to render Swagger UI and OpenAPI documents have the } ``` -You can have granular controls to both Swagger UI and OpenAPI documents by setting the authorisation level to `Anonymous`, `User`, `Function`, `System` or `Admin`. Make sure that you MUST provide the `OpenApi__AuthKey` value, if you choose the `OpenApi__AuthLevel__Document` value other than `Anonymous`. Otherwise, it will throw an error. +You can have granular controls to both Swagger UI and OpenAPI documents by setting the authorisation level to `Anonymous`, `User`, `Function`, `System` or `Admin`. Make sure that you MUST provide the `OpenApi__ApiKey` value, if you choose the `OpenApi__AuthLevel__Document` value other than `Anonymous`. Otherwise, it will throw an error. > **NOTE**: Both Swagger UI and OpenAPI document pages are allowed `Anonymous` access by default. diff --git a/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Configurations/OpenApiConfigurationOptions.cs b/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Configurations/MyOpenApiConfigurationOptions.cs similarity index 84% rename from samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Configurations/OpenApiConfigurationOptions.cs rename to samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Configurations/MyOpenApiConfigurationOptions.cs index f94fc34a..af2e42b5 100644 --- a/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Configurations/OpenApiConfigurationOptions.cs +++ b/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Configurations/MyOpenApiConfigurationOptions.cs @@ -6,13 +6,13 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc.Configurations { - public class OpenApiConfigurationOptions : DefaultOpenApiConfigurationOptions + public class MyOpenApiConfigurationOptions : DefaultOpenApiConfigurationOptions { public override OpenApiInfo Info { get; set; } = new OpenApiInfo() { Version = GetOpenApiDocVersion(), Title = GetOpenApiDocTitle(), - Description = "This is a sample server Petstore API designed by [http://swagger.io](http://swagger.io).", + Description = GetOpenApiDocDescription(), TermsOfService = new Uri("https://github.com/Azure/azure-functions-openapi-extension"), Contact = new OpenApiContact() { diff --git a/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Program.cs b/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Program.cs index e747e9c6..16e53d7e 100644 --- a/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Program.cs +++ b/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/Program.cs @@ -1,40 +1,62 @@ -using AutoFixture; - -using Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Extensions; -using Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc.Configurations; +using System; + +using AutoFixture; + +using Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Extensions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc -{ - public class Program - { - public static void Main() - { - var host = new HostBuilder() - .ConfigureFunctionsWorkerDefaults(worker => worker.UseNewtonsoftJson()) - .ConfigureOpenApi() - .ConfigureServices(services => { - services.AddSingleton(); +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; + +namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc +{ + public class Program + { + public static void Main() + { + var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults(worker => worker.UseNewtonsoftJson()) + .ConfigureOpenApi() + .ConfigureServices(services => + { + services.AddSingleton() + .AddSingleton(_ => + { + var options = new OpenApiConfigurationOptions() + { + Info = new OpenApiInfo() + { + Version = DefaultOpenApiConfigurationOptions.GetOpenApiDocVersion(), + Title = $"{DefaultOpenApiConfigurationOptions.GetOpenApiDocTitle()} (Injected)", + Description = DefaultOpenApiConfigurationOptions.GetOpenApiDocDescription(), + TermsOfService = new Uri("https://github.com/Azure/azure-functions-openapi-extension"), + Contact = new OpenApiContact() + { + Name = "Enquiry", + Email = "azfunc-openapi@microsoft.com", + Url = new Uri("https://github.com/Azure/azure-functions-openapi-extension/issues"), + }, + License = new OpenApiLicense() + { + Name = "MIT", + Url = new Uri("http://opensource.org/licenses/MIT"), + } + }, + Servers = DefaultOpenApiConfigurationOptions.GetHostNames(), + OpenApiVersion = DefaultOpenApiConfigurationOptions.GetOpenApiVersion(), + IncludeRequestingHostName = DefaultOpenApiConfigurationOptions.IsFunctionsRuntimeEnvironmentDevelopment(), + ForceHttps = DefaultOpenApiConfigurationOptions.IsHttpsForced(), + ForceHttp = DefaultOpenApiConfigurationOptions.IsHttpForced(), + }; - // Example: If you want to change the configuration during startup of your function you can use the following code: + return options; + }) + ; + }) + .Build(); - //services.AddSingleton(x => - //{ - // return new OpenApiConfigurationOptions() - // { - // Info = new Microsoft.OpenApi.Models.OpenApiInfo - // { - // Title = "A dynamic title generated at runtime", - // Description = "Dynamic Open API information at runtime" - // } - // }; - //}); - }) - .Build(); - - host.Run(); - } - } -} + host.Run(); + } + } +} diff --git a/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/local.settings.json b/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/local.settings.json index 1be47491..57811471 100644 --- a/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/local.settings.json +++ b/samples/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.FunctionApp.OutOfProc/local.settings.json @@ -6,6 +6,7 @@ "OpenApi__Version": "v3", "OpenApi__DocVersion": "1.0.0", - "OpenApi__DocTitle": "Swagger Petstore" + "OpenApi__DocTitle": "Swagger Petstore", + "OpenApi__DocDescription": "This is a sample server Petstore API designed by [http://swagger.io](http://swagger.io)." } } diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Configurations/OpenApiConfigurationOptions.cs b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Configurations/MyOpenApiConfigurationOptions.cs similarity index 84% rename from samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Configurations/OpenApiConfigurationOptions.cs rename to samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Configurations/MyOpenApiConfigurationOptions.cs index 4a811a54..d1b72647 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Configurations/OpenApiConfigurationOptions.cs +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Configurations/MyOpenApiConfigurationOptions.cs @@ -1,17 +1,18 @@ using System; + using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; using Microsoft.OpenApi.Models; namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc.Configurations { - public class OpenApiConfigurationOptions : DefaultOpenApiConfigurationOptions + public class MyOpenApiConfigurationOptions : DefaultOpenApiConfigurationOptions { public override OpenApiInfo Info { get; set; } = new OpenApiInfo() { Version = GetOpenApiDocVersion(), Title = GetOpenApiDocTitle(), - Description = "This is a sample server Petstore API designed by [http://swagger.io](http://swagger.io).", + Description = GetOpenApiDocDescription(), TermsOfService = new Uri("https://github.com/Azure/azure-functions-openapi-extension"), Contact = new OpenApiContact() { diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Startup.cs b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Startup.cs index 116ec7f6..3a502e08 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Startup.cs +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/Startup.cs @@ -1,34 +1,54 @@ -using AutoFixture; - -using Microsoft.Azure.Functions.Extensions.DependencyInjection; +using System; + +using AutoFixture; + +using Microsoft.Azure.Functions.Extensions.DependencyInjection; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc.Configurations; -using Microsoft.Extensions.DependencyInjection; - -[assembly: FunctionsStartup(typeof(Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc.Startup))] - -namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc -{ - public class Startup : FunctionsStartup - { - public override void Configure(IFunctionsHostBuilder builder) - { - var fixture = new Fixture(); - builder.Services.AddSingleton(fixture); +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; + +[assembly: FunctionsStartup(typeof(Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc.Startup))] - // Example: If you want to change the configuration during startup of your function you can use the following code: +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc +{ + public class Startup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { + builder.Services.AddSingleton() + .AddSingleton(_ => + { + var options = new OpenApiConfigurationOptions() + { + Info = new OpenApiInfo() + { + Version = DefaultOpenApiConfigurationOptions.GetOpenApiDocVersion(), + Title = $"{DefaultOpenApiConfigurationOptions.GetOpenApiDocTitle()} (Injected)", + Description = DefaultOpenApiConfigurationOptions.GetOpenApiDocDescription(), + TermsOfService = new Uri("https://github.com/Azure/azure-functions-openapi-extension"), + Contact = new OpenApiContact() + { + Name = "Enquiry", + Email = "azfunc-openapi@microsoft.com", + Url = new Uri("https://github.com/Azure/azure-functions-openapi-extension/issues"), + }, + License = new OpenApiLicense() + { + Name = "MIT", + Url = new Uri("http://opensource.org/licenses/MIT"), + } + }, + Servers = DefaultOpenApiConfigurationOptions.GetHostNames(), + OpenApiVersion = DefaultOpenApiConfigurationOptions.GetOpenApiVersion(), + IncludeRequestingHostName = DefaultOpenApiConfigurationOptions.IsFunctionsRuntimeEnvironmentDevelopment(), + ForceHttps = DefaultOpenApiConfigurationOptions.IsHttpsForced(), + ForceHttp = DefaultOpenApiConfigurationOptions.IsHttpForced(), + }; - //builder.Services.AddSingleton((_) => - //{ - // return new OpenApiConfigurationOptions() - // { - // Info = new Microsoft.OpenApi.Models.OpenApiInfo - // { - // Title = "A dynamic title generated at runtime", - // Description = "Dynamic Open API information at runtime" - // } - // }; - //}); - } - } -} + return options; + }) + ; + } + } +} diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/local.settings.json b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/local.settings.json index 172eacad..29a0f97c 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/local.settings.json +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc/local.settings.json @@ -6,6 +6,7 @@ "OpenApi__Version": "v3", "OpenApi__DocVersion": "1.0.0", - "OpenApi__DocTitle": "Swagger Petstore" + "OpenApi__DocTitle": "Swagger Petstore", + "OpenApi__DocDescription": "This is a sample server Petstore API designed by [http://swagger.io](http://swagger.io)." } } diff --git a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs index 9ba29f5f..12706313 100644 --- a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs +++ b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs @@ -40,6 +40,7 @@ public class OpenApiHttpTriggerContext : IOpenApiHttpTriggerContext /// /// Initializes a new instance of the class. /// + /// instance. public OpenApiHttpTriggerContext(IOpenApiConfigurationOptions configOptions = null) { this._configOptions = configOptions; diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.CLI/Microsoft.Azure.WebJobs.Extensions.OpenApi.CLI.csproj b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.CLI/Microsoft.Azure.WebJobs.Extensions.OpenApi.CLI.csproj index ce1828bd..259567a9 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.CLI/Microsoft.Azure.WebJobs.Extensions.OpenApi.CLI.csproj +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.CLI/Microsoft.Azure.WebJobs.Extensions.OpenApi.CLI.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/OpenApiHttpTriggerContextBinding.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/OpenApiHttpTriggerContextBindingMetadata.cs similarity index 79% rename from src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/OpenApiHttpTriggerContextBinding.cs rename to src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/OpenApiHttpTriggerContextBindingMetadata.cs index 887fdba8..da5e6a0b 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/OpenApiHttpTriggerContextBinding.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/OpenApiHttpTriggerContextBindingMetadata.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; - -using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; using Newtonsoft.Json; @@ -8,16 +5,16 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions { /// - /// This represents the metadata entity for OpenApiHttpTrigger context binding. + /// This represents the metadata entity for OpenAPI HTTP Trigger context binding. /// public class OpenApiHttpTriggerContextBindingMetadata { /// - /// Gets or sets the name of the binding parameter. Default value is context. + /// Gets or sets the name of the binding parameter. Default value is openApiContext. /// [JsonRequired] [JsonProperty("name")] - public virtual string Name { get; set; } = "context"; + public virtual string Name { get; set; } = "openApiContext"; /// /// Gets or sets the binding type. Default value is openApiHttpTriggerContext. diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Attributes/OpenApiConfigurationOptionsIgnoreAttribute.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Attributes/OpenApiConfigurationOptionsIgnoreAttribute.cs new file mode 100644 index 00000000..77e6e012 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Attributes/OpenApiConfigurationOptionsIgnoreAttribute.cs @@ -0,0 +1,14 @@ +using System; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes +{ + /// + /// This represents the attribute entity for to be excluded from auto-loading. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class OpenApiConfigurationOptionsIgnoreAttribute : Attribute + { + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiConfigurationOptions.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiConfigurationOptions.cs index deb3bfb5..15d1249c 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiConfigurationOptions.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiConfigurationOptions.cs @@ -3,6 +3,7 @@ using System.Linq; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; using Microsoft.OpenApi.Models; @@ -12,10 +13,12 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations /// /// This represents the options entity for OpenAPI metadata configuration. /// - public class DefaultOpenApiConfigurationOptions : IOpenApiConfigurationOptions + [OpenApiConfigurationOptionsIgnore] + public class DefaultOpenApiConfigurationOptions : OpenApiConfigurationOptions { private const string OpenApiDocVersionKey = "OpenApi__DocVersion"; private const string OpenApiDocTitleKey = "OpenApi__DocTitle"; + private const string OpenApiDocDescriptionKey = "OpenApi__DocDescription"; private const string OpenApiHostNamesKey = "OpenApi__HostNames"; private const string OpenApiVersionKey = "OpenApi__Version"; private const string FunctionsRuntimeEnvironmentKey = "AZURE_FUNCTIONS_ENVIRONMENT"; @@ -23,35 +26,36 @@ public class DefaultOpenApiConfigurationOptions : IOpenApiConfigurationOptions private const string ForceHttpsKey = "OpenApi__ForceHttps"; /// - public virtual OpenApiInfo Info { get; set; } = new OpenApiInfo() + public override OpenApiInfo Info { get; set; } = new OpenApiInfo() { Version = GetOpenApiDocVersion(), Title = GetOpenApiDocTitle(), + Description = GetOpenApiDocDescription(), }; /// - public virtual List Servers { get; set; } = GetHostNames(); + public override List Servers { get; set; } = GetHostNames(); /// - public virtual OpenApiVersionType OpenApiVersion { get; set; } = GetOpenApiVersion(); + public override OpenApiVersionType OpenApiVersion { get; set; } = GetOpenApiVersion(); /// - public virtual bool IncludeRequestingHostName { get; set; } = IsFunctionsRuntimeEnvironmentDevelopment(); + public override bool IncludeRequestingHostName { get; set; } = IsFunctionsRuntimeEnvironmentDevelopment(); /// - public virtual bool ForceHttp { get; set; } = IsHttpForced(); + public override bool ForceHttp { get; set; } = IsHttpForced(); /// - public virtual bool ForceHttps { get; set; } = IsHttpsForced(); + public override bool ForceHttps { get; set; } = IsHttpsForced(); /// - public virtual List DocumentFilters { get; set; } = new List(); + public override List DocumentFilters { get; set; } = new List(); /// /// Gets the OpenAPI document version. /// /// Returns the OpenAPI document version. - protected static string GetOpenApiDocVersion() + public static string GetOpenApiDocVersion() { var version = Environment.GetEnvironmentVariable(OpenApiDocVersionKey) ?? DefaultOpenApiDocVersion(); @@ -62,18 +66,29 @@ protected static string GetOpenApiDocVersion() /// Gets the OpenAPI document title. /// /// Returns the OpenAPI document title. - protected static string GetOpenApiDocTitle() + public static string GetOpenApiDocTitle() { var title = Environment.GetEnvironmentVariable(OpenApiDocTitleKey) ?? DefaultOpenApiDocTitle(); return title; } + /// + /// Gets the OpenAPI document description. + /// + /// Returns the OpenAPI document description. + public static string GetOpenApiDocDescription() + { + var description = Environment.GetEnvironmentVariable(OpenApiDocDescriptionKey) ?? DefaultOpenApiDocDescription(); + + return description; + } + /// /// Gets the list of hostnames. /// /// Returns the list of hostnames. - protected static List GetHostNames() + public static List GetHostNames() { var servers = new List(); var collection = Environment.GetEnvironmentVariable(OpenApiHostNamesKey); @@ -95,7 +110,7 @@ protected static List GetHostNames() /// Gets the OpenAPI version. /// /// Returns the OpenAPI version. - protected static OpenApiVersionType GetOpenApiVersion() + public static OpenApiVersionType GetOpenApiVersion() { var version = Enum.TryParse( Environment.GetEnvironmentVariable(OpenApiVersionKey), ignoreCase: true, out var result) @@ -109,7 +124,7 @@ protected static OpenApiVersionType GetOpenApiVersion() /// Checks whether the current Azure Functions runtime environment is "Development" or not. /// /// Returns True, if the current Azure Functions runtime environment is "Development"; otherwise returns False - protected static bool IsFunctionsRuntimeEnvironmentDevelopment() + public static bool IsFunctionsRuntimeEnvironmentDevelopment() { var development = Environment.GetEnvironmentVariable(FunctionsRuntimeEnvironmentKey) == "Development"; @@ -120,7 +135,7 @@ protected static bool IsFunctionsRuntimeEnvironmentDevelopment() /// Checks whether HTTP is forced or not. /// /// Returns True, if HTTP is forced; otherwise returns False. - protected static bool IsHttpForced() + public static bool IsHttpForced() { var development = bool.TryParse(Environment.GetEnvironmentVariable(ForceHttpKey), out var result) ? result : false; @@ -131,7 +146,7 @@ protected static bool IsHttpForced() /// Checks whether HTTPS is forced or not. /// /// Returns True, if HTTPS is forced; otherwise returns False. - protected static bool IsHttpsForced() + public static bool IsHttpsForced() { var development = bool.TryParse(Environment.GetEnvironmentVariable(ForceHttpsKey), out var result) ? result : false; @@ -152,5 +167,10 @@ private static string DefaultOpenApiDocTitle() { return "OpenAPI Document on Azure Functions"; } + + private static string DefaultOpenApiDocDescription() + { + return "This is the OpenAPI Document on Azure Functions"; + } } } diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/OpenApiConfigurationOptions.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/OpenApiConfigurationOptions.cs new file mode 100644 index 00000000..da26fd20 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/OpenApiConfigurationOptions.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; +using Microsoft.OpenApi.Models; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations +{ + /// + /// This represents the options entity for OpenAPI metadata configuration. + /// + [OpenApiConfigurationOptionsIgnore] + public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions + { + /// + public virtual OpenApiInfo Info { get; set; } = new OpenApiInfo(); + + /// + public virtual List Servers { get; set; } = new List(); + + /// + public virtual OpenApiVersionType OpenApiVersion { get; set; } + + /// + public virtual bool IncludeRequestingHostName { get; set; } + + /// + public virtual bool ForceHttp { get; set; } + + /// + public virtual bool ForceHttps { get; set; } + + /// + public virtual List DocumentFilters { get; set; } = new List(); + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Resolvers/OpenApiConfigurationResolver.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Resolvers/OpenApiConfigurationResolver.cs index a47ec67f..dd49319c 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Resolvers/OpenApiConfigurationResolver.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Resolvers/OpenApiConfigurationResolver.cs @@ -1,39 +1,40 @@ -using System; -using System.Linq; +using System; +using System.Linq; using System.Reflection; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; -using Microsoft.OpenApi.Models; - -namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Resolvers -{ - /// - /// This represents the resolver entity for . - /// - public static class OpenApiConfigurationResolver + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; +using Microsoft.OpenApi.Models; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Resolvers +{ + /// + /// This represents the resolver entity for . + /// + public static class OpenApiConfigurationResolver { - /// - /// Gets the instance from the given assembly. - /// - /// The executing assembly instance. - /// Returns the instance resolved. - public static IOpenApiConfigurationOptions Resolve(Assembly assembly) - { - var type = assembly.GetLoadableTypes() - .SingleOrDefault(p => p.GetInterface("IOpenApiConfigurationOptions", ignoreCase: true).IsNullOrDefault() == false - && p.IsAbstract == false - && p.GetCustomAttribute(inherit: false).IsNullOrDefault() == true); - if (type.IsNullOrDefault()) - { - var settings = new DefaultOpenApiConfigurationOptions(); - - return settings; + /// + /// Gets the instance from the given assembly. + /// + /// The executing assembly instance. + /// Returns the instance resolved. + public static IOpenApiConfigurationOptions Resolve(Assembly assembly) + { + var type = assembly.GetLoadableTypes() + .SingleOrDefault(p => p.GetInterface("IOpenApiConfigurationOptions", ignoreCase: true).IsNullOrDefault() == false + && p.IsAbstract == false + && p.GetCustomAttribute(inherit: false).IsNullOrDefault() == true + && p.GetCustomAttribute(inherit: false).IsNullOrDefault() == true); + if (type.IsNullOrDefault()) + { + return new DefaultOpenApiConfigurationOptions(); } - - var options = Activator.CreateInstance(type); - - return options as IOpenApiConfigurationOptions; - } - } -} + + var options = Activator.CreateInstance(type); + + return options as IOpenApiConfigurationOptions; + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Attributes/OpenApiHttpTriggerContextAttribute.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Attributes/OpenApiHttpTriggerContextAttribute.cs new file mode 100644 index 00000000..3651b343 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Attributes/OpenApiHttpTriggerContextAttribute.cs @@ -0,0 +1,15 @@ +using System; + +using Microsoft.Azure.WebJobs.Description; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Attributes +{ + /// + /// This represents the parameter binding attribute that injects the instance to each OpenAPI HTTP trigger endpoint. + /// + [Binding] + [AttributeUsage(AttributeTargets.Parameter)] + public class OpenApiHttpTriggerContextAttribute : Attribute + { + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Attributes/OpenApiHttpTriggerContextBinding.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Attributes/OpenApiHttpTriggerContextBinding.cs new file mode 100644 index 00000000..ceca104f --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Attributes/OpenApiHttpTriggerContextBinding.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Description; +using Microsoft.Azure.WebJobs.Host.Bindings; +using Microsoft.Azure.WebJobs.Host.Config; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Attributes +{ + /// + /// This represents the extension attribute that registers the to inject the instance. + /// + [Extension(nameof(OpenApiHttpTriggerContextBinding))] + public class OpenApiHttpTriggerContextBinding : IExtensionConfigProvider + { + /// + public void Initialize(ExtensionConfigContext context) + { + var rule = context.AddBindingRule(); + rule.BindToInput((attr, vbContext) => + { + var httpContext = vbContext.FunctionContext.CreateObjectInstance(); + + return Task.FromResult(httpContext); + }); + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiSettings.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiSettings.cs index 6bb55f50..2648f46c 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiSettings.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiSettings.cs @@ -1,3 +1,5 @@ +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; + namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations { /// @@ -5,13 +7,48 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations /// public class OpenApiSettings { + /// + /// Gets or sets the OpenAPI spec version. + /// + public virtual OpenApiVersionType Version { get; set; } + + /// + /// Gets or sets the OpenAPI document version. + /// + public virtual string DocVersion { get; set; } + + /// + /// Gets or sets the OpenAPI document title. + /// + public virtual string DocTitle { get; set; } + + /// + /// Gets or sets the OpenAPI document description. + /// + public virtual string DocDescription { get; set; } + + /// + /// Gets or sets the comma delimited host names. + /// + public virtual string HostNames { get; set; } + + /// + /// Gets or sets the value indicating whether to force the HTTPS connection or not. + /// + public virtual bool ForceHttps { get; set; } + + /// + /// Gets or sets the value indicating whether to force the HTTP connection or not. + /// + public virtual bool ForceHttp { get; set; } + /// /// Gets or sets the value indicating whether to hide the Swagger UI page or not. /// public virtual bool HideSwaggerUI { get; set; } /// - /// Gets or sets the value indicating whether to hide the document pages or not. + /// Gets or sets the value indicating whether to hide the OpenAPI document pages or not. /// public virtual bool HideDocument { get; set; } @@ -23,16 +60,11 @@ public class OpenApiSettings /// /// Gets or sets the object. /// - public virtual OpenApiAuthLevelSettings AuthLevel { get; set; } + public virtual OpenApiAuthLevelSettings AuthLevel { get; set; } = new OpenApiAuthLevelSettings(); /// /// Gets or sets the backend URL for Azure Functions Proxy. /// public virtual string BackendProxyUrl { get; set; } - - /// - /// Gets or sets the comma delimited host names. - /// - public virtual string HostNames { get; set; } } } diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs index b9fc3179..aed8a096 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs @@ -1,332 +1,333 @@ -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.OpenApi.Core; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Filters; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Resolvers; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors; -using Microsoft.OpenApi; - -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace Microsoft.Azure.WebJobs.Extensions.OpenApi -{ - /// - /// This represents the context entity for . - /// - [SuppressMessage("Design", "CA1823", Justification = "")] - [SuppressMessage("Design", "MEN002", Justification = "")] - [SuppressMessage("Design", "SA1206", Justification = "")] - [SuppressMessage("Layout Rules", "SA1311", Justification = "")] - [SuppressMessage("Layout Rules", "SA1500", Justification = "")] - [SuppressMessage("Readability Rules", "SX1101", Justification = "")] - public class OpenApiHttpTriggerContext : IOpenApiHttpTriggerContext - { - private string _dllpath; - private Assembly _appAssembly; - private IOpenApiConfigurationOptions _configOptions; - private IOpenApiCustomUIOptions _uiOptions; - - /// - /// Initializes a new instance of the class. - /// - public OpenApiHttpTriggerContext(IOpenApiConfigurationOptions configOptions = null) +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.OpenApi.Core; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Filters; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Resolvers; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors; +using Microsoft.OpenApi; + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi +{ + /// + /// This represents the context entity for . + /// + [SuppressMessage("Design", "CA1823", Justification = "")] + [SuppressMessage("Design", "MEN002", Justification = "")] + [SuppressMessage("Design", "SA1206", Justification = "")] + [SuppressMessage("Layout Rules", "SA1311", Justification = "")] + [SuppressMessage("Layout Rules", "SA1500", Justification = "")] + [SuppressMessage("Readability Rules", "SX1101", Justification = "")] + public class OpenApiHttpTriggerContext : IOpenApiHttpTriggerContext + { + private string _dllpath; + private Assembly _appAssembly; + private IOpenApiConfigurationOptions _configOptions; + private IOpenApiCustomUIOptions _uiOptions; + + /// + /// Initializes a new instance of the class. + /// + /// instance. + public OpenApiHttpTriggerContext(IOpenApiConfigurationOptions configOptions = null) { this._configOptions = configOptions; - this.PackageAssembly = this.GetAssembly(); - - var host = HostJsonResolver.Resolve(); - this.HttpSettings = host.GetHttpSettings(); - - var filter = new RouteConstraintFilter(); - var acceptor = new OpenApiSchemaAcceptor(); - var helper = new DocumentHelper(filter, acceptor); - - this.Document = new Document(helper); - this.SwaggerUI = new SwaggerUI(); - } - - /// - public virtual Assembly ApplicationAssembly - { - get - { - if (this._appAssembly.IsNullOrDefault()) - { - this._appAssembly = this.GetAssembly(this._dllpath); - } - - return this._appAssembly; - } - } - - /// - public virtual Assembly PackageAssembly { get; } - - /// - public virtual IOpenApiConfigurationOptions OpenApiConfigurationOptions - { - get - { - if (this._configOptions.IsNullOrDefault()) - { - this._configOptions = OpenApiConfigurationResolver.Resolve(this.ApplicationAssembly); - } - - return this._configOptions; - } - } - - /// - public virtual IOpenApiCustomUIOptions OpenApiCustomUIOptions - { - get - { - if (this._uiOptions.IsNullOrDefault()) - { - this._uiOptions = OpenApiCustomUIResolver.Resolve(this.ApplicationAssembly); - } - - return this._uiOptions; - } - } - - /// - public virtual HttpSettings HttpSettings { get; } - - /// - public virtual IDocument Document { get; } - - /// - public virtual ISwaggerUI SwaggerUI { get; } - - /// - public virtual NamingStrategy NamingStrategy { get; } = new CamelCaseNamingStrategy(); - - /// - public virtual bool IsDevelopment { get; } = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"; - - /// - [Obsolete("This method is obsolete. Use GetAssembly() or GetAssembly(object) instead", error: true)] - public virtual Assembly GetExecutingAssembly() - { - var assembly = Assembly.GetExecutingAssembly(); - - return assembly; - } - - /// - public virtual async Task SetApplicationAssemblyAsync(string functionAppDirectory, bool appendBin = true) - { - if (!this._dllpath.IsNullOrWhiteSpace()) - { - return this; - } - - var runtimepath = this.GetRuntimePath(functionAppDirectory, appendBin); - var runtimename = await this.GetRuntimeFilenameAsync(functionAppDirectory); - var dllpath = $"{runtimepath}{Path.DirectorySeparatorChar}{runtimename}"; - - this._dllpath = dllpath; - - return this; - } - - /// - public virtual async Task AuthorizeAsync(IHttpRequestDataObject req) - { - var result = default(OpenApiAuthorizationResult); - var type = this.ApplicationAssembly - .GetLoadableTypes() - .SingleOrDefault(p => p.HasInterface()); - if (type.IsNullOrDefault()) - { - return result; - } - - var auth = Activator.CreateInstance(type) as IOpenApiHttpTriggerAuthorization; - result = await auth.AuthorizeAsync(req).ConfigureAwait(false); - - return result; - } - - /// - public virtual VisitorCollection GetVisitorCollection() - { - var collection = VisitorCollection.CreateInstance(); - - return collection; - } - - /// - public virtual OpenApiVersionType GetOpenApiVersionType(string version = "v2") - { - var parsed = Enum.TryParse(version, true, out OpenApiVersionType output) - ? output - : throw new InvalidOperationException("Invalid OpenAPI version"); - - return parsed; - } - - /// - public virtual OpenApiSpecVersion GetOpenApiSpecVersion(string version = "v2") - { - var parsed = Enum.TryParse(version, true, out OpenApiVersionType output) - ? output - : throw new InvalidOperationException("Invalid OpenAPI version"); - - return this.GetOpenApiSpecVersion(parsed); - } - - /// - public virtual OpenApiSpecVersion GetOpenApiSpecVersion(OpenApiVersionType version = OpenApiVersionType.V2) - { - return version.ToOpenApiSpecVersion(); - } - - /// - public virtual OpenApiFormat GetOpenApiFormat(string format = "json") - { - if (format.Equals("yml", StringComparison.InvariantCultureIgnoreCase)) - { - format = "yaml"; - } - - var parsed = Enum.TryParse(format, true, out OpenApiFormatType output) - ? output - : throw new InvalidOperationException("Invalid OpenAPI format"); - - return this.GetOpenApiFormat(parsed); - } - - /// - public virtual OpenApiFormat GetOpenApiFormat(OpenApiFormatType format = OpenApiFormatType.Json) - { - return format.ToOpenApiFormat(); - } - - /// - public virtual OpenApiAuthLevelType GetDocumentAuthLevel(string key = "OpenApi__AuthLevel__Document") - { - var value = Environment.GetEnvironmentVariable(key); - var parsed = Enum.TryParse(value, out var result) ? result : OpenApiAuthLevelType.Anonymous; - - return parsed; - } - - /// - public virtual OpenApiAuthLevelType GetUIAuthLevel(string key = "OpenApi__AuthLevel__UI") - { - var value = Environment.GetEnvironmentVariable(key); - var parsed = Enum.TryParse(value, out var result) ? result : OpenApiAuthLevelType.Anonymous; - - return parsed; - } - - /// - public virtual string GetSwaggerAuthKey(string key = "OpenApi__ApiKey") - { - var value = Environment.GetEnvironmentVariable(key); - - return value ?? string.Empty; - } - - /// - public virtual DocumentFilterCollection GetDocumentFilterCollection() - { - var collection = new DocumentFilterCollection(this.OpenApiConfigurationOptions.DocumentFilters); - - return collection; - } - - private string GetRuntimePath(string functionAppDirectory, bool appendBin) - { - var path = functionAppDirectory; - if (appendBin) - { - path += $"{Path.DirectorySeparatorChar}bin"; - } - - return path; - } - - // **NOTE**: - // 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); - 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()) - .Where(manifest => manifest.Value.Dependencies != null) - .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(); - } - - return JsonConvert.DeserializeObject(serialised); - } - - private Assembly GetAssembly(object instance) - { - return this.GetAssembly(instance.GetType()); - } - - private Assembly GetAssembly() - { - return this.GetAssembly(typeof(T)); - } - - private Assembly GetAssembly(Type type) - { - var assembly = type.Assembly; - - return assembly; - } - - private Assembly GetAssembly(string dllpath) - { - var assembly = Assembly.LoadFile(dllpath); - - return assembly; - } - } -} + this.PackageAssembly = this.GetAssembly(); + + var host = HostJsonResolver.Resolve(); + this.HttpSettings = host.GetHttpSettings(); + + var filter = new RouteConstraintFilter(); + var acceptor = new OpenApiSchemaAcceptor(); + var helper = new DocumentHelper(filter, acceptor); + + this.Document = new Document(helper); + this.SwaggerUI = new SwaggerUI(); + } + + /// + public virtual Assembly ApplicationAssembly + { + get + { + if (this._appAssembly.IsNullOrDefault()) + { + this._appAssembly = this.GetAssembly(this._dllpath); + } + + return this._appAssembly; + } + } + + /// + public virtual Assembly PackageAssembly { get; } + + /// + public virtual IOpenApiConfigurationOptions OpenApiConfigurationOptions + { + get + { + if (this._configOptions.IsNullOrDefault()) + { + this._configOptions = OpenApiConfigurationResolver.Resolve(this.ApplicationAssembly); + } + + return this._configOptions; + } + } + + /// + public virtual IOpenApiCustomUIOptions OpenApiCustomUIOptions + { + get + { + if (this._uiOptions.IsNullOrDefault()) + { + this._uiOptions = OpenApiCustomUIResolver.Resolve(this.ApplicationAssembly); + } + + return this._uiOptions; + } + } + + /// + public virtual HttpSettings HttpSettings { get; } + + /// + public virtual IDocument Document { get; } + + /// + public virtual ISwaggerUI SwaggerUI { get; } + + /// + public virtual NamingStrategy NamingStrategy { get; } = new CamelCaseNamingStrategy(); + + /// + public virtual bool IsDevelopment { get; } = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"; + + /// + [Obsolete("This method is obsolete. Use GetAssembly() or GetAssembly(object) instead", error: true)] + public virtual Assembly GetExecutingAssembly() + { + var assembly = Assembly.GetExecutingAssembly(); + + return assembly; + } + + /// + public virtual async Task SetApplicationAssemblyAsync(string functionAppDirectory, bool appendBin = true) + { + if (!this._dllpath.IsNullOrWhiteSpace()) + { + return this; + } + + var runtimepath = this.GetRuntimePath(functionAppDirectory, appendBin); + var runtimename = await this.GetRuntimeFilenameAsync(functionAppDirectory); + var dllpath = $"{runtimepath}{Path.DirectorySeparatorChar}{runtimename}"; + + this._dllpath = dllpath; + + return this; + } + + /// + public virtual async Task AuthorizeAsync(IHttpRequestDataObject req) + { + var result = default(OpenApiAuthorizationResult); + var type = this.ApplicationAssembly + .GetLoadableTypes() + .SingleOrDefault(p => p.HasInterface()); + if (type.IsNullOrDefault()) + { + return result; + } + + var auth = Activator.CreateInstance(type) as IOpenApiHttpTriggerAuthorization; + result = await auth.AuthorizeAsync(req).ConfigureAwait(false); + + return result; + } + + /// + public virtual VisitorCollection GetVisitorCollection() + { + var collection = VisitorCollection.CreateInstance(); + + return collection; + } + + /// + public virtual OpenApiVersionType GetOpenApiVersionType(string version = "v2") + { + var parsed = Enum.TryParse(version, true, out OpenApiVersionType output) + ? output + : throw new InvalidOperationException("Invalid OpenAPI version"); + + return parsed; + } + + /// + public virtual OpenApiSpecVersion GetOpenApiSpecVersion(string version = "v2") + { + var parsed = Enum.TryParse(version, true, out OpenApiVersionType output) + ? output + : throw new InvalidOperationException("Invalid OpenAPI version"); + + return this.GetOpenApiSpecVersion(parsed); + } + + /// + public virtual OpenApiSpecVersion GetOpenApiSpecVersion(OpenApiVersionType version = OpenApiVersionType.V2) + { + return version.ToOpenApiSpecVersion(); + } + + /// + public virtual OpenApiFormat GetOpenApiFormat(string format = "json") + { + if (format.Equals("yml", StringComparison.InvariantCultureIgnoreCase)) + { + format = "yaml"; + } + + var parsed = Enum.TryParse(format, true, out OpenApiFormatType output) + ? output + : throw new InvalidOperationException("Invalid OpenAPI format"); + + return this.GetOpenApiFormat(parsed); + } + + /// + public virtual OpenApiFormat GetOpenApiFormat(OpenApiFormatType format = OpenApiFormatType.Json) + { + return format.ToOpenApiFormat(); + } + + /// + public virtual OpenApiAuthLevelType GetDocumentAuthLevel(string key = "OpenApi__AuthLevel__Document") + { + var value = Environment.GetEnvironmentVariable(key); + var parsed = Enum.TryParse(value, out var result) ? result : OpenApiAuthLevelType.Anonymous; + + return parsed; + } + + /// + public virtual OpenApiAuthLevelType GetUIAuthLevel(string key = "OpenApi__AuthLevel__UI") + { + var value = Environment.GetEnvironmentVariable(key); + var parsed = Enum.TryParse(value, out var result) ? result : OpenApiAuthLevelType.Anonymous; + + return parsed; + } + + /// + public virtual string GetSwaggerAuthKey(string key = "OpenApi__ApiKey") + { + var value = Environment.GetEnvironmentVariable(key); + + return value ?? string.Empty; + } + + /// + public virtual DocumentFilterCollection GetDocumentFilterCollection() + { + var collection = new DocumentFilterCollection(this.OpenApiConfigurationOptions.DocumentFilters); + + return collection; + } + + private string GetRuntimePath(string functionAppDirectory, bool appendBin) + { + var path = functionAppDirectory; + if (appendBin) + { + path += $"{Path.DirectorySeparatorChar}bin"; + } + + return path; + } + + // **NOTE**: + // 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); + 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()) + .Where(manifest => manifest.Value.Dependencies != null) + .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(); + } + + return JsonConvert.DeserializeObject(serialised); + } + + private Assembly GetAssembly(object instance) + { + return this.GetAssembly(instance.GetType()); + } + + private Assembly GetAssembly() + { + return this.GetAssembly(typeof(T)); + } + + private Assembly GetAssembly(Type type) + { + var assembly = type.Assembly; + + return assembly; + } + + private Assembly GetAssembly(string dllpath) + { + var assembly = Assembly.LoadFile(dllpath); + + return assembly; + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs index 36b1cc09..a727c5e7 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Attributes; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; using Microsoft.Extensions.Logging; @@ -23,13 +24,16 @@ public class OpenApiTriggerFunctions /// /// Invokes the HTTP trigger endpoint to get OpenAPI document. /// + /// instance. /// instance. /// File extension representing the document format. This MUST be either "json" or "yaml". /// instance. /// instance. /// OpenAPI document in a format of either JSON or YAML. [OpenApiIgnore] - public static async Task RenderSwaggerDocument([OpenApiHttpTriggerContext] OpenApiHttpTriggerContext context, HttpRequest req, string extension, ExecutionContext ctx, ILogger log) + public static async Task RenderSwaggerDocument( + [OpenApiHttpTriggerContext] OpenApiHttpTriggerContext openApiContext, + HttpRequest req, string extension, ExecutionContext ctx, ILogger log) { log.LogInformation($"swagger.{extension} was requested."); @@ -38,7 +42,7 @@ public static async Task RenderSwaggerDocument([OpenApiHttpTrigge var content = default(ContentResult); try { - var auth = await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) + var auth = await openApiContext.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) .AuthorizeAsync(request) .ConfigureAwait(false); if (!auth.IsNullOrDefault()) @@ -53,21 +57,21 @@ public static async Task RenderSwaggerDocument([OpenApiHttpTrigge return content; } - result = await context.Document + result = await openApiContext.Document .InitialiseDocument() - .AddMetadata(context.OpenApiConfigurationOptions.Info) - .AddServer(request, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) - .AddNamingStrategy(context.NamingStrategy) - .AddVisitors(context.GetVisitorCollection()) - .Build(context.ApplicationAssembly, context.OpenApiConfigurationOptions.OpenApiVersion) - .ApplyDocumentFilters(context.GetDocumentFilterCollection()) - .RenderAsync(context.GetOpenApiSpecVersion(context.OpenApiConfigurationOptions.OpenApiVersion), context.GetOpenApiFormat(extension)) + .AddMetadata(openApiContext.OpenApiConfigurationOptions.Info) + .AddServer(request, openApiContext.HttpSettings.RoutePrefix, openApiContext.OpenApiConfigurationOptions) + .AddNamingStrategy(openApiContext.NamingStrategy) + .AddVisitors(openApiContext.GetVisitorCollection()) + .Build(openApiContext.ApplicationAssembly, openApiContext.OpenApiConfigurationOptions.OpenApiVersion) + .ApplyDocumentFilters(openApiContext.GetDocumentFilterCollection()) + .RenderAsync(openApiContext.GetOpenApiSpecVersion(openApiContext.OpenApiConfigurationOptions.OpenApiVersion), openApiContext.GetOpenApiFormat(extension)) .ConfigureAwait(false); content = new ContentResult() { Content = result, - ContentType = context.GetOpenApiFormat(extension).GetContentType(), + ContentType = openApiContext.GetOpenApiFormat(extension).GetContentType(), StatusCode = (int)HttpStatusCode.OK, }; } @@ -76,7 +80,7 @@ public static async Task RenderSwaggerDocument([OpenApiHttpTrigge log.LogError(ex.Message); result = ex.Message; - if (context.IsDevelopment) + if (openApiContext.IsDevelopment) { result += "\r\n\r\n"; result += ex.StackTrace; @@ -95,6 +99,7 @@ public static async Task RenderSwaggerDocument([OpenApiHttpTrigge /// /// Invokes the HTTP trigger endpoint to get OpenAPI document. /// + /// instance. /// instance. /// OpenAPI document spec version. This MUST be either "v2" or "v3". /// File extension representing the document format. This MUST be either "json" or "yaml". @@ -102,7 +107,9 @@ public static async Task RenderSwaggerDocument([OpenApiHttpTrigge /// instance. /// OpenAPI document in a format of either JSON or YAML. [OpenApiIgnore] - public static async Task RenderOpenApiDocument([OpenApiHttpTriggerContext] OpenApiHttpTriggerContext context, HttpRequest req, string version, string extension, ExecutionContext ctx, ILogger log) + public static async Task RenderOpenApiDocument( + [OpenApiHttpTriggerContext] OpenApiHttpTriggerContext openApiContext, + HttpRequest req, string version, string extension, ExecutionContext ctx, ILogger log) { log.LogInformation($"{version}.{extension} was requested."); @@ -111,7 +118,7 @@ public static async Task RenderOpenApiDocument([OpenApiHttpTrigge var content = default(ContentResult); try { - var auth = await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) + var auth = await openApiContext.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) .AuthorizeAsync(request) .ConfigureAwait(false); if (!auth.IsNullOrDefault()) @@ -126,21 +133,21 @@ public static async Task RenderOpenApiDocument([OpenApiHttpTrigge return content; } - result = await context.Document + result = await openApiContext.Document .InitialiseDocument() - .AddMetadata(context.OpenApiConfigurationOptions.Info) - .AddServer(new HttpRequestObject(req), context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) - .AddNamingStrategy(context.NamingStrategy) - .AddVisitors(context.GetVisitorCollection()) - .Build(context.ApplicationAssembly, context.GetOpenApiVersionType(version)) - .ApplyDocumentFilters(context.GetDocumentFilterCollection()) - .RenderAsync(context.GetOpenApiSpecVersion(version), context.GetOpenApiFormat(extension)) + .AddMetadata(openApiContext.OpenApiConfigurationOptions.Info) + .AddServer(new HttpRequestObject(req), openApiContext.HttpSettings.RoutePrefix, openApiContext.OpenApiConfigurationOptions) + .AddNamingStrategy(openApiContext.NamingStrategy) + .AddVisitors(openApiContext.GetVisitorCollection()) + .Build(openApiContext.ApplicationAssembly, openApiContext.GetOpenApiVersionType(version)) + .ApplyDocumentFilters(openApiContext.GetDocumentFilterCollection()) + .RenderAsync(openApiContext.GetOpenApiSpecVersion(version), openApiContext.GetOpenApiFormat(extension)) .ConfigureAwait(false); content = new ContentResult() { Content = result, - ContentType = context.GetOpenApiFormat(extension).GetContentType(), + ContentType = openApiContext.GetOpenApiFormat(extension).GetContentType(), StatusCode = (int)HttpStatusCode.OK, }; } @@ -149,7 +156,7 @@ public static async Task RenderOpenApiDocument([OpenApiHttpTrigge log.LogError(ex.Message); result = ex.Message; - if (context.IsDevelopment) + if (openApiContext.IsDevelopment) { result += "\r\n\r\n"; result += ex.StackTrace; @@ -168,12 +175,15 @@ public static async Task RenderOpenApiDocument([OpenApiHttpTrigge /// /// Invokes the HTTP trigger endpoint to render Swagger UI in HTML. /// + /// instance. /// instance. /// instance. /// instance. /// Swagger UI in HTML. [OpenApiIgnore] - public static async Task RenderSwaggerUI([OpenApiHttpTriggerContext] OpenApiHttpTriggerContext context, HttpRequest req, ExecutionContext ctx, ILogger log) + public static async Task RenderSwaggerUI( + [OpenApiHttpTriggerContext] OpenApiHttpTriggerContext openApiContext, + HttpRequest req, ExecutionContext ctx, ILogger log) { log.LogInformation("SwaggerUI page was requested."); @@ -182,7 +192,7 @@ public static async Task RenderSwaggerUI([OpenApiHttpTriggerConte var content = default(ContentResult); try { - var auth = await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) + var auth = await openApiContext.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) .AuthorizeAsync(request) .ConfigureAwait(false); if (!auth.IsNullOrDefault()) @@ -197,11 +207,11 @@ public static async Task RenderSwaggerUI([OpenApiHttpTriggerConte return content; } - result = await context.SwaggerUI - .AddMetadata(context.OpenApiConfigurationOptions.Info) - .AddServer(new HttpRequestObject(req), context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) - .BuildAsync(context.PackageAssembly, context.OpenApiCustomUIOptions) - .RenderAsync("swagger.json", context.GetDocumentAuthLevel(), context.GetSwaggerAuthKey()) + result = await openApiContext.SwaggerUI + .AddMetadata(openApiContext.OpenApiConfigurationOptions.Info) + .AddServer(new HttpRequestObject(req), openApiContext.HttpSettings.RoutePrefix, openApiContext.OpenApiConfigurationOptions) + .BuildAsync(openApiContext.PackageAssembly, openApiContext.OpenApiCustomUIOptions) + .RenderAsync("swagger.json", openApiContext.GetDocumentAuthLevel(), openApiContext.GetSwaggerAuthKey()) .ConfigureAwait(false); content = new ContentResult() @@ -216,7 +226,7 @@ public static async Task RenderSwaggerUI([OpenApiHttpTriggerConte log.LogError(ex.Message); result = ex.Message; - if (context.IsDevelopment) + if (openApiContext.IsDevelopment) { result += "\r\n\r\n"; result += ex.StackTrace; @@ -235,12 +245,15 @@ public static async Task RenderSwaggerUI([OpenApiHttpTriggerConte /// /// Invokes the HTTP trigger endpoint to render oauth2-redirect.html. /// + /// instance. /// instance. /// instance. /// instance. /// oauth2-redirect.html. [OpenApiIgnore] - public static async Task RenderOAuth2Redirect([OpenApiHttpTriggerContext] OpenApiHttpTriggerContext context, HttpRequest req, ExecutionContext ctx, ILogger log) + public static async Task RenderOAuth2Redirect( + [OpenApiHttpTriggerContext] OpenApiHttpTriggerContext openApiContext, + HttpRequest req, ExecutionContext ctx, ILogger log) { log.LogInformation("The oauth2-redirect.html page was requested."); @@ -249,13 +262,13 @@ public static async Task RenderOAuth2Redirect([OpenApiHttpTrigger var content = default(ContentResult); try { - await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) + await openApiContext.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) .ConfigureAwait(false); - result = await context.SwaggerUI - .AddServer(request, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) - .BuildOAuth2RedirectAsync(context.PackageAssembly) - .RenderOAuth2RedirectAsync("oauth2-redirect.html", context.GetDocumentAuthLevel(), context.GetSwaggerAuthKey()) + result = await openApiContext.SwaggerUI + .AddServer(request, openApiContext.HttpSettings.RoutePrefix, openApiContext.OpenApiConfigurationOptions) + .BuildOAuth2RedirectAsync(openApiContext.PackageAssembly) + .RenderOAuth2RedirectAsync("oauth2-redirect.html", openApiContext.GetDocumentAuthLevel(), openApiContext.GetSwaggerAuthKey()) .ConfigureAwait(false); content = new ContentResult() @@ -270,7 +283,7 @@ await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) log.LogError(ex.Message); result = ex.Message; - if (context.IsDevelopment) + if (openApiContext.IsDevelopment) { result += "\r\n\r\n"; result += ex.StackTrace; diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiWebJobsStartup.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiWebJobsStartup.cs index 95a2f753..924a0271 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiWebJobsStartup.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiWebJobsStartup.cs @@ -1,18 +1,15 @@ -using Microsoft.Azure.WebJobs.Description; using Microsoft.Azure.WebJobs.Extensions.OpenApi; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Attributes; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations.AppSettings.Extensions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations.AppSettings.Resolvers; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; -using Microsoft.Azure.WebJobs.Host.Bindings; -using Microsoft.Azure.WebJobs.Host.Config; using Microsoft.Azure.WebJobs.Hosting; using Microsoft.Azure.WebJobs.Script.Description; using Microsoft.Extensions.DependencyInjection; -using System; -using System.Threading.Tasks; [assembly: WebJobsStartup(typeof(OpenApiWebJobsStartup))] + namespace Microsoft.Azure.WebJobs.Extensions.OpenApi { /// @@ -35,31 +32,4 @@ public void Configure(IWebJobsBuilder builder) builder.AddExtension(); } } - - /// - /// Binding for injecting the OpenApiTriggerContext during InProc function creation - /// - [Binding] - [AttributeUsage(AttributeTargets.Parameter)] - public class OpenApiHttpTriggerContextAttribute : Attribute - { - } - - /// - /// Extension to register the [OpenApiHttpTriggerContext] attribute to inject IOpenApHttpTriggerContext - /// - [Extension(nameof(OpenApiHttpTriggerContextBinding))] - public class OpenApiHttpTriggerContextBinding : IExtensionConfigProvider - { - public void Initialize(ExtensionConfigContext context) - { - var rule = context.AddBindingRule(); - rule.BindToInput((OpenApiHttpTriggerContextAttribute attr, ValueBindingContext vbContext) => - { - var httpContext = vbContext.FunctionContext.CreateObjectInstance(); - - return Task.FromResult(httpContext); - }); - } - } } diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/DefaultOpenApiConfigurationOptionsTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/DefaultOpenApiConfigurationOptionsTests.cs index 6f246cd7..e453e937 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/DefaultOpenApiConfigurationOptionsTests.cs +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/DefaultOpenApiConfigurationOptionsTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using FluentAssertions; @@ -15,16 +14,23 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations [TestClass] public class DefaultOpenApiConfigurationOptionsTests { - [TestMethod] - public void Given_Type_When_Instantiated_Then_Properties_Should_Return_Value() + [TestCleanup] + public void Cleanup() { Environment.SetEnvironmentVariable("OpenApi__DocVersion", null); Environment.SetEnvironmentVariable("OpenApi__DocTitle", null); + Environment.SetEnvironmentVariable("OpenApi__DocDescription", null); Environment.SetEnvironmentVariable("OpenApi__HostNames", null); Environment.SetEnvironmentVariable("OpenApi__Version", null); Environment.SetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT", "Development"); Environment.SetEnvironmentVariable("OpenApi__ForceHttp", null); Environment.SetEnvironmentVariable("OpenApi__ForceHttps", null); + } + + [TestMethod] + public void Given_Type_When_Instantiated_Then_Properties_Should_Return_Value() + { + Environment.SetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT", "Development"); var options = new DefaultOpenApiConfigurationOptions(); @@ -68,6 +74,19 @@ public void Given_OpenApiDocTitle_When_Instantiated_Then_Property_Should_Return_ options.Info.Title.Should().Be(expected); } + [DataTestMethod] + [DataRow(null, "This is the OpenAPI Document on Azure Functions")] + [DataRow("", "This is the OpenAPI Document on Azure Functions")] + [DataRow("hello world", "hello world")] + public void Given_OpenApiDocDescription_When_Instantiated_Then_Property_Should_Return_Value(string description, string expected) + { + Environment.SetEnvironmentVariable("OpenApi__DocDescription", description); + + var options = new DefaultOpenApiConfigurationOptions(); + + options.Info.Description.Should().Be(expected); + } + [DataTestMethod] [DataRow(0, null)] [DataRow(0, "")] @@ -145,10 +164,7 @@ public void Given_EnvironmentVariable_When_GetOpenApiDocVersion_Invoked_Then_It_ { Environment.SetEnvironmentVariable("OpenApi__DocVersion", version); - var options = new DefaultOpenApiConfigurationOptions(); - var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("GetOpenApiDocVersion", BindingFlags.NonPublic | BindingFlags.Static); - - var result = method.Invoke(options, null); + var result = DefaultOpenApiConfigurationOptions.GetOpenApiDocVersion(); result.Should().BeOfType(); (result as string).Should().Be(expected); @@ -162,10 +178,21 @@ public void Given_EnvironmentVariable_When_GetOpenApiDocTitle_Invoked_Then_It_Sh { Environment.SetEnvironmentVariable("OpenApi__DocTitle", title); - var options = new DefaultOpenApiConfigurationOptions(); - var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("GetOpenApiDocTitle", BindingFlags.NonPublic | BindingFlags.Static); + var result = DefaultOpenApiConfigurationOptions.GetOpenApiDocTitle(); - var result = method.Invoke(options, null); + result.Should().BeOfType(); + (result as string).Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, "This is the OpenAPI Document on Azure Functions")] + [DataRow("", "This is the OpenAPI Document on Azure Functions")] + [DataRow("hello world", "hello world")] + public void Given_OpenApiDocDescription_When_Instantiated_Then_Property_Should_Return_Result(string description, string expected) + { + Environment.SetEnvironmentVariable("OpenApi__DocDescription", description); + + var result = DefaultOpenApiConfigurationOptions.GetOpenApiDocDescription(); result.Should().BeOfType(); (result as string).Should().Be(expected); @@ -180,10 +207,7 @@ public void Given_EnvironmentVariable_When_GetHostNames_Invoked_Then_It_Should_R { Environment.SetEnvironmentVariable("OpenApi__HostNames", hostnames); - var options = new DefaultOpenApiConfigurationOptions(); - var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("GetHostNames", BindingFlags.NonPublic | BindingFlags.Static); - - var result = method.Invoke(options, null); + var result = DefaultOpenApiConfigurationOptions.GetHostNames(); result.Should().BeOfType>(); (result as List).Should().HaveCount(expected); @@ -200,13 +224,9 @@ public void Given_EnvironmentVariable_When_GetOpenApiVersion_Invoked_Then_It_Sho { Environment.SetEnvironmentVariable("OpenApi__Version", version); - var options = new DefaultOpenApiConfigurationOptions(); - var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("GetOpenApiVersion", BindingFlags.NonPublic | BindingFlags.Static); - - var result = method.Invoke(options, null); + var result = DefaultOpenApiConfigurationOptions.GetOpenApiVersion(); - result.Should().BeOfType(); - ((OpenApiVersionType)result).Should().Be(expected); + result.Should().Be(expected); } [DataTestMethod] @@ -216,13 +236,9 @@ public void Given_EnvironmentVariable_When_IsFunctionsRuntimeEnvironmentDevelopm { Environment.SetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT", environment); - var options = new DefaultOpenApiConfigurationOptions(); - var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("IsFunctionsRuntimeEnvironmentDevelopment", BindingFlags.NonPublic | BindingFlags.Static); - - var result = method.Invoke(options, null); + var result = DefaultOpenApiConfigurationOptions.IsFunctionsRuntimeEnvironmentDevelopment(); - result.Should().BeOfType(); - ((bool)result).Should().Be(expected); + result.Should().Be(expected); } [DataTestMethod] @@ -234,13 +250,9 @@ public void Given_EnvironmentVariable_When_IsHttpForced_Invoked_Then_It_Should_R { Environment.SetEnvironmentVariable("OpenApi__ForceHttp", forceHttps); - var options = new DefaultOpenApiConfigurationOptions(); - var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("IsHttpForced", BindingFlags.NonPublic | BindingFlags.Static); - - var result = method.Invoke(options, null); + var result = DefaultOpenApiConfigurationOptions.IsHttpForced(); - result.Should().BeOfType(); - ((bool)result).Should().Be(expected); + result.Should().Be(expected); } [DataTestMethod] @@ -252,13 +264,9 @@ public void Given_EnvironmentVariable_When_IsHttpsForced_Invoked_Then_It_Should_ { Environment.SetEnvironmentVariable("OpenApi__ForceHttps", forceHttps); - var options = new DefaultOpenApiConfigurationOptions(); - var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("IsHttpsForced", BindingFlags.NonPublic | BindingFlags.Static); - - var result = method.Invoke(options, null); + var result = DefaultOpenApiConfigurationOptions.IsHttpsForced(); - result.Should().BeOfType(); - ((bool)result).Should().Be(expected); + result.Should().Be(expected); } public void Given_Type_When_Instantiated_Then_It_Should_Return_EmptyListOfDocumentFilters() diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/OpenApiConfigurationOptionsTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/OpenApiConfigurationOptionsTests.cs new file mode 100644 index 00000000..40feb7c6 --- /dev/null +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/OpenApiConfigurationOptionsTests.cs @@ -0,0 +1,34 @@ +using FluentAssertions; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations +{ + [TestClass] + public class OpenApiConfigurationOptionsTests + { + [TestMethod] + public void Given_Type_When_Instantiated_Then_Properties_Should_Return_Result() + { + var options = new OpenApiConfigurationOptions(); + + options.Info.Should().NotBeNull(); + options.Info.Version.Should().BeNull(); + options.Info.Title.Should().BeNull(); + options.Info.Description.Should().BeNull(); + + options.Servers.Should().NotBeNull(); + options.Servers.Should().HaveCount(0); + + options.OpenApiVersion.Should().Be(OpenApiVersionType.V2); + options.IncludeRequestingHostName.Should().BeFalse(); + options.ForceHttp.Should().BeFalse(); + options.ForceHttps.Should().BeFalse(); + + options.DocumentFilters.Should().NotBeNull(); + options.DocumentFilters.Should().HaveCount(0); + } + } +} diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Configurations/OpenApiSettingsTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Configurations/OpenApiSettingsTests.cs index c990088e..d469a6eb 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Configurations/OpenApiSettingsTests.cs +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Configurations/OpenApiSettingsTests.cs @@ -6,6 +6,7 @@ using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations.AppSettings.Extensions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations.AppSettings.Resolvers; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -14,35 +15,228 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.Configurations [TestClass] public class OpenApiSettingsTests { + [TestCleanup] + public void Cleanup() + { + Environment.SetEnvironmentVariable("OpenApi__Version", null); + Environment.SetEnvironmentVariable("OpenApi__DocVersion", null); + Environment.SetEnvironmentVariable("OpenApi__DocTitle", null); + Environment.SetEnvironmentVariable("OpenApi__DocDescription", null); + Environment.SetEnvironmentVariable("OpenApi__HostNames", null); + Environment.SetEnvironmentVariable("OpenApi__ForceHttps", null); + Environment.SetEnvironmentVariable("OpenApi__ForceHttp", null); + Environment.SetEnvironmentVariable("OpenApi__HideSwaggerUI", null); + Environment.SetEnvironmentVariable("OpenApi__HideDocument", null); + Environment.SetEnvironmentVariable("OpenApi__ApiKey", null); + Environment.SetEnvironmentVariable("OpenApi__AuthLevel__Document", null); + Environment.SetEnvironmentVariable("OpenApi__AuthLevel__UI", null); + Environment.SetEnvironmentVariable("OpenApi__BackendProxyUrl", null); + } + [DataTestMethod] - [DataRow("true", true, "false", false, "lorem", "Function", AuthorizationLevel.Function, "Anonymous", AuthorizationLevel.Anonymous, "http://localhost:7071", "https://contoso.com/api/")] - [DataRow("false", false, "false", false, "ipsum", "Anonymous", AuthorizationLevel.Anonymous, "Function", AuthorizationLevel.Function, "http://contoso", "https://fabrikam.com/api/")] - [DataRow("false", false, "true", true, "ipsum", "Anonymous", AuthorizationLevel.Anonymous, "Function", AuthorizationLevel.Function, "http://contoso", "https://fabrikam.com/api/")] - public void Given_EnvironmentVariables_When_Instantiated_Then_It_Should_Return_Result(string hideSwaggerUI, bool expectedHideSwaggerUI, - string hideDocument, bool expectedHideDocument, - string apiKey, - string authLevelDoc, AuthorizationLevel expectedAuthLevelDoc, - string authLevelUI, AuthorizationLevel expectedAuthLevelUI, - string proxyUrl, string hostnames) - { - Environment.SetEnvironmentVariable("OpenApi__HideSwaggerUI", hideSwaggerUI); - Environment.SetEnvironmentVariable("OpenApi__HideDocument", hideDocument); - Environment.SetEnvironmentVariable("OpenApi__ApiKey", apiKey); - Environment.SetEnvironmentVariable("OpenApi__AuthLevel__Document", authLevelDoc); - Environment.SetEnvironmentVariable("OpenApi__AuthLevel__UI", authLevelUI); - Environment.SetEnvironmentVariable("OpenApi__BackendProxyUrl", proxyUrl); + [DataRow(null, OpenApiVersionType.V2)] + [DataRow("", OpenApiVersionType.V2)] + [DataRow("v2", OpenApiVersionType.V2)] + [DataRow("v3", OpenApiVersionType.V3)] + public void Given_Version_When_Instantiated_Then_It_Should_Return_Result(string version, OpenApiVersionType expected) + { + Environment.SetEnvironmentVariable("OpenApi__Version", version); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.Version.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, null)] + [DataRow("", null)] + [DataRow("1.0.0", "1.0.0")] + public void Given_DocVersion_When_Instantiated_Then_It_Should_Return_Result(string version, string expected) + { + Environment.SetEnvironmentVariable("OpenApi__DocVersion", version); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.DocVersion.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, null)] + [DataRow("", null)] + [DataRow("hello", "hello")] + public void Given_DocTitle_When_Instantiated_Then_It_Should_Return_Result(string title, string expected) + { + Environment.SetEnvironmentVariable("OpenApi__DocTitle", title); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.DocTitle.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, null)] + [DataRow("", null)] + [DataRow("world", "world")] + public void Given_DocDescription_When_Instantiated_Then_It_Should_Return_Result(string description, string expected) + { + Environment.SetEnvironmentVariable("OpenApi__DocDescription", description); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.DocDescription.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, null)] + [DataRow("", null)] + [DataRow("https://contoso", "https://contoso")] + [DataRow("https://contoso, https://fabrikam", "https://contoso, https://fabrikam")] + public void Given_HostNames_When_Instantiated_Then_It_Should_Return_Result(string hostnames, string expected) + { Environment.SetEnvironmentVariable("OpenApi__HostNames", hostnames); var config = ConfigurationResolver.Resolve(); var settings = config.Get("OpenApi"); - settings.HideSwaggerUI.Should().Be(expectedHideSwaggerUI); - settings.HideDocument.Should().Be(expectedHideDocument); - settings.ApiKey.Should().Be(apiKey); - settings.AuthLevel.Document.Should().Be(expectedAuthLevelDoc); - settings.AuthLevel.UI.Should().Be(expectedAuthLevelUI); - settings.BackendProxyUrl.Should().Be(proxyUrl); - settings.HostNames.Should().Be(hostnames); + settings.HostNames.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, false)] + [DataRow("", false)] + [DataRow("true", true)] + [DataRow("false", false)] + public void Given_ForceHttps_When_Instantiated_Then_It_Should_Return_Result(string https, bool expected) + { + Environment.SetEnvironmentVariable("OpenApi__ForceHttps", https); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.ForceHttps.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, false)] + [DataRow("", false)] + [DataRow("true", true)] + [DataRow("false", false)] + public void Given_ForceHttp_When_Instantiated_Then_It_Should_Return_Result(string http, bool expected) + { + Environment.SetEnvironmentVariable("OpenApi__ForceHttp", http); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.ForceHttp.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, false)] + [DataRow("", false)] + [DataRow("true", true)] + [DataRow("false", false)] + public void Given_HideSwaggerUI_When_Instantiated_Then_It_Should_Return_Result(string hide, bool expected) + { + Environment.SetEnvironmentVariable("OpenApi__HideSwaggerUI", hide); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.HideSwaggerUI.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, false)] + [DataRow("", false)] + [DataRow("true", true)] + [DataRow("false", false)] + public void Given_HideDocument_When_Instantiated_Then_It_Should_Return_Result(string hide, bool expected) + { + Environment.SetEnvironmentVariable("OpenApi__HideDocument", hide); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.HideDocument.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, null)] + [DataRow("", null)] + [DataRow("lorem", "lorem")] + public void Given_ApiKey_When_Instantiated_Then_It_Should_Return_Result(string apiKey, string expected) + { + Environment.SetEnvironmentVariable("OpenApi__ApiKey", apiKey); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.ApiKey.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, null)] + [DataRow("", null)] + [DataRow("Anonymous", AuthorizationLevel.Anonymous)] + [DataRow("anonymous", AuthorizationLevel.Anonymous)] + [DataRow("Function", AuthorizationLevel.Function)] + [DataRow("function", AuthorizationLevel.Function)] + [DataRow("User", AuthorizationLevel.User)] + [DataRow("user", AuthorizationLevel.User)] + [DataRow("Admin", AuthorizationLevel.Admin)] + [DataRow("admin", AuthorizationLevel.Admin)] + [DataRow("System", AuthorizationLevel.System)] + [DataRow("system", AuthorizationLevel.System)] + public void Given_AuthLevelDoc_When_Instantiated_Then_It_Should_Return_Result(string authLevel, AuthorizationLevel? expected) + { + Environment.SetEnvironmentVariable("OpenApi__AuthLevel__Document", authLevel); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.AuthLevel.Document.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, null)] + [DataRow("", null)] + [DataRow("Anonymous", AuthorizationLevel.Anonymous)] + [DataRow("anonymous", AuthorizationLevel.Anonymous)] + [DataRow("Function", AuthorizationLevel.Function)] + [DataRow("function", AuthorizationLevel.Function)] + [DataRow("User", AuthorizationLevel.User)] + [DataRow("user", AuthorizationLevel.User)] + [DataRow("Admin", AuthorizationLevel.Admin)] + [DataRow("admin", AuthorizationLevel.Admin)] + [DataRow("System", AuthorizationLevel.System)] + [DataRow("system", AuthorizationLevel.System)] + public void Given_AuthLevelUI_When_Instantiated_Then_It_Should_Return_Result(string authLevel, AuthorizationLevel? expected) + { + Environment.SetEnvironmentVariable("OpenApi__AuthLevel__UI", authLevel); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.AuthLevel.UI.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(null, null)] + [DataRow("", null)] + [DataRow("lorem", "lorem")] + public void Given_BackendProxyUrl_When_Instantiated_Then_It_Should_Return_Result(string url, string expected) + { + Environment.SetEnvironmentVariable("OpenApi__BackendProxyUrl", url); + + var config = ConfigurationResolver.Resolve(); + var settings = config.Get("OpenApi"); + + settings.BackendProxyUrl.Should().Be(expected); } } }