Dynamic OpenAPI document injection (#90)

This commit is contained in:
Justin Yoo 2021-04-14 08:07:14 +09:00 коммит произвёл GitHub
Родитель 9ba8b98d2e
Коммит 8372253f42
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
23 изменённых файлов: 806 добавлений и 257 удалений

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

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