Dynamic OpenAPI document injection (#90)
This commit is contained in:
Родитель
9ba8b98d2e
Коммит
8372253f42
|
@ -41,12 +41,4 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\IOpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTrigger.cs" />
|
||||
</ItemGroup>
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -38,12 +38,4 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\IOpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTrigger.cs" />
|
||||
</ItemGroup>
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -37,12 +37,4 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\IOpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTrigger.cs" />
|
||||
</ItemGroup>
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -38,12 +38,4 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\IOpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTrigger.cs" />
|
||||
</ItemGroup>
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -47,12 +47,4 @@
|
|||
</ItemGroup> -->
|
||||
<!-- Uncomment this block if you want to use custom UI -->
|
||||
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\IOpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTrigger.cs" />
|
||||
</ItemGroup>
|
||||
<!-- Comment this block if you want to use NuGet package from https://nuget.org -->
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
"AZURE_FUNCTION_PROXY_DISABLE_LOCAL_CALL": "true",
|
||||
|
||||
"OpenApi__HideSwaggerUI": "false",
|
||||
"OpenApi__ApiKey": "",
|
||||
"OpenApi__AuthLevel__Document": "Anonymous",
|
||||
"OpenApi__AuthLevel__UI": "Anonymous",
|
||||
"OpenApi__BackendProxyUrl": "http://localhost:7071"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions
|
||||
|
@ -14,14 +15,15 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions
|
|||
/// </summary>
|
||||
/// <param name="ui"><see cref="ISwaggerUI"/> instance.</param>
|
||||
/// <param name="endpoint">The endpoint of the Swagger document.</param>
|
||||
/// <param name="authLevel">The authorisation level of the Swagger document.</param>
|
||||
/// <param name="authKey">API key of the HTTP endpoint to render OpenAPI document.</param>
|
||||
/// <returns>The OpenAPI UI in HTML.</returns>
|
||||
public static async Task<string> RenderAsync(this Task<ISwaggerUI> ui, string endpoint, string authKey = null)
|
||||
public static async Task<string> RenderAsync(this Task<ISwaggerUI> ui, string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null)
|
||||
{
|
||||
var instance = await ui.ThrowIfNullOrDefault().ConfigureAwait(false);
|
||||
endpoint.ThrowIfNullOrWhiteSpace();
|
||||
|
||||
return await instance.RenderAsync(endpoint, authKey).ConfigureAwait(false);
|
||||
return await instance.RenderAsync(endpoint, authLevel, authKey).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -29,14 +31,15 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions
|
|||
/// </summary>
|
||||
/// <param name="ui"><see cref="ISwaggerUI"/> instance.</param>
|
||||
/// <param name="endpoint">The endpoint of the OAuth2 Redirect page.</param>
|
||||
/// <param name="authLevel">The authorisation level of the Swagger document.</param>
|
||||
/// <param name="authKey">API key of the HTTP endpoint to render OpenAPI document.</param>
|
||||
/// <returns>The OAuth2 Redirect page in HTML.</returns>
|
||||
public static async Task<string> RenderOAuth2RedirectAsync(this Task<ISwaggerUI> ui, string endpoint, string authKey = null)
|
||||
public static async Task<string> RenderOAuth2RedirectAsync(this Task<ISwaggerUI> ui, string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null)
|
||||
{
|
||||
var instance = await ui.ThrowIfNullOrDefault().ConfigureAwait(false);
|
||||
endpoint.ThrowIfNullOrWhiteSpace();
|
||||
|
||||
return await instance.RenderOAuth2RedirectAsync(endpoint, authKey).ConfigureAwait(false);
|
||||
return await instance.RenderOAuth2RedirectAsync(endpoint, authLevel, authKey).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ using System.Reflection;
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions
|
||||
|
@ -46,16 +47,18 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions
|
|||
/// Renders OpenAPI UI in HTML.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The endpoint of the Swagger document.</param>
|
||||
/// <param name="authLevel">The authorisation level of the Swagger document.</param>
|
||||
/// <param name="authKey">API key of the HTTP endpoint to render OpenAPI document.</param>
|
||||
/// <returns>OpenAPI UI in HTML.</returns>
|
||||
Task<string> RenderAsync(string endpoint, string authKey = null);
|
||||
Task<string> RenderAsync(string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null);
|
||||
|
||||
/// <summary>
|
||||
/// Renders OAuth Redirect in HTML.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The endpoint of the OAuth2 Redirect page.</param>
|
||||
/// <param name="authLevel">The authorisation level of the Swagger document.</param>
|
||||
/// <param name="authKey">API key of the HTTP endpoint to render OpenAPI document.</param>
|
||||
/// <returns>OAuth Redirect in HTML.</returns>
|
||||
Task<string> RenderOAuth2RedirectAsync(string endpoint, string authKey = null);
|
||||
Task<string> RenderOAuth2RedirectAsync(string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
@ -125,32 +127,32 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> RenderAsync(string endpoint, string authKey = null)
|
||||
public async Task<string> RenderAsync(string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null)
|
||||
{
|
||||
endpoint.ThrowIfNullOrWhiteSpace();
|
||||
|
||||
var html = await Task.Factory
|
||||
.StartNew(() => this.Render(endpoint, authKey))
|
||||
.StartNew(() => this.Render(endpoint, authLevel, authKey))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> RenderOAuth2RedirectAsync(string endpoint, string authKey = null)
|
||||
public async Task<string> RenderOAuth2RedirectAsync(string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null)
|
||||
{
|
||||
var html = await Task.Factory
|
||||
.StartNew(() => this.RenderOAuth2Redirect(endpoint, authKey))
|
||||
.StartNew(() => this.RenderOAuth2Redirect(endpoint, authLevel, authKey))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
private string Render(string endpoint, string authKey = null)
|
||||
private string Render(string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null)
|
||||
{
|
||||
var swaggerUiTitle = $"{this._info.Title} - Swagger UI";
|
||||
var swaggerUrl = $"{this._baseUrl.TrimEnd('/')}/{endpoint}";
|
||||
if (!string.IsNullOrWhiteSpace(authKey))
|
||||
if (this.IsAuthKeyRequired(authLevel, authKey))
|
||||
{
|
||||
swaggerUrl += $"?code={authKey}";
|
||||
}
|
||||
|
@ -167,10 +169,10 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
private string RenderOAuth2Redirect(string endpoint, string authKey = null)
|
||||
private string RenderOAuth2Redirect(string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null)
|
||||
{
|
||||
var pageUrl = $"{this._baseUrl.TrimEnd('/')}/{endpoint}";
|
||||
if (!string.IsNullOrWhiteSpace(authKey))
|
||||
if (this.IsAuthKeyRequired(authLevel, authKey))
|
||||
{
|
||||
pageUrl += $"?code={authKey}";
|
||||
}
|
||||
|
@ -179,5 +181,20 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core
|
|||
|
||||
return html;
|
||||
}
|
||||
|
||||
private bool IsAuthKeyRequired(AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null)
|
||||
{
|
||||
if (authLevel == AuthorizationLevel.Anonymous)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (authKey.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new InvalidOperationException("API key is required to access OpenAPI document");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the environment variable settings entity for OpenAPI document auth level.
|
||||
/// </summary>
|
||||
public class OpenApiAuthLevelSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="AuthorizationLevel"/> value for OpenAPI document rendering endpoints.
|
||||
/// </summary>
|
||||
public virtual AuthorizationLevel? Document { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="AuthorizationLevel"/> value for Swagger UI page rendering endpoints.
|
||||
/// </summary>
|
||||
public virtual AuthorizationLevel? UI { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the environment variable settings entity for OpenAPI document.
|
||||
/// </summary>
|
||||
public class OpenApiSettings
|
||||
{
|
||||
/// <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 API key to access to OpenAPI document.
|
||||
/// </summary>
|
||||
public virtual string AuthKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenApiAuthLevelSettings"/> object.
|
||||
/// </summary>
|
||||
public virtual OpenApiAuthLevelSettings AuthLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the backend URL for Azure Functions Proxy.
|
||||
/// </summary>
|
||||
public virtual string BackendProxyUrl { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Script.Description;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the metadata entity for HTTP trigger binding.
|
||||
/// </summary>
|
||||
public class HttpBindingMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the binding parameter. Default value is <c>req</c>.
|
||||
/// </summary>
|
||||
[JsonRequired]
|
||||
[JsonProperty("name")]
|
||||
public virtual string Name { get; set; } = "req";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding type. Default value is <c>httpTrigger</c>.
|
||||
/// </summary>
|
||||
[JsonRequired]
|
||||
[JsonProperty("type")]
|
||||
public virtual string Type { get; set; } = "httpTrigger";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding direction. Default value is <see cref="BindingDirection.In"/>.
|
||||
/// </summary>
|
||||
[JsonRequired]
|
||||
[JsonProperty("direction")]
|
||||
public virtual BindingDirection Direction { get; set; } = BindingDirection.In;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding data type.
|
||||
/// </summary>
|
||||
[JsonProperty("dataType", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public virtual DataType? DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP endpoint route template.
|
||||
/// </summary>
|
||||
[JsonProperty("route", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public virtual string Route { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the webhook type, handled by the trigger.
|
||||
/// </summary>
|
||||
[JsonProperty("webHookType", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public virtual string WebHookType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP endpoint authorisation level. Default value is <cref see="AuthorizationLevel.Function"/>.
|
||||
/// </summary>
|
||||
[JsonRequired]
|
||||
[JsonProperty("authLevel")]
|
||||
public virtual AuthorizationLevel AuthLevel { get; set; } = AuthorizationLevel.Function;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of the HTTP verbs. Default values are <c>GET</c> and <c>POST</c>.
|
||||
/// </summary>
|
||||
[JsonRequired]
|
||||
[JsonProperty("methods")]
|
||||
public virtual List<string> Methods { get; set; } = new List<string>() { "GET", "POST" };
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
|
||||
|
@ -56,6 +57,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
|||
/// </summary>
|
||||
NamingStrategy NamingStrategy { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value indicating whether it's in the development environment or not.
|
||||
/// </summary>
|
||||
bool IsDevelopment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the executing assembly.
|
||||
/// </summary>
|
||||
|
@ -63,6 +69,13 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
|||
[Obsolete("This method is obsolete.", error: true)]
|
||||
Assembly GetExecutingAssembly();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the application assembly from the function app directory.
|
||||
/// </summary>
|
||||
/// <param name="functionAppDirectory">Function app directory.</param>
|
||||
/// <param name="appendBin">Value indicating whether to append the "bin" directory or not.</param>
|
||||
IOpenApiHttpTriggerContext SetApplicationAssembly(string functionAppDirectory, bool appendBin = true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="VisitorCollection"/> instance.
|
||||
/// </summary>
|
||||
|
@ -97,6 +110,20 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
|||
/// <returns>Returns the <see cref="OpenApiFormat"/> value.</returns>
|
||||
OpenApiFormat GetOpenApiFormat(OpenApiFormatType format = OpenApiFormatType.Json);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the auth level of the document rendering page endpoint.
|
||||
/// </summary>
|
||||
/// <param name="key">Environment variables key to look for.</param>
|
||||
/// <returns>Returns the auth level of the document rendering page endpoint.</returns>
|
||||
AuthorizationLevel GetDocumentAuthLevel(string key = "OpenApi__AuthLevel__Document");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the auth level of the UI rendering page endpoint.
|
||||
/// </summary>
|
||||
/// <param name="key">Environment variables key to look for.</param>
|
||||
/// <returns>Returns the auth level of the UI rendering page endpoint.</returns>
|
||||
AuthorizationLevel GetUIAuthLevel(string key = "OpenApi__AuthLevel__UI");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the API key for endpoints from environment variables.
|
||||
/// </summary>
|
|
@ -32,10 +32,14 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Azure.WebJobs.Extensions.OpenApi.Core\Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.csproj" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Script.Abstractions" Version="1.0.0-preview" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Azure.WebJobs.Extensions.OpenApi.Core\Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- <ItemGroup>
|
||||
<Content Include="..\..\templates\OpenApiEndpoints\IOpenApiHttpTriggerContext.cs">
|
||||
<Pack>true</Pack>
|
||||
<BuildAction>Compile</BuildAction>
|
||||
|
@ -51,6 +55,6 @@
|
|||
<BuildAction>Compile</BuildAction>
|
||||
<PackagePath>contentFiles\any\netstandard2.0\OpenApi\;</PackagePath>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</ItemGroup> -->
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations;
|
||||
|
@ -16,7 +19,7 @@ using Newtonsoft.Json.Serialization;
|
|||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the context entity for <see cref="OpenApiHttpTrigger"/>.
|
||||
/// This represents the context entity for <see cref="OpenApiTriggerFunctionProvider"/>.
|
||||
/// </summary>
|
||||
[SuppressMessage("Design", "CA1823", Justification = "")]
|
||||
[SuppressMessage("Design", "MEN002", Justification = "")]
|
||||
|
@ -26,17 +29,18 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
|||
[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="OpenApiHttpTrigger"/> class.
|
||||
/// Initializes a new instance of the <see cref="OpenApiTriggerFunctionProvider"/> class.
|
||||
/// </summary>
|
||||
public OpenApiHttpTriggerContext()
|
||||
{
|
||||
this.ApplicationAssembly = this.GetAssembly(this);
|
||||
this.PackageAssembly = this.GetAssembly<ISwaggerUI>();
|
||||
|
||||
this.OpenApiConfigurationOptions = OpenApiConfigurationResolver.Resolve(this.ApplicationAssembly);
|
||||
this.OpenApiCustomUIOptions = OpenApiCustomUIResolver.Resolve(this.ApplicationAssembly);
|
||||
|
||||
var host = HostJsonResolver.Resolve();
|
||||
this.HttpSettings = host.GetHttpSettings();
|
||||
|
||||
|
@ -49,16 +53,49 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Assembly ApplicationAssembly { get; }
|
||||
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; }
|
||||
public virtual IOpenApiConfigurationOptions OpenApiConfigurationOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this._configOptions.IsNullOrDefault())
|
||||
{
|
||||
this._configOptions = OpenApiConfigurationResolver.Resolve(this.ApplicationAssembly);
|
||||
}
|
||||
|
||||
return this._configOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IOpenApiCustomUIOptions OpenApiCustomUIOptions { get; }
|
||||
public virtual IOpenApiCustomUIOptions OpenApiCustomUIOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this._uiOptions.IsNullOrDefault())
|
||||
{
|
||||
this._uiOptions = OpenApiCustomUIResolver.Resolve(this.ApplicationAssembly);
|
||||
}
|
||||
|
||||
return this._uiOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual HttpSettings HttpSettings { get; }
|
||||
|
@ -72,6 +109,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
|||
/// <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()
|
||||
|
@ -81,6 +121,35 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
|||
return assembly;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IOpenApiHttpTriggerContext SetApplicationAssembly(string functionAppDirectory, bool appendBin = true)
|
||||
{
|
||||
if (!this._dllpath.IsNullOrWhiteSpace())
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var file = Directory.GetFiles(functionAppDirectory, "*.deps.json", SearchOption.TopDirectoryOnly).FirstOrDefault();
|
||||
if (file.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new InvalidOperationException("Invalid function app directory");
|
||||
}
|
||||
|
||||
var pattern = functionAppDirectory;
|
||||
var replacement = $"{functionAppDirectory.TrimEnd(Path.DirectorySeparatorChar)}";
|
||||
if (appendBin)
|
||||
{
|
||||
replacement += $"{Path.DirectorySeparatorChar}bin";
|
||||
}
|
||||
|
||||
var dllpath = file.Replace(pattern, replacement)
|
||||
.Replace("deps.json", "dll");
|
||||
|
||||
this._dllpath = dllpath;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual VisitorCollection GetVisitorCollection()
|
||||
{
|
||||
|
@ -126,6 +195,24 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
|||
return format.ToOpenApiFormat();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual AuthorizationLevel GetDocumentAuthLevel(string key = "OpenApi__AuthLevel__Document")
|
||||
{
|
||||
var value = Environment.GetEnvironmentVariable(key);
|
||||
var parsed = Enum.TryParse<AuthorizationLevel>(value, out var result) ? result : AuthorizationLevel.Anonymous;
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual AuthorizationLevel GetUIAuthLevel(string key = "OpenApi__AuthLevel__UI")
|
||||
{
|
||||
var value = Environment.GetEnvironmentVariable(key);
|
||||
var parsed = Enum.TryParse<AuthorizationLevel>(value, out var result) ? result : AuthorizationLevel.Anonymous;
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string GetSwaggerAuthKey(string key = "OpenApi__ApiKey")
|
||||
{
|
||||
|
@ -153,5 +240,12 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
|||
|
||||
return assembly;
|
||||
}
|
||||
|
||||
private Assembly GetAssembly(string dllpath)
|
||||
{
|
||||
var assembly = Assembly.LoadFile(dllpath);
|
||||
|
||||
return assembly;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations;
|
||||
using Microsoft.Azure.WebJobs.Script.Description;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the function provider entity for Open API HTTP triggers.
|
||||
/// </summary>
|
||||
public partial class OpenApiTriggerFunctionProvider : IFunctionProvider
|
||||
{
|
||||
private const string RenderSwaggerDocumentKey = "RenderSwaggerDocument";
|
||||
private const string RenderOpenApiDocumentKey = "RenderOpenApiDocument";
|
||||
private const string RenderSwaggerUIKey = "RenderSwaggerUI";
|
||||
private const string RenderOAuth2RedirectKey = "RenderOAuth2Redirect";
|
||||
|
||||
private readonly OpenApiSettings _settings;
|
||||
private readonly Dictionary<string, HttpBindingMetadata> _bindings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OpenApiTriggerFunctionProvider"/> class.
|
||||
/// </summary>
|
||||
public OpenApiTriggerFunctionProvider(OpenApiSettings settings)
|
||||
{
|
||||
this._settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
this._bindings = this.SetupOpenApiHttpBindings();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImmutableDictionary<string, ImmutableArray<string>> FunctionErrors { get; } = new Dictionary<string, ImmutableArray<string>>().ToImmutableDictionary();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ImmutableArray<FunctionMetadata>> GetFunctionMetadataAsync()
|
||||
{
|
||||
var functionMetadataList = this.GetFunctionMetadataList();
|
||||
|
||||
return await Task.FromResult(functionMetadataList.ToImmutableArray()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Dictionary<string, HttpBindingMetadata> SetupOpenApiHttpBindings()
|
||||
{
|
||||
var renderSwaggerDocument = new HttpBindingMetadata()
|
||||
{
|
||||
Methods = { HttpMethods.Get },
|
||||
Route = "swagger.{extension}",
|
||||
AuthLevel = this._settings.AuthLevel?.Document ?? AuthorizationLevel.Anonymous,
|
||||
};
|
||||
|
||||
var renderOpenApiDocument = new HttpBindingMetadata()
|
||||
{
|
||||
Methods = { HttpMethods.Get },
|
||||
Route = "openapi/{version}.{extension}",
|
||||
AuthLevel = this._settings.AuthLevel?.Document ?? AuthorizationLevel.Anonymous,
|
||||
};
|
||||
|
||||
var renderOAuth2Redirect = new HttpBindingMetadata()
|
||||
{
|
||||
Methods = { HttpMethods.Get },
|
||||
Route = "oauth2-redirect.html",
|
||||
AuthLevel = this._settings.AuthLevel?.UI ?? AuthorizationLevel.Anonymous,
|
||||
};
|
||||
|
||||
var bindings = new Dictionary<string, HttpBindingMetadata>()
|
||||
{
|
||||
{ RenderSwaggerDocumentKey, renderSwaggerDocument },
|
||||
{ RenderOpenApiDocumentKey, renderOpenApiDocument },
|
||||
{ RenderOAuth2RedirectKey, renderOAuth2Redirect },
|
||||
};
|
||||
|
||||
if (!this._settings.HideSwaggerUI)
|
||||
{
|
||||
var renderSwaggerUI = new HttpBindingMetadata()
|
||||
{
|
||||
Methods = { HttpMethods.Get },
|
||||
Route = "swagger/ui",
|
||||
AuthLevel = this._settings.AuthLevel?.UI ?? AuthorizationLevel.Anonymous,
|
||||
};
|
||||
|
||||
bindings.Add(RenderSwaggerUIKey, renderSwaggerUI);
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
private List<FunctionMetadata> GetFunctionMetadataList()
|
||||
{
|
||||
var list = new List<FunctionMetadata>()
|
||||
{
|
||||
this.GetFunctionMetadata(RenderSwaggerDocumentKey),
|
||||
this.GetFunctionMetadata(RenderOpenApiDocumentKey),
|
||||
this.GetFunctionMetadata(RenderOAuth2RedirectKey),
|
||||
};
|
||||
|
||||
if (!this._settings.HideSwaggerUI)
|
||||
{
|
||||
list.Add(this.GetFunctionMetadata(RenderSwaggerUIKey));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private FunctionMetadata GetFunctionMetadata(string functionName)
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var functionMetadata = new FunctionMetadata()
|
||||
{
|
||||
Name = functionName,
|
||||
FunctionDirectory = null,
|
||||
ScriptFile = $"assembly:{assembly.FullName}",
|
||||
EntryPoint = $"{assembly.GetName().Name}.{typeof(OpenApiTriggerFunctionProvider).Name}.{functionName}",
|
||||
Language = "DotNetAssembly"
|
||||
};
|
||||
|
||||
var jo = JObject.FromObject(this._bindings[functionName]);
|
||||
var binding = BindingMetadata.Create(jo);
|
||||
functionMetadata.Bindings.Add(binding);
|
||||
|
||||
return functionMetadata;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the function provider entity for Open API HTTP triggers.
|
||||
/// </summary>
|
||||
public partial class OpenApiTriggerFunctionProvider
|
||||
{
|
||||
private readonly static IOpenApiHttpTriggerContext context = new OpenApiHttpTriggerContext();
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the HTTP trigger endpoint to get Open API document.
|
||||
/// </summary>
|
||||
/// <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>Open API document in a format of either JSON or YAML.</returns>
|
||||
[OpenApiIgnore]
|
||||
public static async Task<IActionResult> RenderSwaggerDocument(HttpRequest req, string extension, ExecutionContext ctx, ILogger log)
|
||||
{
|
||||
log.LogInformation($"swagger.{extension} was requested.");
|
||||
|
||||
var result = default(string);
|
||||
var content = default(ContentResult);
|
||||
try
|
||||
{
|
||||
result = await context.SetApplicationAssembly(ctx.FunctionAppDirectory)
|
||||
.Document
|
||||
.InitialiseDocument()
|
||||
.AddMetadata(context.OpenApiConfigurationOptions.Info)
|
||||
.AddServer(req, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions)
|
||||
.AddNamingStrategy(context.NamingStrategy)
|
||||
.AddVisitors(context.GetVisitorCollection())
|
||||
.Build(context.ApplicationAssembly)
|
||||
.RenderAsync(context.GetOpenApiSpecVersion(context.OpenApiConfigurationOptions.OpenApiVersion), context.GetOpenApiFormat(extension))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = context.GetOpenApiFormat(extension).GetContentType(),
|
||||
StatusCode = (int)HttpStatusCode.OK,
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogError(ex.Message);
|
||||
|
||||
result = ex.Message;
|
||||
if (context.IsDevelopment)
|
||||
{
|
||||
result += "\r\n\r\n";
|
||||
result += ex.StackTrace;
|
||||
}
|
||||
content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = "text/plain",
|
||||
StatusCode = (int)HttpStatusCode.InternalServerError,
|
||||
};
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the HTTP trigger endpoint to get Open API document.
|
||||
/// </summary>
|
||||
/// <param name="req"><see cref="HttpRequest"/> instance.</param>
|
||||
/// <param name="version">Open API 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>
|
||||
/// <param name="ctx"><see cref="ExecutionContext"/> instance.</param>
|
||||
/// <param name="log"><see cref="ILogger"/> instance.</param>
|
||||
/// <returns>Open API document in a format of either JSON or YAML.</returns>
|
||||
[OpenApiIgnore]
|
||||
public static async Task<IActionResult> RenderOpenApiDocument(HttpRequest req, string version, string extension, ExecutionContext ctx, ILogger log)
|
||||
{
|
||||
log.LogInformation($"{version}.{extension} was requested.");
|
||||
|
||||
var result = default(string);
|
||||
var content = default(ContentResult);
|
||||
try
|
||||
{
|
||||
result = await context.SetApplicationAssembly(ctx.FunctionAppDirectory)
|
||||
.Document
|
||||
.InitialiseDocument()
|
||||
.AddMetadata(context.OpenApiConfigurationOptions.Info)
|
||||
.AddServer(req, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions)
|
||||
.AddNamingStrategy(context.NamingStrategy)
|
||||
.AddVisitors(context.GetVisitorCollection())
|
||||
.Build(context.ApplicationAssembly)
|
||||
.RenderAsync(context.GetOpenApiSpecVersion(version), context.GetOpenApiFormat(extension))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = context.GetOpenApiFormat(extension).GetContentType(),
|
||||
StatusCode = (int)HttpStatusCode.OK,
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogError(ex.Message);
|
||||
|
||||
result = ex.Message;
|
||||
if (context.IsDevelopment)
|
||||
{
|
||||
result += "\r\n\r\n";
|
||||
result += ex.StackTrace;
|
||||
}
|
||||
content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = "text/plain",
|
||||
StatusCode = (int)HttpStatusCode.InternalServerError,
|
||||
};
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the HTTP trigger endpoint to render Swagger UI in HTML.
|
||||
/// </summary>
|
||||
/// <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(HttpRequest req, ExecutionContext ctx, ILogger log)
|
||||
{
|
||||
log.LogInformation("SwaggerUI page was requested.");
|
||||
|
||||
var result = default(string);
|
||||
var content = default(ContentResult);
|
||||
try
|
||||
{
|
||||
result = await context.SetApplicationAssembly(ctx.FunctionAppDirectory)
|
||||
.SwaggerUI
|
||||
.AddMetadata(context.OpenApiConfigurationOptions.Info)
|
||||
.AddServer(req, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions)
|
||||
.BuildAsync(context.PackageAssembly, context.OpenApiCustomUIOptions)
|
||||
.RenderAsync("swagger.json", context.GetDocumentAuthLevel(), context.GetSwaggerAuthKey())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = "text/html",
|
||||
StatusCode = (int)HttpStatusCode.OK,
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogError(ex.Message);
|
||||
|
||||
result = ex.Message;
|
||||
if (context.IsDevelopment)
|
||||
{
|
||||
result += "\r\n\r\n";
|
||||
result += ex.StackTrace;
|
||||
}
|
||||
content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = "text/plain",
|
||||
StatusCode = (int)HttpStatusCode.InternalServerError,
|
||||
};
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the HTTP trigger endpoint to render oauth2-redirect.html.
|
||||
/// </summary>
|
||||
/// <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(HttpRequest req, ExecutionContext ctx, ILogger log)
|
||||
{
|
||||
log.LogInformation("The oauth2-redirect.html page was requested.");
|
||||
|
||||
var result = default(string);
|
||||
var content = default(ContentResult);
|
||||
try
|
||||
{
|
||||
result = await context.SetApplicationAssembly(ctx.FunctionAppDirectory)
|
||||
.SwaggerUI
|
||||
.AddServer(req, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions)
|
||||
.BuildOAuth2RedirectAsync(context.PackageAssembly)
|
||||
.RenderOAuth2RedirectAsync("oauth2-redirect.html", context.GetDocumentAuthLevel(), context.GetSwaggerAuthKey())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = "text/html",
|
||||
StatusCode = (int)HttpStatusCode.OK,
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogError(ex.Message);
|
||||
|
||||
result = ex.Message;
|
||||
if (context.IsDevelopment)
|
||||
{
|
||||
result += "\r\n\r\n";
|
||||
result += ex.StackTrace;
|
||||
}
|
||||
content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = "text/plain",
|
||||
StatusCode = (int)HttpStatusCode.InternalServerError,
|
||||
};
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.Azure.WebJobs.Extensions.OpenApi;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configuration.AppSettings.Extensions;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configuration.AppSettings.Resolvers;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations;
|
||||
using Microsoft.Azure.WebJobs.Hosting;
|
||||
using Microsoft.Azure.WebJobs.Script.Description;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
[assembly: WebJobsStartup(typeof(OpenApiWebJobsStartup))]
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the startup entity for Open API endpoints registration
|
||||
/// </summary>
|
||||
public class OpenApiWebJobsStartup : IWebJobsStartup
|
||||
{
|
||||
private const string OpenApiSettingsKey = "OpenApi";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(IWebJobsBuilder builder)
|
||||
{
|
||||
var config = ConfigurationResolver.Resolve();
|
||||
var settings = config.Get<OpenApiSettings>(OpenApiSettingsKey);
|
||||
|
||||
builder.Services.AddSingleton(settings);
|
||||
builder.Services.AddSingleton<IFunctionProvider, OpenApiTriggerFunctionProvider>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
using System.Net;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the HTTP trigger entity for OpenAPI documents.
|
||||
/// </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 static class OpenApiHttpTrigger
|
||||
{
|
||||
private const string V2 = "v2";
|
||||
private const string V3 = "v3";
|
||||
private const string JSON = "json";
|
||||
private const string YAML = "yaml";
|
||||
|
||||
private readonly static IOpenApiHttpTriggerContext context = new OpenApiHttpTriggerContext();
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the HTTP trigger endpoint to get OpenAPI document.
|
||||
/// </summary>
|
||||
/// <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="log"><see cref="ILogger"/> instance.</param>
|
||||
/// <returns>OpenAPI document in a format of either JSON or YAML.</returns>
|
||||
[FunctionName(nameof(OpenApiHttpTrigger.RenderSwaggerDocument))]
|
||||
[OpenApiIgnore]
|
||||
public static async Task<IActionResult> RenderSwaggerDocument(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "swagger.{extension}")] HttpRequest req,
|
||||
string extension,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation($"swagger.{extension} was requested.");
|
||||
|
||||
var result = await context.Document
|
||||
.InitialiseDocument()
|
||||
.AddMetadata(context.OpenApiConfigurationOptions.Info)
|
||||
.AddServer(req, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions)
|
||||
.AddNamingStrategy(context.NamingStrategy)
|
||||
.AddVisitors(context.GetVisitorCollection())
|
||||
.Build(context.ApplicationAssembly)
|
||||
.RenderAsync(context.GetOpenApiSpecVersion(context.OpenApiConfigurationOptions.OpenApiVersion), context.GetOpenApiFormat(extension))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = context.GetOpenApiFormat(extension).GetContentType(),
|
||||
StatusCode = (int)HttpStatusCode.OK
|
||||
};
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the HTTP trigger endpoint to get OpenAPI document.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <param name="log"><see cref="ILogger"/> instance.</param>
|
||||
/// <returns>OpenAPI document in a format of either JSON or YAML.</returns>
|
||||
[FunctionName(nameof(OpenApiHttpTrigger.RenderOpenApiDocument))]
|
||||
[OpenApiIgnore]
|
||||
public static async Task<IActionResult> RenderOpenApiDocument(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "openapi/{version}.{extension}")] HttpRequest req,
|
||||
string version,
|
||||
string extension,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation($"{version}.{extension} was requested.");
|
||||
|
||||
var result = await context.Document
|
||||
.InitialiseDocument()
|
||||
.AddMetadata(context.OpenApiConfigurationOptions.Info)
|
||||
.AddServer(req, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions)
|
||||
.AddNamingStrategy(context.NamingStrategy)
|
||||
.AddVisitors(context.GetVisitorCollection())
|
||||
.Build(context.ApplicationAssembly)
|
||||
.RenderAsync(context.GetOpenApiSpecVersion(version), context.GetOpenApiFormat(extension))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = context.GetOpenApiFormat(extension).GetContentType(),
|
||||
StatusCode = (int)HttpStatusCode.OK
|
||||
};
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the HTTP trigger endpoint to render Swagger UI in HTML.
|
||||
/// </summary>
|
||||
/// <param name="req"><see cref="HttpRequest"/> instance.</param>
|
||||
/// <param name="log"><see cref="ILogger"/> instance.</param>
|
||||
/// <returns>Swagger UI in HTML.</returns>
|
||||
[FunctionName(nameof(OpenApiHttpTrigger.RenderSwaggerUI))]
|
||||
[OpenApiIgnore]
|
||||
public static async Task<IActionResult> RenderSwaggerUI(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "swagger/ui")] HttpRequest req,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation("SwaggerUI page was requested.");
|
||||
|
||||
var result = await context.SwaggerUI
|
||||
.AddMetadata(context.OpenApiConfigurationOptions.Info)
|
||||
.AddServer(req, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions)
|
||||
.BuildAsync(context.PackageAssembly, context.OpenApiCustomUIOptions)
|
||||
.RenderAsync("swagger.json", context.GetSwaggerAuthKey())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = "text/html",
|
||||
StatusCode = (int)HttpStatusCode.OK
|
||||
};
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the HTTP trigger endpoint to render oauth2-redirect.html.
|
||||
/// </summary>
|
||||
/// <param name="req"><see cref="HttpRequest"/> instance.</param>
|
||||
/// <param name="log"><see cref="ILogger"/> instance.</param>
|
||||
/// <returns>oauth2-redirect.html.</returns>
|
||||
[FunctionName(nameof(OpenApiHttpTrigger.RenderOAuth2Redirect))]
|
||||
[OpenApiIgnore]
|
||||
public static async Task<IActionResult> RenderOAuth2Redirect(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "oauth2-redirect.html")] HttpRequest req,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation("The oauth2-redirect.html page was requested.");
|
||||
|
||||
var result = await context.SwaggerUI
|
||||
.AddServer(req, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions)
|
||||
.BuildOAuth2RedirectAsync(context.PackageAssembly)
|
||||
.RenderOAuth2RedirectAsync("oauth2-redirect.html", context.GetSwaggerAuthKey())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var content = new ContentResult()
|
||||
{
|
||||
Content = result,
|
||||
ContentType = "text/html",
|
||||
StatusCode = (int)HttpStatusCode.OK
|
||||
};
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions;
|
||||
|
||||
using FluentAssertions;
|
||||
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
@ -35,7 +35,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Extensions
|
|||
var rendered = "hello world";
|
||||
|
||||
var ui = new Mock<ISwaggerUI>();
|
||||
ui.Setup(p => p.RenderAsync(It.IsAny<string>(), It.IsAny<string>())).ReturnsAsync(rendered);
|
||||
ui.Setup(p => p.RenderAsync(It.IsAny<string>(), It.IsAny<AuthorizationLevel>(), It.IsAny<string>())).ReturnsAsync(rendered);
|
||||
|
||||
var task = Task.FromResult(ui.Object);
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Extensions
|
|||
var rendered = "hello world";
|
||||
|
||||
var ui = new Mock<ISwaggerUI>();
|
||||
ui.Setup(p => p.RenderOAuth2RedirectAsync(It.IsAny<string>(), It.IsAny<string>())).ReturnsAsync(rendered);
|
||||
ui.Setup(p => p.RenderOAuth2RedirectAsync(It.IsAny<string>(), It.IsAny<AuthorizationLevel>(), It.IsAny<string>())).ReturnsAsync(rendered);
|
||||
|
||||
var task = Task.FromResult(ui.Object);
|
||||
|
||||
|
|
|
@ -36,10 +36,4 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\IOpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTriggerContext.cs" />
|
||||
<Compile Include="..\..\templates\OpenApiEndpoints\OpenApiHttpTrigger.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using FluentAssertions;
|
||||
|
@ -11,25 +14,47 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests
|
|||
[TestClass]
|
||||
public class OpenApiHttpTriggerContextTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Given_Type_When_Initiated_Then_It_Should_Return_ApplicationAssembly()
|
||||
[DataTestMethod]
|
||||
[DataRow(typeof(OpenApiHttpTriggerContextTests))]
|
||||
public void Given_Type_When_Initiated_Then_It_Should_Return_ApplicationAssemblyWithGivenType(Type type)
|
||||
{
|
||||
var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;
|
||||
var context = new OpenApiHttpTriggerContext();
|
||||
|
||||
var assembly = context.ApplicationAssembly;
|
||||
var assembly = context.SetApplicationAssembly(location, false)
|
||||
.ApplicationAssembly;
|
||||
|
||||
assembly.DefinedTypes.Should().Contain(typeof(IOpenApiHttpTriggerContext).GetTypeInfo());
|
||||
assembly.DefinedTypes.Should().Contain(typeof(OpenApiHttpTriggerContext).GetTypeInfo());
|
||||
assembly.DefinedTypes.Should().Contain(typeof(OpenApiHttpTrigger).GetTypeInfo());
|
||||
assembly.DefinedTypes.Should().NotContain(typeof(ISwaggerUI).GetTypeInfo());
|
||||
var ti = type.GetTypeInfo();
|
||||
|
||||
assembly.DefinedTypes.Select(p => p.FullName).Should().Contain(ti.FullName);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(typeof(IOpenApiHttpTriggerContext))]
|
||||
[DataRow(typeof(OpenApiHttpTriggerContext))]
|
||||
[DataRow(typeof(OpenApiTriggerFunctionProvider))]
|
||||
[DataRow(typeof(ISwaggerUI))]
|
||||
public void Given_Type_When_Initiated_Then_It_Should_NotReturn_ApplicationAssemblyWithGivenType(Type type)
|
||||
{
|
||||
var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;
|
||||
var context = new OpenApiHttpTriggerContext();
|
||||
|
||||
var assembly = context.SetApplicationAssembly(location, false)
|
||||
.ApplicationAssembly;
|
||||
|
||||
var ti = type.GetTypeInfo();
|
||||
|
||||
assembly.DefinedTypes.Select(p => p.FullName).Should().NotContain(ti.FullName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Given_Type_When_Initiated_Then_It_Should_Return_PackageAssembly()
|
||||
{
|
||||
var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;
|
||||
var context = new OpenApiHttpTriggerContext();
|
||||
|
||||
var assembly = context.PackageAssembly;
|
||||
var assembly = context.SetApplicationAssembly(location, false)
|
||||
.PackageAssembly;
|
||||
|
||||
assembly.DefinedTypes.Should().Contain(typeof(ISwaggerUI).GetTypeInfo());
|
||||
assembly.DefinedTypes.Should().NotContain(typeof(IOpenApiHttpTriggerContext).GetTypeInfo());
|
||||
|
@ -38,9 +63,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests
|
|||
[TestMethod]
|
||||
public void Given_Type_When_Initiated_Then_It_Should_Return_OpenApiConfigurationOptions()
|
||||
{
|
||||
var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;
|
||||
var context = new OpenApiHttpTriggerContext();
|
||||
|
||||
var options = context.OpenApiConfigurationOptions;
|
||||
var options = context.SetApplicationAssembly(location, false)
|
||||
.OpenApiConfigurationOptions;
|
||||
|
||||
options.Info.Version.Should().Be("1.0.0");
|
||||
options.Servers.Count.Should().Be(0);
|
||||
|
@ -49,9 +76,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests
|
|||
[TestMethod]
|
||||
public void Given_Type_When_Initiated_Then_It_Should_Return_OpenApiCustomUIOptions()
|
||||
{
|
||||
var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;
|
||||
var context = new OpenApiHttpTriggerContext();
|
||||
|
||||
var options = context.OpenApiCustomUIOptions;
|
||||
var options = context.SetApplicationAssembly(location, false)
|
||||
.OpenApiCustomUIOptions;
|
||||
|
||||
options.CustomStylesheetPath.Should().Be("dist.custom.css");
|
||||
options.CustomJavaScriptPath.Should().Be("dist.custom.js");
|
||||
|
@ -60,9 +89,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests
|
|||
[TestMethod]
|
||||
public void Given_Type_When_Initiated_Then_It_Should_Return_HttpSettings()
|
||||
{
|
||||
var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;
|
||||
var context = new OpenApiHttpTriggerContext();
|
||||
|
||||
var settings = context.HttpSettings;
|
||||
var settings = context.SetApplicationAssembly(location, false)
|
||||
.HttpSettings;
|
||||
|
||||
settings.RoutePrefix.Should().Be("api");
|
||||
|
||||
|
@ -73,9 +104,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests
|
|||
[DataRow("v3", OpenApiSpecVersion.OpenApi3_0)]
|
||||
public void Given_Type_When_GetOpenApiSpecVersion_Invoked_Then_It_Should_Return_Result(string version, OpenApiSpecVersion expected)
|
||||
{
|
||||
var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;
|
||||
var context = new OpenApiHttpTriggerContext();
|
||||
|
||||
var result = context.GetOpenApiSpecVersion(version);
|
||||
var result = context.SetApplicationAssembly(location, false)
|
||||
.GetOpenApiSpecVersion(version);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
@ -85,9 +118,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests
|
|||
[DataRow("json", OpenApiFormat.Json)]
|
||||
public void Given_Type_When_GetOpenApiSpecVersion_Invoked_Then_It_Should_Return_Result(string format, OpenApiFormat expected)
|
||||
{
|
||||
var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;
|
||||
var context = new OpenApiHttpTriggerContext();
|
||||
|
||||
var result = context.GetOpenApiFormat(format);
|
||||
var result = context.SetApplicationAssembly(location, false)
|
||||
.GetOpenApiFormat(format);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using FluentAssertions;
|
||||
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class OpenApiTriggerFunctionProviderTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Given_Null_When_Instantiated_Then_It_Should_Throw_Exception()
|
||||
{
|
||||
Action action = () => new OpenApiTriggerFunctionProvider(null);
|
||||
|
||||
action.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(true, 3)]
|
||||
[DataRow(false, 4)]
|
||||
public async Task Given_HideSwaggerUI_When_GetFunctionMetadataAsync_Invoked_Then_It_Should_Return_Result(bool hideSwaggerUI, int expected)
|
||||
{
|
||||
var settings = new Mock<OpenApiSettings>();
|
||||
settings.SetupGet(p => p.HideSwaggerUI).Returns(hideSwaggerUI);
|
||||
|
||||
var provider = new OpenApiTriggerFunctionProvider(settings.Object);
|
||||
|
||||
var result = await provider.GetFunctionMetadataAsync().ConfigureAwait(false);
|
||||
|
||||
result.Should().HaveCount(expected);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(AuthorizationLevel.Anonymous, AuthorizationLevel.Anonymous)]
|
||||
[DataRow(AuthorizationLevel.Anonymous, AuthorizationLevel.Function)]
|
||||
[DataRow(AuthorizationLevel.Function, AuthorizationLevel.Anonymous)]
|
||||
[DataRow(AuthorizationLevel.Function, AuthorizationLevel.Function)]
|
||||
public async Task Given_AuthLevel_When_GetFunctionMetadataAsync_Invoked_Then_It_Should_Return_Result(AuthorizationLevel authLevelDoc, AuthorizationLevel authLevelUI)
|
||||
{
|
||||
var authLevelSettings = new Mock<OpenApiAuthLevelSettings>();
|
||||
authLevelSettings.SetupGet(p => p.Document).Returns(authLevelDoc);
|
||||
authLevelSettings.SetupGet(p => p.UI).Returns(authLevelUI);
|
||||
|
||||
var settings = new Mock<OpenApiSettings>();
|
||||
settings.SetupGet(p => p.HideSwaggerUI).Returns(false);
|
||||
settings.SetupGet(p => p.AuthLevel).Returns(authLevelSettings.Object);
|
||||
|
||||
var provider = new OpenApiTriggerFunctionProvider(settings.Object);
|
||||
|
||||
var result = await provider.GetFunctionMetadataAsync().ConfigureAwait(false);
|
||||
|
||||
result.Single(p => p.Name == "RenderSwaggerDocument").Bindings.First().Raw.Value<int>("authLevel").Should().Be((int)authLevelDoc);
|
||||
result.Single(p => p.Name == "RenderOpenApiDocument").Bindings.First().Raw.Value<int>("authLevel").Should().Be((int)authLevelDoc);
|
||||
result.Single(p => p.Name == "RenderSwaggerUI").Bindings.First().Raw.Value<int>("authLevel").Should().Be((int)authLevelUI);
|
||||
result.Single(p => p.Name == "RenderOAuth2Redirect").Bindings.First().Raw.Value<int>("authLevel").Should().Be((int)authLevelUI);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче