diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V1Proxy/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V1Proxy.csproj b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V1Proxy/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V1Proxy.csproj index b3a4000..f9e4fe4 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V1Proxy/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V1Proxy.csproj +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V1Proxy/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V1Proxy.csproj @@ -41,12 +41,4 @@ - - - - - - - - diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2IoC/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2IoC.csproj b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2IoC/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2IoC.csproj index b383c41..a06b044 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2IoC/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2IoC.csproj +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2IoC/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2IoC.csproj @@ -38,12 +38,4 @@ - - - - - - - - diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2Static.csproj b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2Static.csproj index 0435ca0..39211af 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2Static.csproj +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V2Static.csproj @@ -37,12 +37,4 @@ - - - - - - - - diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3IoC/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3IoC.csproj b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3IoC/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3IoC.csproj index 251b82a..aba7872 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3IoC/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3IoC.csproj +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3IoC/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3IoC.csproj @@ -38,12 +38,4 @@ - - - - - - - - diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.csproj b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.csproj index 461c6ac..518b88f 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.csproj +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.csproj @@ -47,12 +47,4 @@ --> - - - - - - - - diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/local.settings.json b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/local.settings.json index fd5c20f..2ffcd3e 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/local.settings.json +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/local.settings.json @@ -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" } } diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Extensions/SwaggerUIExtensions.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Extensions/SwaggerUIExtensions.cs index d55242b..77346b5 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Extensions/SwaggerUIExtensions.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Extensions/SwaggerUIExtensions.cs @@ -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 /// /// instance. /// The endpoint of the Swagger document. + /// The authorisation level of the Swagger document. /// API key of the HTTP endpoint to render OpenAPI document. /// The OpenAPI UI in HTML. - public static async Task RenderAsync(this Task ui, string endpoint, string authKey = null) + public static async Task RenderAsync(this Task 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); } /// @@ -29,14 +31,15 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions /// /// instance. /// The endpoint of the OAuth2 Redirect page. + /// The authorisation level of the Swagger document. /// API key of the HTTP endpoint to render OpenAPI document. /// The OAuth2 Redirect page in HTML. - public static async Task RenderOAuth2RedirectAsync(this Task ui, string endpoint, string authKey = null) + public static async Task RenderOAuth2RedirectAsync(this Task 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); } } } diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/ISwaggerUI.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/ISwaggerUI.cs index 5e1509a..c02a02d 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/ISwaggerUI.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/ISwaggerUI.cs @@ -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. /// /// The endpoint of the Swagger document. + /// The authorisation level of the Swagger document. /// API key of the HTTP endpoint to render OpenAPI document. /// OpenAPI UI in HTML. - Task RenderAsync(string endpoint, string authKey = null); + Task RenderAsync(string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null); /// /// Renders OAuth Redirect in HTML. /// /// The endpoint of the OAuth2 Redirect page. + /// The authorisation level of the Swagger document. /// API key of the HTTP endpoint to render OpenAPI document. /// OAuth Redirect in HTML. - Task RenderOAuth2RedirectAsync(string endpoint, string authKey = null); + Task RenderOAuth2RedirectAsync(string endpoint, AuthorizationLevel authLevel = AuthorizationLevel.Anonymous, string authKey = null); } } diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/SwaggerUI.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/SwaggerUI.cs index 72b6756..6793f98 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/SwaggerUI.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/SwaggerUI.cs @@ -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 } /// - public async Task RenderAsync(string endpoint, string authKey = null) + public async Task 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; } /// - public async Task RenderOAuth2RedirectAsync(string endpoint, string authKey = null) + public async Task 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 } /// - 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; + } } } diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiAuthLevelSettings.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiAuthLevelSettings.cs new file mode 100644 index 0000000..55c53d2 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiAuthLevelSettings.cs @@ -0,0 +1,20 @@ +using Microsoft.Azure.WebJobs.Extensions.Http; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations +{ + /// + /// This represents the environment variable settings entity for OpenAPI document auth level. + /// + public class OpenApiAuthLevelSettings + { + /// + /// Gets or sets the value for OpenAPI document rendering endpoints. + /// + public virtual AuthorizationLevel? Document { get; set; } + + /// + /// Gets or sets the value for Swagger UI page rendering endpoints. + /// + public virtual AuthorizationLevel? UI { get; set; } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiSettings.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiSettings.cs new file mode 100644 index 0000000..71883d2 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Configurations/OpenApiSettings.cs @@ -0,0 +1,28 @@ +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Configurations +{ + /// + /// This represents the environment variable settings entity for OpenAPI document. + /// + public class OpenApiSettings + { + /// + /// Gets or sets the value indicating whether to hide the Swagger UI page or not. + /// + public virtual bool HideSwaggerUI { get; set; } + + /// + /// Gets or sets the API key to access to OpenAPI document. + /// + public virtual string AuthKey { get; set; } + + /// + /// Gets or sets the object. + /// + public virtual OpenApiAuthLevelSettings AuthLevel { get; set; } + + /// + /// Gets or sets the backend URL for Azure Functions Proxy. + /// + public virtual string BackendProxyUrl { get; set; } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/HttpBindingMetadata.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/HttpBindingMetadata.cs new file mode 100644 index 0000000..96dd9ef --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/HttpBindingMetadata.cs @@ -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 +{ + /// + /// This represents the metadata entity for HTTP trigger binding. + /// + public class HttpBindingMetadata + { + /// + /// Gets or sets the name of the binding parameter. Default value is req. + /// + [JsonRequired] + [JsonProperty("name")] + public virtual string Name { get; set; } = "req"; + + /// + /// Gets or sets the binding type. Default value is httpTrigger. + /// + [JsonRequired] + [JsonProperty("type")] + public virtual string Type { get; set; } = "httpTrigger"; + + /// + /// Gets or sets the binding direction. Default value is . + /// + [JsonRequired] + [JsonProperty("direction")] + public virtual BindingDirection Direction { get; set; } = BindingDirection.In; + + /// + /// Gets or sets the binding data type. + /// + [JsonProperty("dataType", NullValueHandling = NullValueHandling.Ignore)] + public virtual DataType? DataType { get; set; } + + /// + /// Gets or sets the HTTP endpoint route template. + /// + [JsonProperty("route", NullValueHandling = NullValueHandling.Ignore)] + public virtual string Route { get; set; } + + /// + /// Gets or sets the webhook type, handled by the trigger. + /// + [JsonProperty("webHookType", NullValueHandling = NullValueHandling.Ignore)] + public virtual string WebHookType { get; set; } + + /// + /// Gets or sets the HTTP endpoint authorisation level. Default value is . + /// + [JsonRequired] + [JsonProperty("authLevel")] + public virtual AuthorizationLevel AuthLevel { get; set; } = AuthorizationLevel.Function; + + /// + /// Gets or sets the list of the HTTP verbs. Default values are GET and POST. + /// + [JsonRequired] + [JsonProperty("methods")] + public virtual List Methods { get; set; } = new List() { "GET", "POST" }; + } +} diff --git a/templates/OpenApiEndpoints/IOpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/IOpenApiHttpTriggerContext.cs similarity index 75% rename from templates/OpenApiEndpoints/IOpenApiHttpTriggerContext.cs rename to src/Microsoft.Azure.WebJobs.Extensions.OpenApi/IOpenApiHttpTriggerContext.cs index 1d8bd3b..fa8601f 100644 --- a/templates/OpenApiEndpoints/IOpenApiHttpTriggerContext.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/IOpenApiHttpTriggerContext.cs @@ -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 /// NamingStrategy NamingStrategy { get; } + /// + /// Gets the value indicating whether it's in the development environment or not. + /// + bool IsDevelopment { get; } + /// /// Gets the executing assembly. /// @@ -63,6 +69,13 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi [Obsolete("This method is obsolete.", error: true)] Assembly GetExecutingAssembly(); + /// + /// Sets the application assembly from the function app directory. + /// + /// Function app directory. + /// Value indicating whether to append the "bin" directory or not. + IOpenApiHttpTriggerContext SetApplicationAssembly(string functionAppDirectory, bool appendBin = true); + /// /// Gets the instance. /// @@ -97,6 +110,20 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi /// Returns the value. OpenApiFormat GetOpenApiFormat(OpenApiFormatType format = OpenApiFormatType.Json); + /// + /// Gets the auth level of the document rendering page endpoint. + /// + /// Environment variables key to look for. + /// Returns the auth level of the document rendering page endpoint. + AuthorizationLevel GetDocumentAuthLevel(string key = "OpenApi__AuthLevel__Document"); + + /// + /// Gets the auth level of the UI rendering page endpoint. + /// + /// Environment variables key to look for. + /// Returns the auth level of the UI rendering page endpoint. + AuthorizationLevel GetUIAuthLevel(string key = "OpenApi__AuthLevel__UI"); + /// /// Gets the API key for endpoints from environment variables. /// diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Microsoft.Azure.WebJobs.Extensions.OpenApi.csproj b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Microsoft.Azure.WebJobs.Extensions.OpenApi.csproj index d073d4a..c82aeb3 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Microsoft.Azure.WebJobs.Extensions.OpenApi.csproj +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Microsoft.Azure.WebJobs.Extensions.OpenApi.csproj @@ -32,10 +32,14 @@ - + + + + + diff --git a/templates/OpenApiEndpoints/OpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs similarity index 60% rename from templates/OpenApiEndpoints/OpenApiHttpTriggerContext.cs rename to src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs index d533c40..f0f3b66 100644 --- a/templates/OpenApiEndpoints/OpenApiHttpTriggerContext.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs @@ -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 { /// - /// This represents the context entity for . + /// This represents the context entity for . /// [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; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public OpenApiHttpTriggerContext() { - this.ApplicationAssembly = this.GetAssembly(this); this.PackageAssembly = this.GetAssembly(); - 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 } /// - public virtual Assembly ApplicationAssembly { get; } + public virtual Assembly ApplicationAssembly + { + get + { + if (this._appAssembly.IsNullOrDefault()) + { + this._appAssembly = this.GetAssembly(this._dllpath); + } + + return this._appAssembly; + } + } /// public virtual Assembly PackageAssembly { get; } /// - public virtual IOpenApiConfigurationOptions OpenApiConfigurationOptions { get; } + public virtual IOpenApiConfigurationOptions OpenApiConfigurationOptions + { + get + { + if (this._configOptions.IsNullOrDefault()) + { + this._configOptions = OpenApiConfigurationResolver.Resolve(this.ApplicationAssembly); + } + + return this._configOptions; + } + } /// - public virtual IOpenApiCustomUIOptions OpenApiCustomUIOptions { get; } + public virtual IOpenApiCustomUIOptions OpenApiCustomUIOptions + { + get + { + if (this._uiOptions.IsNullOrDefault()) + { + this._uiOptions = OpenApiCustomUIResolver.Resolve(this.ApplicationAssembly); + } + + return this._uiOptions; + } + } /// public virtual HttpSettings HttpSettings { get; } @@ -72,6 +109,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi /// public virtual NamingStrategy NamingStrategy { get; } = new CamelCaseNamingStrategy(); + /// + public virtual bool IsDevelopment { get; } = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"; + /// [Obsolete("This method is obsolete. Use GetAssembly() or GetAssembly(object) instead", error: true)] public virtual Assembly GetExecutingAssembly() @@ -81,6 +121,35 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi return assembly; } + /// + 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; + } + /// public virtual VisitorCollection GetVisitorCollection() { @@ -126,6 +195,24 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi return format.ToOpenApiFormat(); } + /// + public virtual AuthorizationLevel GetDocumentAuthLevel(string key = "OpenApi__AuthLevel__Document") + { + var value = Environment.GetEnvironmentVariable(key); + var parsed = Enum.TryParse(value, out var result) ? result : AuthorizationLevel.Anonymous; + + return parsed; + } + + /// + public virtual AuthorizationLevel GetUIAuthLevel(string key = "OpenApi__AuthLevel__UI") + { + var value = Environment.GetEnvironmentVariable(key); + var parsed = Enum.TryParse(value, out var result) ? result : AuthorizationLevel.Anonymous; + + return parsed; + } + /// 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; + } } } diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctionProvider.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctionProvider.cs new file mode 100644 index 0000000..103b254 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctionProvider.cs @@ -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 +{ + /// + /// This represents the function provider entity for Open API HTTP triggers. + /// + 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 _bindings; + + /// + /// Initializes a new instance of the class. + /// + public OpenApiTriggerFunctionProvider(OpenApiSettings settings) + { + this._settings = settings ?? throw new ArgumentNullException(nameof(settings)); + this._bindings = this.SetupOpenApiHttpBindings(); + } + + /// + public ImmutableDictionary> FunctionErrors { get; } = new Dictionary>().ToImmutableDictionary(); + + /// + public async Task> GetFunctionMetadataAsync() + { + var functionMetadataList = this.GetFunctionMetadataList(); + + return await Task.FromResult(functionMetadataList.ToImmutableArray()).ConfigureAwait(false); + } + + private Dictionary 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() + { + { 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 GetFunctionMetadataList() + { + var list = new List() + { + 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; + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs new file mode 100644 index 0000000..97d4b05 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs @@ -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 +{ + /// + /// This represents the function provider entity for Open API HTTP triggers. + /// + public partial class OpenApiTriggerFunctionProvider + { + private readonly static IOpenApiHttpTriggerContext context = new OpenApiHttpTriggerContext(); + + /// + /// Invokes the HTTP trigger endpoint to get Open API document. + /// + /// instance. + /// File extension representing the document format. This MUST be either "json" or "yaml". + /// instance. + /// instance. + /// Open API document in a format of either JSON or YAML. + [OpenApiIgnore] + public static async Task 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; + } + + /// + /// Invokes the HTTP trigger endpoint to get Open API document. + /// + /// instance. + /// Open API document spec version. This MUST be either "v2" or "v3". + /// File extension representing the document format. This MUST be either "json" or "yaml". + /// instance. + /// instance. + /// Open API document in a format of either JSON or YAML. + [OpenApiIgnore] + public static async Task 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; + } + + /// + /// Invokes the HTTP trigger endpoint to render Swagger UI in HTML. + /// + /// instance. + /// instance. + /// instance. + /// Swagger UI in HTML. + [OpenApiIgnore] + public static async Task 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; + } + + /// + /// Invokes the HTTP trigger endpoint to render oauth2-redirect.html. + /// + /// instance. + /// instance. + /// instance. + /// oauth2-redirect.html. + [OpenApiIgnore] + public static async Task 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; + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiWebJobsStartup.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiWebJobsStartup.cs new file mode 100644 index 0000000..8bb1044 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiWebJobsStartup.cs @@ -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 +{ + /// + /// This represents the startup entity for Open API endpoints registration + /// + public class OpenApiWebJobsStartup : IWebJobsStartup + { + private const string OpenApiSettingsKey = "OpenApi"; + + /// + public void Configure(IWebJobsBuilder builder) + { + var config = ConfigurationResolver.Resolve(); + var settings = config.Get(OpenApiSettingsKey); + + builder.Services.AddSingleton(settings); + builder.Services.AddSingleton(); + } + } +} diff --git a/templates/OpenApiEndpoints/OpenApiHttpTrigger.cs b/templates/OpenApiEndpoints/OpenApiHttpTrigger.cs deleted file mode 100644 index c8f315e..0000000 --- a/templates/OpenApiEndpoints/OpenApiHttpTrigger.cs +++ /dev/null @@ -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 -{ - /// - /// This represents the HTTP trigger entity for OpenAPI documents. - /// - [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(); - - /// - /// Invokes the HTTP trigger endpoint to get OpenAPI document. - /// - /// instance. - /// File extension representing the document format. This MUST be either "json" or "yaml". - /// instance. - /// OpenAPI document in a format of either JSON or YAML. - [FunctionName(nameof(OpenApiHttpTrigger.RenderSwaggerDocument))] - [OpenApiIgnore] - public static async Task 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; - } - - /// - /// Invokes the HTTP trigger endpoint to get OpenAPI document. - /// - /// instance. - /// OpenAPI document spec version. This MUST be either "v2" or "v3". - /// File extension representing the document format. This MUST be either "json" or "yaml". - /// instance. - /// OpenAPI document in a format of either JSON or YAML. - [FunctionName(nameof(OpenApiHttpTrigger.RenderOpenApiDocument))] - [OpenApiIgnore] - public static async Task 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; - } - - /// - /// Invokes the HTTP trigger endpoint to render Swagger UI in HTML. - /// - /// instance. - /// instance. - /// Swagger UI in HTML. - [FunctionName(nameof(OpenApiHttpTrigger.RenderSwaggerUI))] - [OpenApiIgnore] - public static async Task 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; - } - - /// - /// Invokes the HTTP trigger endpoint to render oauth2-redirect.html. - /// - /// instance. - /// instance. - /// oauth2-redirect.html. - [FunctionName(nameof(OpenApiHttpTrigger.RenderOAuth2Redirect))] - [OpenApiIgnore] - public static async Task 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; - } - } -} diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Extensions/SwaggerUIExtensionsTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Extensions/SwaggerUIExtensionsTests.cs index 577c882..6186e82 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Extensions/SwaggerUIExtensionsTests.cs +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Extensions/SwaggerUIExtensionsTests.cs @@ -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(); - ui.Setup(p => p.RenderAsync(It.IsAny(), It.IsAny())).ReturnsAsync(rendered); + ui.Setup(p => p.RenderAsync(It.IsAny(), It.IsAny(), It.IsAny())).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(); - ui.Setup(p => p.RenderOAuth2RedirectAsync(It.IsAny(), It.IsAny())).ReturnsAsync(rendered); + ui.Setup(p => p.RenderOAuth2RedirectAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(rendered); var task = Task.FromResult(ui.Object); diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.csproj b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.csproj index 2e25f86..30e6f83 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.csproj +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.csproj @@ -36,10 +36,4 @@ - - - - - - diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs index e680c20..c0ac17b 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs @@ -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); } diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiTriggerFunctionProviderTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiTriggerFunctionProviderTests.cs new file mode 100644 index 0000000..c4855f8 --- /dev/null +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiTriggerFunctionProviderTests.cs @@ -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(); + } + + [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(); + 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(); + authLevelSettings.SetupGet(p => p.Document).Returns(authLevelDoc); + authLevelSettings.SetupGet(p => p.UI).Returns(authLevelUI); + + var settings = new Mock(); + 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("authLevel").Should().Be((int)authLevelDoc); + result.Single(p => p.Name == "RenderOpenApiDocument").Bindings.First().Raw.Value("authLevel").Should().Be((int)authLevelDoc); + result.Single(p => p.Name == "RenderSwaggerUI").Bindings.First().Raw.Value("authLevel").Should().Be((int)authLevelUI); + result.Single(p => p.Name == "RenderOAuth2Redirect").Bindings.First().Raw.Value("authLevel").Should().Be((int)authLevelUI); + } + } +}