More DI feature for `OpenApiConfigurationOptions` (#466)

This commit is contained in:
Justin Yoo 2022-08-08 21:19:03 +07:00 коммит произвёл GitHub
Родитель 1ac4a84258
Коммит 9072106051
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
26 изменённых файлов: 1127 добавлений и 661 удалений

Просмотреть файл

@ -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<IOpenApiConfigurationOptions>(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<Fixture>())
.ConfigureServices(services => {
services.AddSingleton<Fixture>();
// Example: If you want to change the configuration during startup of your function you can use the following code:
services.AddSingleton<IOpenApiConfigurationOptions>(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 ##

Просмотреть файл

@ -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<IOpenApiConfigurationOptions>(_ =>
{
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;
});
}
}
}
```

Просмотреть файл

@ -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<IOpenApiConfigurationOptions>(_ =>
{
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();
}
}
}
```

Просмотреть файл

@ -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.

Просмотреть файл

@ -6,13 +6,13 @@ using Microsoft.OpenApi.Models;
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()
{

Просмотреть файл

@ -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<Fixture>();
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
// Example: If you want to change the configuration during startup of your function you can use the following code:
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<Fixture>()
.AddSingleton<IOpenApiConfigurationOptions>(_ =>
{
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(),
};
//services.AddSingleton<IOpenApiConfigurationOptions>(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();
}
}
}
return options;
})
;
})
.Build();
host.Run();
}
}
}

Просмотреть файл

@ -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)."
}
}

Просмотреть файл

@ -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()
{

Просмотреть файл

@ -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;
// Example: If you want to change the configuration during startup of your function you can use the following code:
[assembly: FunctionsStartup(typeof(Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc.Startup))]
//builder.Services.AddSingleton<IOpenApiConfigurationOptions>((_) =>
//{
// return new OpenApiConfigurationOptions()
// {
// Info = new Microsoft.OpenApi.Models.OpenApiInfo
// {
// Title = "A dynamic title generated at runtime",
// Description = "Dynamic Open API information at runtime"
// }
// };
//});
}
}
}
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.InProc
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<Fixture>()
.AddSingleton<IOpenApiConfigurationOptions>(_ =>
{
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(),
};
return options;
})
;
}
}
}

Просмотреть файл

@ -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)."
}
}

Просмотреть файл

@ -40,6 +40,7 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi
/// <summary>
/// Initializes a new instance of the <see cref="OpenApiHttpTriggerContext"/> class.
/// </summary>
/// <param name="configOptions"><see cref="IOpenApiConfigurationOptions"/> instance.</param>
public OpenApiHttpTriggerContext(IOpenApiConfigurationOptions configOptions = null)
{
this._configOptions = configOptions;

Просмотреть файл

@ -11,7 +11,7 @@
<ItemGroup>
<PackageReference Include="Cocona.Lite" Version="1.4.0" />
<PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.33" />
<PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.25" />
<PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />

Просмотреть файл

@ -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 @@ using Newtonsoft.Json;
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions
{
/// <summary>
/// This represents the metadata entity for OpenApiHttpTrigger context binding.
/// This represents the metadata entity for OpenAPI HTTP Trigger context binding.
/// </summary>
public class OpenApiHttpTriggerContextBindingMetadata
{
/// <summary>
/// Gets or sets the name of the binding parameter. Default value is <c>context</c>.
/// Gets or sets the name of the binding parameter. Default value is <c>openApiContext</c>.
/// </summary>
[JsonRequired]
[JsonProperty("name")]
public virtual string Name { get; set; } = "context";
public virtual string Name { get; set; } = "openApiContext";
/// <summary>
/// Gets or sets the binding type. Default value is <c>openApiHttpTriggerContext</c>.

Просмотреть файл

@ -0,0 +1,14 @@
using System;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes
{
/// <summary>
/// This represents the attribute entity for <see cref="IOpenApiConfigurationOptions"/> to be excluded from auto-loading.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class OpenApiConfigurationOptionsIgnoreAttribute : Attribute
{
}
}

Просмотреть файл

@ -3,6 +3,7 @@ using System.Collections.Generic;
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
/// <summary>
/// This represents the options entity for OpenAPI metadata configuration.
/// </summary>
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations
private const string ForceHttpsKey = "OpenApi__ForceHttps";
/// <inheritdoc />
public virtual OpenApiInfo Info { get; set; } = new OpenApiInfo()
public override OpenApiInfo Info { get; set; } = new OpenApiInfo()
{
Version = GetOpenApiDocVersion(),
Title = GetOpenApiDocTitle(),
Description = GetOpenApiDocDescription(),
};
/// <inheritdoc />
public virtual List<OpenApiServer> Servers { get; set; } = GetHostNames();
public override List<OpenApiServer> Servers { get; set; } = GetHostNames();
/// <inheritdoc />
public virtual OpenApiVersionType OpenApiVersion { get; set; } = GetOpenApiVersion();
public override OpenApiVersionType OpenApiVersion { get; set; } = GetOpenApiVersion();
/// <inheritdoc />
public virtual bool IncludeRequestingHostName { get; set; } = IsFunctionsRuntimeEnvironmentDevelopment();
public override bool IncludeRequestingHostName { get; set; } = IsFunctionsRuntimeEnvironmentDevelopment();
/// <inheritdoc />
public virtual bool ForceHttp { get; set; } = IsHttpForced();
public override bool ForceHttp { get; set; } = IsHttpForced();
/// <inheritdoc />
public virtual bool ForceHttps { get; set; } = IsHttpsForced();
public override bool ForceHttps { get; set; } = IsHttpsForced();
/// <inheritdoc />
public virtual List<IDocumentFilter> DocumentFilters { get; set; } = new List<IDocumentFilter>();
public override List<IDocumentFilter> DocumentFilters { get; set; } = new List<IDocumentFilter>();
/// <summary>
/// Gets the OpenAPI document version.
/// </summary>
/// <returns>Returns the OpenAPI document version.</returns>
protected static string GetOpenApiDocVersion()
public static string GetOpenApiDocVersion()
{
var version = Environment.GetEnvironmentVariable(OpenApiDocVersionKey) ?? DefaultOpenApiDocVersion();
@ -62,18 +66,29 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations
/// Gets the OpenAPI document title.
/// </summary>
/// <returns>Returns the OpenAPI document title.</returns>
protected static string GetOpenApiDocTitle()
public static string GetOpenApiDocTitle()
{
var title = Environment.GetEnvironmentVariable(OpenApiDocTitleKey) ?? DefaultOpenApiDocTitle();
return title;
}
/// <summary>
/// Gets the OpenAPI document description.
/// </summary>
/// <returns>Returns the OpenAPI document description.</returns>
public static string GetOpenApiDocDescription()
{
var description = Environment.GetEnvironmentVariable(OpenApiDocDescriptionKey) ?? DefaultOpenApiDocDescription();
return description;
}
/// <summary>
/// Gets the list of hostnames.
/// </summary>
/// <returns>Returns the list of hostnames.</returns>
protected static List<OpenApiServer> GetHostNames()
public static List<OpenApiServer> GetHostNames()
{
var servers = new List<OpenApiServer>();
var collection = Environment.GetEnvironmentVariable(OpenApiHostNamesKey);
@ -95,7 +110,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations
/// Gets the OpenAPI version.
/// </summary>
/// <returns>Returns the OpenAPI version.</returns>
protected static OpenApiVersionType GetOpenApiVersion()
public static OpenApiVersionType GetOpenApiVersion()
{
var version = Enum.TryParse<OpenApiVersionType>(
Environment.GetEnvironmentVariable(OpenApiVersionKey), ignoreCase: true, out var result)
@ -109,7 +124,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations
/// Checks whether the current Azure Functions runtime environment is "Development" or not.
/// </summary>
/// <returns>Returns <c>True</c>, if the current Azure Functions runtime environment is "Development"; otherwise returns <c>False</c></returns>
protected static bool IsFunctionsRuntimeEnvironmentDevelopment()
public static bool IsFunctionsRuntimeEnvironmentDevelopment()
{
var development = Environment.GetEnvironmentVariable(FunctionsRuntimeEnvironmentKey) == "Development";
@ -120,7 +135,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations
/// Checks whether HTTP is forced or not.
/// </summary>
/// <returns>Returns <c>True</c>, if HTTP is forced; otherwise returns <c>False</c>.</returns>
protected static bool IsHttpForced()
public static bool IsHttpForced()
{
var development = bool.TryParse(Environment.GetEnvironmentVariable(ForceHttpKey), out var result) ? result : false;
@ -131,7 +146,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations
/// Checks whether HTTPS is forced or not.
/// </summary>
/// <returns>Returns <c>True</c>, if HTTPS is forced; otherwise returns <c>False</c>.</returns>
protected static bool IsHttpsForced()
public static bool IsHttpsForced()
{
var development = bool.TryParse(Environment.GetEnvironmentVariable(ForceHttpsKey), out var result) ? result : false;
@ -152,5 +167,10 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations
{
return "OpenAPI Document on Azure Functions";
}
private static string DefaultOpenApiDocDescription()
{
return "This is the OpenAPI Document on Azure Functions";
}
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// This represents the options entity for OpenAPI metadata configuration.
/// </summary>
[OpenApiConfigurationOptionsIgnore]
public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions
{
/// <inheritdoc />
public virtual OpenApiInfo Info { get; set; } = new OpenApiInfo();
/// <inheritdoc />
public virtual List<OpenApiServer> Servers { get; set; } = new List<OpenApiServer>();
/// <inheritdoc />
public virtual OpenApiVersionType OpenApiVersion { get; set; }
/// <inheritdoc />
public virtual bool IncludeRequestingHostName { get; set; }
/// <inheritdoc />
public virtual bool ForceHttp { get; set; }
/// <inheritdoc />
public virtual bool ForceHttps { get; set; }
/// <inheritdoc />
public virtual List<IDocumentFilter> DocumentFilters { get; set; } = new List<IDocumentFilter>();
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// This represents the resolver entity for <see cref="OpenApiServer"/>.
/// </summary>
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
{
/// <summary>
/// This represents the resolver entity for <see cref="OpenApiServer"/>.
/// </summary>
public static class OpenApiConfigurationResolver
{
/// <summary>
/// Gets the <see cref="IOpenApiConfigurationOptions"/> instance from the given assembly.
/// </summary>
/// <param name="assembly">The executing assembly instance.</param>
/// <returns>Returns the <see cref="IOpenApiConfigurationOptions"/> instance resolved.</returns>
public static IOpenApiConfigurationOptions Resolve(Assembly assembly)
{
var type = assembly.GetLoadableTypes()
.SingleOrDefault(p => p.GetInterface("IOpenApiConfigurationOptions", ignoreCase: true).IsNullOrDefault() == false
&& p.IsAbstract == false
&& p.GetCustomAttribute<ObsoleteAttribute>(inherit: false).IsNullOrDefault() == true);
if (type.IsNullOrDefault())
{
var settings = new DefaultOpenApiConfigurationOptions();
return settings;
/// <summary>
/// Gets the <see cref="IOpenApiConfigurationOptions"/> instance from the given assembly.
/// </summary>
/// <param name="assembly">The executing assembly instance.</param>
/// <returns>Returns the <see cref="IOpenApiConfigurationOptions"/> instance resolved.</returns>
public static IOpenApiConfigurationOptions Resolve(Assembly assembly)
{
var type = assembly.GetLoadableTypes()
.SingleOrDefault(p => p.GetInterface("IOpenApiConfigurationOptions", ignoreCase: true).IsNullOrDefault() == false
&& p.IsAbstract == false
&& p.GetCustomAttribute<ObsoleteAttribute>(inherit: false).IsNullOrDefault() == true
&& p.GetCustomAttribute<OpenApiConfigurationOptionsIgnoreAttribute>(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;
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
using System;
using Microsoft.Azure.WebJobs.Description;
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Attributes
{
/// <summary>
/// This represents the parameter binding attribute that injects the <see cref="OpenApiHttpTriggerContext"/> instance to each OpenAPI HTTP trigger endpoint.
/// </summary>
[Binding]
[AttributeUsage(AttributeTargets.Parameter)]
public class OpenApiHttpTriggerContextAttribute : Attribute
{
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// This represents the extension attribute that registers the <see cref="OpenApiHttpTriggerContextAttribute"/> to inject the <see cref="OpenApiHttpTriggerContext"/> instance.
/// </summary>
[Extension(nameof(OpenApiHttpTriggerContextBinding))]
public class OpenApiHttpTriggerContextBinding : IExtensionConfigProvider
{
/// <inheritdoc/>
public void Initialize(ExtensionConfigContext context)
{
var rule = context.AddBindingRule<OpenApiHttpTriggerContextAttribute>();
rule.BindToInput((attr, vbContext) =>
{
var httpContext = vbContext.FunctionContext.CreateObjectInstance<OpenApiHttpTriggerContext>();
return Task.FromResult(httpContext);
});
}
}
}

Просмотреть файл

@ -1,3 +1,5 @@
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations
{
/// <summary>
@ -5,13 +7,48 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations
/// </summary>
public class OpenApiSettings
{
/// <summary>
/// Gets or sets the OpenAPI spec version.
/// </summary>
public virtual OpenApiVersionType Version { get; set; }
/// <summary>
/// Gets or sets the OpenAPI document version.
/// </summary>
public virtual string DocVersion { get; set; }
/// <summary>
/// Gets or sets the OpenAPI document title.
/// </summary>
public virtual string DocTitle { get; set; }
/// <summary>
/// Gets or sets the OpenAPI document description.
/// </summary>
public virtual string DocDescription { get; set; }
/// <summary>
/// Gets or sets the comma delimited host names.
/// </summary>
public virtual string HostNames { get; set; }
/// <summary>
/// Gets or sets the value indicating whether to force the HTTPS connection or not.
/// </summary>
public virtual bool ForceHttps { get; set; }
/// <summary>
/// Gets or sets the value indicating whether to force the HTTP connection or not.
/// </summary>
public virtual bool ForceHttp { get; set; }
/// <summary>
/// Gets or sets the value indicating whether to hide the Swagger UI page or not.
/// </summary>
public virtual bool HideSwaggerUI { get; set; }
/// <summary>
/// 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.
/// </summary>
public virtual bool HideDocument { get; set; }
@ -23,16 +60,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations
/// <summary>
/// Gets or sets the <see cref="OpenApiAuthLevelSettings"/> object.
/// </summary>
public virtual OpenApiAuthLevelSettings AuthLevel { get; set; }
public virtual OpenApiAuthLevelSettings AuthLevel { get; set; } = new OpenApiAuthLevelSettings();
/// <summary>
/// Gets or sets the backend URL for Azure Functions Proxy.
/// </summary>
public virtual string BackendProxyUrl { get; set; }
/// <summary>
/// Gets or sets the comma delimited host names.
/// </summary>
public virtual string HostNames { get; set; }
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// This represents the context entity for <see cref="OpenApiTriggerFunctionProvider"/>.
/// </summary>
[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;
/// <summary>
/// Initializes a new instance of the <see cref="OpenApiHttpTriggerContext"/> class.
/// </summary>
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
{
/// <summary>
/// This represents the context entity for <see cref="OpenApiTriggerFunctionProvider"/>.
/// </summary>
[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;
/// <summary>
/// Initializes a new instance of the <see cref="OpenApiHttpTriggerContext"/> class.
/// </summary>
/// <param name="configOptions"><see cref="IOpenApiConfigurationOptions"/> instance.</param>
public OpenApiHttpTriggerContext(IOpenApiConfigurationOptions configOptions = null)
{
this._configOptions = configOptions;
this.PackageAssembly = this.GetAssembly<ISwaggerUI>();
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();
}
/// <inheritdoc />
public virtual Assembly ApplicationAssembly
{
get
{
if (this._appAssembly.IsNullOrDefault())
{
this._appAssembly = this.GetAssembly(this._dllpath);
}
return this._appAssembly;
}
}
/// <inheritdoc />
public virtual Assembly PackageAssembly { get; }
/// <inheritdoc />
public virtual IOpenApiConfigurationOptions OpenApiConfigurationOptions
{
get
{
if (this._configOptions.IsNullOrDefault())
{
this._configOptions = OpenApiConfigurationResolver.Resolve(this.ApplicationAssembly);
}
return this._configOptions;
}
}
/// <inheritdoc />
public virtual IOpenApiCustomUIOptions OpenApiCustomUIOptions
{
get
{
if (this._uiOptions.IsNullOrDefault())
{
this._uiOptions = OpenApiCustomUIResolver.Resolve(this.ApplicationAssembly);
}
return this._uiOptions;
}
}
/// <inheritdoc />
public virtual HttpSettings HttpSettings { get; }
/// <inheritdoc />
public virtual IDocument Document { get; }
/// <inheritdoc />
public virtual ISwaggerUI SwaggerUI { get; }
/// <inheritdoc />
public virtual NamingStrategy NamingStrategy { get; } = new CamelCaseNamingStrategy();
/// <inheritdoc />
public virtual bool IsDevelopment { get; } = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development";
/// <inheritdoc />
[Obsolete("This method is obsolete. Use GetAssembly<T>() or GetAssembly(object) instead", error: true)]
public virtual Assembly GetExecutingAssembly()
{
var assembly = Assembly.GetExecutingAssembly();
return assembly;
}
/// <inheritdoc />
public virtual async Task<IOpenApiHttpTriggerContext> 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;
}
/// <inheritdoc />
public virtual async Task<OpenApiAuthorizationResult> AuthorizeAsync(IHttpRequestDataObject req)
{
var result = default(OpenApiAuthorizationResult);
var type = this.ApplicationAssembly
.GetLoadableTypes()
.SingleOrDefault(p => p.HasInterface<IOpenApiHttpTriggerAuthorization>());
if (type.IsNullOrDefault())
{
return result;
}
var auth = Activator.CreateInstance(type) as IOpenApiHttpTriggerAuthorization;
result = await auth.AuthorizeAsync(req).ConfigureAwait(false);
return result;
}
/// <inheritdoc />
public virtual VisitorCollection GetVisitorCollection()
{
var collection = VisitorCollection.CreateInstance();
return collection;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public virtual OpenApiSpecVersion GetOpenApiSpecVersion(OpenApiVersionType version = OpenApiVersionType.V2)
{
return version.ToOpenApiSpecVersion();
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public virtual OpenApiFormat GetOpenApiFormat(OpenApiFormatType format = OpenApiFormatType.Json)
{
return format.ToOpenApiFormat();
}
/// <inheritdoc />
public virtual OpenApiAuthLevelType GetDocumentAuthLevel(string key = "OpenApi__AuthLevel__Document")
{
var value = Environment.GetEnvironmentVariable(key);
var parsed = Enum.TryParse<OpenApiAuthLevelType>(value, out var result) ? result : OpenApiAuthLevelType.Anonymous;
return parsed;
}
/// <inheritdoc />
public virtual OpenApiAuthLevelType GetUIAuthLevel(string key = "OpenApi__AuthLevel__UI")
{
var value = Environment.GetEnvironmentVariable(key);
var parsed = Enum.TryParse<OpenApiAuthLevelType>(value, out var result) ? result : OpenApiAuthLevelType.Anonymous;
return parsed;
}
/// <inheritdoc />
public virtual string GetSwaggerAuthKey(string key = "OpenApi__ApiKey")
{
var value = Environment.GetEnvironmentVariable(key);
return value ?? string.Empty;
}
/// <inheritdoc />
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 <your_runtime>.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<string> 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<DependencyManifest>();
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<DependencyManifest> GetDependencyManifestAsync(string file)
{
var serialised = default(string);
using (var reader = File.OpenText(file))
{
serialised = await reader.ReadToEndAsync();
}
return JsonConvert.DeserializeObject<DependencyManifest>(serialised);
}
private Assembly GetAssembly(object instance)
{
return this.GetAssembly(instance.GetType());
}
private Assembly GetAssembly<T>()
{
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<ISwaggerUI>();
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();
}
/// <inheritdoc />
public virtual Assembly ApplicationAssembly
{
get
{
if (this._appAssembly.IsNullOrDefault())
{
this._appAssembly = this.GetAssembly(this._dllpath);
}
return this._appAssembly;
}
}
/// <inheritdoc />
public virtual Assembly PackageAssembly { get; }
/// <inheritdoc />
public virtual IOpenApiConfigurationOptions OpenApiConfigurationOptions
{
get
{
if (this._configOptions.IsNullOrDefault())
{
this._configOptions = OpenApiConfigurationResolver.Resolve(this.ApplicationAssembly);
}
return this._configOptions;
}
}
/// <inheritdoc />
public virtual IOpenApiCustomUIOptions OpenApiCustomUIOptions
{
get
{
if (this._uiOptions.IsNullOrDefault())
{
this._uiOptions = OpenApiCustomUIResolver.Resolve(this.ApplicationAssembly);
}
return this._uiOptions;
}
}
/// <inheritdoc />
public virtual HttpSettings HttpSettings { get; }
/// <inheritdoc />
public virtual IDocument Document { get; }
/// <inheritdoc />
public virtual ISwaggerUI SwaggerUI { get; }
/// <inheritdoc />
public virtual NamingStrategy NamingStrategy { get; } = new CamelCaseNamingStrategy();
/// <inheritdoc />
public virtual bool IsDevelopment { get; } = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development";
/// <inheritdoc />
[Obsolete("This method is obsolete. Use GetAssembly<T>() or GetAssembly(object) instead", error: true)]
public virtual Assembly GetExecutingAssembly()
{
var assembly = Assembly.GetExecutingAssembly();
return assembly;
}
/// <inheritdoc />
public virtual async Task<IOpenApiHttpTriggerContext> 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;
}
/// <inheritdoc />
public virtual async Task<OpenApiAuthorizationResult> AuthorizeAsync(IHttpRequestDataObject req)
{
var result = default(OpenApiAuthorizationResult);
var type = this.ApplicationAssembly
.GetLoadableTypes()
.SingleOrDefault(p => p.HasInterface<IOpenApiHttpTriggerAuthorization>());
if (type.IsNullOrDefault())
{
return result;
}
var auth = Activator.CreateInstance(type) as IOpenApiHttpTriggerAuthorization;
result = await auth.AuthorizeAsync(req).ConfigureAwait(false);
return result;
}
/// <inheritdoc />
public virtual VisitorCollection GetVisitorCollection()
{
var collection = VisitorCollection.CreateInstance();
return collection;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public virtual OpenApiSpecVersion GetOpenApiSpecVersion(OpenApiVersionType version = OpenApiVersionType.V2)
{
return version.ToOpenApiSpecVersion();
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public virtual OpenApiFormat GetOpenApiFormat(OpenApiFormatType format = OpenApiFormatType.Json)
{
return format.ToOpenApiFormat();
}
/// <inheritdoc />
public virtual OpenApiAuthLevelType GetDocumentAuthLevel(string key = "OpenApi__AuthLevel__Document")
{
var value = Environment.GetEnvironmentVariable(key);
var parsed = Enum.TryParse<OpenApiAuthLevelType>(value, out var result) ? result : OpenApiAuthLevelType.Anonymous;
return parsed;
}
/// <inheritdoc />
public virtual OpenApiAuthLevelType GetUIAuthLevel(string key = "OpenApi__AuthLevel__UI")
{
var value = Environment.GetEnvironmentVariable(key);
var parsed = Enum.TryParse<OpenApiAuthLevelType>(value, out var result) ? result : OpenApiAuthLevelType.Anonymous;
return parsed;
}
/// <inheritdoc />
public virtual string GetSwaggerAuthKey(string key = "OpenApi__ApiKey")
{
var value = Environment.GetEnvironmentVariable(key);
return value ?? string.Empty;
}
/// <inheritdoc />
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 <your_runtime>.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<string> 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<DependencyManifest>();
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<DependencyManifest> GetDependencyManifestAsync(string file)
{
var serialised = default(string);
using (var reader = File.OpenText(file))
{
serialised = await reader.ReadToEndAsync();
}
return JsonConvert.DeserializeObject<DependencyManifest>(serialised);
}
private Assembly GetAssembly(object instance)
{
return this.GetAssembly(instance.GetType());
}
private Assembly GetAssembly<T>()
{
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;
}
}
}

Просмотреть файл

@ -4,6 +4,7 @@ using System.Threading.Tasks;
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
/// <summary>
/// Invokes the HTTP trigger endpoint to get OpenAPI document.
/// </summary>
/// <param name="openApiContext"><see cref="OpenApiHttpTriggerContext"/> instance.</param>
/// <param name="req"><see cref="HttpRequest"/> instance.</param>
/// <param name="extension">File extension representing the document format. This MUST be either "json" or "yaml".</param>
/// <param name="ctx"><see cref="ExecutionContext"/> instance.</param>
/// <param name="log"><see cref="ILogger"/> instance.</param>
/// <returns>OpenAPI document in a format of either JSON or YAML.</returns>
[OpenApiIgnore]
public static async Task<IActionResult> RenderSwaggerDocument([OpenApiHttpTriggerContext] OpenApiHttpTriggerContext context, HttpRequest req, string extension, ExecutionContext ctx, ILogger log)
public static async Task<IActionResult> RenderSwaggerDocument(
[OpenApiHttpTriggerContext] OpenApiHttpTriggerContext openApiContext,
HttpRequest req, string extension, ExecutionContext ctx, ILogger log)
{
log.LogInformation($"swagger.{extension} was requested.");
@ -38,7 +42,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
/// <summary>
/// Invokes the HTTP trigger endpoint to get OpenAPI document.
/// </summary>
/// <param name="openApiContext"><see cref="OpenApiHttpTriggerContext"/> instance.</param>
/// <param name="req"><see cref="HttpRequest"/> instance.</param>
/// <param name="version">OpenAPI document spec version. This MUST be either "v2" or "v3".</param>
/// <param name="extension">File extension representing the document format. This MUST be either "json" or "yaml".</param>
@ -102,7 +107,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
/// <param name="log"><see cref="ILogger"/> instance.</param>
/// <returns>OpenAPI document in a format of either JSON or YAML.</returns>
[OpenApiIgnore]
public static async Task<IActionResult> RenderOpenApiDocument([OpenApiHttpTriggerContext] OpenApiHttpTriggerContext context, HttpRequest req, string version, string extension, ExecutionContext ctx, ILogger log)
public static async Task<IActionResult> RenderOpenApiDocument(
[OpenApiHttpTriggerContext] OpenApiHttpTriggerContext openApiContext,
HttpRequest req, string version, string extension, ExecutionContext ctx, ILogger log)
{
log.LogInformation($"{version}.{extension} was requested.");
@ -111,7 +118,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
/// <summary>
/// Invokes the HTTP trigger endpoint to render Swagger UI in HTML.
/// </summary>
/// <param name="openApiContext"><see cref="OpenApiHttpTriggerContext"/> instance.</param>
/// <param name="req"><see cref="HttpRequest"/> instance.</param>
/// <param name="ctx"><see cref="ExecutionContext"/> instance.</param>
/// <param name="log"><see cref="ILogger"/> instance.</param>
/// <returns>Swagger UI in HTML.</returns>
[OpenApiIgnore]
public static async Task<IActionResult> RenderSwaggerUI([OpenApiHttpTriggerContext] OpenApiHttpTriggerContext context, HttpRequest req, ExecutionContext ctx, ILogger log)
public static async Task<IActionResult> RenderSwaggerUI(
[OpenApiHttpTriggerContext] OpenApiHttpTriggerContext openApiContext,
HttpRequest req, ExecutionContext ctx, ILogger log)
{
log.LogInformation("SwaggerUI page was requested.");
@ -182,7 +192,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
/// <summary>
/// Invokes the HTTP trigger endpoint to render oauth2-redirect.html.
/// </summary>
/// <param name="openApiContext"><see cref="OpenApiHttpTriggerContext"/> instance.</param>
/// <param name="req"><see cref="HttpRequest"/> instance.</param>
/// <param name="ctx"><see cref="ExecutionContext"/> instance.</param>
/// <param name="log"><see cref="ILogger"/> instance.</param>
/// <returns>oauth2-redirect.html.</returns>
[OpenApiIgnore]
public static async Task<IActionResult> RenderOAuth2Redirect([OpenApiHttpTriggerContext] OpenApiHttpTriggerContext context, HttpRequest req, ExecutionContext ctx, ILogger log)
public static async Task<IActionResult> RenderOAuth2Redirect(
[OpenApiHttpTriggerContext] OpenApiHttpTriggerContext openApiContext,
HttpRequest req, ExecutionContext ctx, ILogger log)
{
log.LogInformation("The oauth2-redirect.html page was requested.");
@ -249,13 +262,13 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
log.LogError(ex.Message);
result = ex.Message;
if (context.IsDevelopment)
if (openApiContext.IsDevelopment)
{
result += "\r\n\r\n";
result += ex.StackTrace;

Просмотреть файл

@ -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
{
/// <summary>
@ -35,31 +32,4 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
builder.AddExtension<OpenApiHttpTriggerContextBinding>();
}
}
/// <summary>
/// Binding for injecting the OpenApiTriggerContext during InProc function creation
/// </summary>
[Binding]
[AttributeUsage(AttributeTargets.Parameter)]
public class OpenApiHttpTriggerContextAttribute : Attribute
{
}
/// <summary>
/// Extension to register the [OpenApiHttpTriggerContext] attribute to inject IOpenApHttpTriggerContext
/// </summary>
[Extension(nameof(OpenApiHttpTriggerContextBinding))]
public class OpenApiHttpTriggerContextBinding : IExtensionConfigProvider
{
public void Initialize(ExtensionConfigContext context)
{
var rule = context.AddBindingRule<OpenApiHttpTriggerContextAttribute>();
rule.BindToInput((OpenApiHttpTriggerContextAttribute attr, ValueBindingContext vbContext) =>
{
var httpContext = vbContext.FunctionContext.CreateObjectInstance<OpenApiHttpTriggerContext>();
return Task.FromResult(httpContext);
});
}
}
}

Просмотреть файл

@ -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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations
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 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations
{
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<string>();
(result as string).Should().Be(expected);
@ -162,10 +178,21 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations
{
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<string>();
(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<string>();
(result as string).Should().Be(expected);
@ -180,10 +207,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations
{
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<List<OpenApiServer>>();
(result as List<OpenApiServer>).Should().HaveCount(expected);
@ -200,13 +224,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations
{
Environment.SetEnvironmentVariable("OpenApi__Version", version);
var options = new DefaultOpenApiConfigurationOptions();
var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("GetOpenApiVersion", BindingFlags.NonPublic | BindingFlags.Static);
var result = DefaultOpenApiConfigurationOptions.GetOpenApiVersion();
var result = method.Invoke(options, null);
result.Should().BeOfType<OpenApiVersionType>();
((OpenApiVersionType)result).Should().Be(expected);
result.Should().Be(expected);
}
[DataTestMethod]
@ -216,13 +236,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations
{
Environment.SetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT", environment);
var options = new DefaultOpenApiConfigurationOptions();
var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("IsFunctionsRuntimeEnvironmentDevelopment", BindingFlags.NonPublic | BindingFlags.Static);
var result = DefaultOpenApiConfigurationOptions.IsFunctionsRuntimeEnvironmentDevelopment();
var result = method.Invoke(options, null);
result.Should().BeOfType<bool>();
((bool)result).Should().Be(expected);
result.Should().Be(expected);
}
[DataTestMethod]
@ -234,13 +250,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations
{
Environment.SetEnvironmentVariable("OpenApi__ForceHttp", forceHttps);
var options = new DefaultOpenApiConfigurationOptions();
var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("IsHttpForced", BindingFlags.NonPublic | BindingFlags.Static);
var result = DefaultOpenApiConfigurationOptions.IsHttpForced();
var result = method.Invoke(options, null);
result.Should().BeOfType<bool>();
((bool)result).Should().Be(expected);
result.Should().Be(expected);
}
[DataTestMethod]
@ -252,13 +264,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations
{
Environment.SetEnvironmentVariable("OpenApi__ForceHttps", forceHttps);
var options = new DefaultOpenApiConfigurationOptions();
var method = typeof(DefaultOpenApiConfigurationOptions).GetMethod("IsHttpsForced", BindingFlags.NonPublic | BindingFlags.Static);
var result = DefaultOpenApiConfigurationOptions.IsHttpsForced();
var result = method.Invoke(options, null);
result.Should().BeOfType<bool>();
((bool)result).Should().Be(expected);
result.Should().Be(expected);
}
public void Given_Type_When_Instantiated_Then_It_Should_Return_EmptyListOfDocumentFilters()

Просмотреть файл

@ -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);
}
}
}

Просмотреть файл

@ -6,6 +6,7 @@ using Microsoft.Azure.WebJobs.Extensions.Http;
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
{
[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)
[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(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<OpenApiSettings>("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<OpenApiSettings>("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<OpenApiSettings>("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<OpenApiSettings>("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__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);
Environment.SetEnvironmentVariable("OpenApi__HostNames", hostnames);
var config = ConfigurationResolver.Resolve();
var settings = config.Get<OpenApiSettings>("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<OpenApiSettings>("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<OpenApiSettings>("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<OpenApiSettings>("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<OpenApiSettings>("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<OpenApiSettings>("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<OpenApiSettings>("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<OpenApiSettings>("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<OpenApiSettings>("OpenApi");
settings.BackendProxyUrl.Should().Be(expected);
}
}
}