From f863110278b96f87fadcabc062adf659fe2ebf9c Mon Sep 17 00:00:00 2001 From: Justin Yoo Date: Wed, 6 Dec 2023 01:50:52 +0900 Subject: [PATCH] Add OpenAPI doc definitions --- .../Controllers/ChatCompletionsController.cs | 5 +- .../Controllers/CompletionsController.cs | 5 +- .../ManagementAccessCodeController.cs | 9 +-- .../Controllers/ManagementEventController.cs | 9 +-- .../Filters/OpenApiParameterIgnoreFilter.cs | 56 +++++++++++++++++++ src/AzureOpenAIProxy.ApiApp/Program.cs | 35 +++++++++++- .../appsettings.Development.sample.json | 3 +- src/AzureOpenAIProxy.ApiApp/appsettings.json | 3 +- 8 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 src/AzureOpenAIProxy.ApiApp/Filters/OpenApiParameterIgnoreFilter.cs diff --git a/src/AzureOpenAIProxy.ApiApp/Controllers/ChatCompletionsController.cs b/src/AzureOpenAIProxy.ApiApp/Controllers/ChatCompletionsController.cs index b218cea9..e9e1bb1c 100644 --- a/src/AzureOpenAIProxy.ApiApp/Controllers/ChatCompletionsController.cs +++ b/src/AzureOpenAIProxy.ApiApp/Controllers/ChatCompletionsController.cs @@ -2,6 +2,7 @@ using Azure.AI.OpenAI; +using AzureOpenAIProxy.ApiApp.Filters; using AzureOpenAIProxy.ApiApp.Models; using AzureOpenAIProxy.ApiApp.Services; @@ -36,8 +37,8 @@ public class ChatCompletionsController( /// Returns the chat completions response. [HttpPost("deployments/{deploymentName}/chat/completions", Name = "GetChatCompletions")] public async Task GetChatCompletionsAsync( + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromRoute] string deploymentName, - [FromHeader(Name = "api-key")] string apiKey, [FromQuery(Name = "api-version")] string apiVersion, [FromBody] ChatCompletionsOptions req) { @@ -87,8 +88,8 @@ public async Task GetChatCompletionsAsync( /// Returns the chat completions response. [HttpPost("deployments/{deploymentName}/extensions/chat/completions", Name = "GetExtensionsChatCompletions")] public async Task GetExtensionsChatCompletionsAsync( + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromRoute] string deploymentName, - [FromHeader(Name = "api-key")] string apiKey, [FromQuery(Name = "api-version")] string apiVersion, [FromBody] ChatCompletionsOptions req) { diff --git a/src/AzureOpenAIProxy.ApiApp/Controllers/CompletionsController.cs b/src/AzureOpenAIProxy.ApiApp/Controllers/CompletionsController.cs index 9086529d..3c6e5380 100644 --- a/src/AzureOpenAIProxy.ApiApp/Controllers/CompletionsController.cs +++ b/src/AzureOpenAIProxy.ApiApp/Controllers/CompletionsController.cs @@ -2,6 +2,7 @@ using Azure.AI.OpenAI; +using AzureOpenAIProxy.ApiApp.Filters; using AzureOpenAIProxy.ApiApp.Models; using AzureOpenAIProxy.ApiApp.Services; @@ -36,8 +37,8 @@ public class CompletionsController( /// Returns the chat completions response. [HttpPost("deployments/{deploymentName}/completions", Name = "GetCompletions")] public async Task GetCompletionsAsync( + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromRoute] string deploymentName, - [FromHeader(Name = "api-key")] string apiKey, [FromQuery(Name = "api-version")] string apiVersion, [FromBody] CompletionsOptions req) { @@ -87,8 +88,8 @@ public async Task GetCompletionsAsync( /// Returns the completions response. [HttpPost("deployments/{deploymentName}/extensions/completions", Name = "GetExtensionsCompletions")] public async Task GetExtensionsCompletionsAsync( + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromRoute] string deploymentName, - [FromHeader(Name = "api-key")] string apiKey, [FromQuery(Name = "api-version")] string apiVersion, [FromBody] CompletionsOptions req) { diff --git a/src/AzureOpenAIProxy.ApiApp/Controllers/ManagementAccessCodeController.cs b/src/AzureOpenAIProxy.ApiApp/Controllers/ManagementAccessCodeController.cs index 2752611b..5cf6ba38 100644 --- a/src/AzureOpenAIProxy.ApiApp/Controllers/ManagementAccessCodeController.cs +++ b/src/AzureOpenAIProxy.ApiApp/Controllers/ManagementAccessCodeController.cs @@ -1,4 +1,5 @@ -using AzureOpenAIProxy.ApiApp.Models; +using AzureOpenAIProxy.ApiApp.Filters; +using AzureOpenAIProxy.ApiApp.Models; using Microsoft.AspNetCore.Mvc; @@ -19,7 +20,7 @@ public partial class ManagementController /// Returns the instance. [HttpGet("events/{eventId}/access-codes", Name = "GetListOfEventAccessCodes")] public async Task GetEventAccessCodesByEventIdAsync( - [FromHeader(Name = "Authorization")] string apiKey, + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromRoute] string eventId, [FromQuery(Name = "page")] int? page = 0, [FromQuery(Name = "size")] int? size = 20) @@ -66,7 +67,7 @@ public async Task GetEventAccessCodesByEventIdAsync( /// Returns the instance. [HttpPost("events/{eventId}/access-codes", Name = "CreateEventAccessCode")] public async Task CreateEventAccessCodeAsync( - [FromHeader(Name = "Authorization")] string apiKey, + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromRoute] string eventId, [FromBody] AccessCodeRequest req) { @@ -119,7 +120,7 @@ public async Task CreateEventAccessCodeAsync( /// Returns the instance. [HttpGet("events/{eventId}/access-codes/{gitHubAlias}", Name = "GetEventAccessCodeByGitHubAlias")] public async Task GetEventAccessCodeByGitHubAliasAsync( - [FromHeader(Name = "Authorization")] string apiKey, + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromRoute] string eventId, [FromRoute] string gitHubAlias) { diff --git a/src/AzureOpenAIProxy.ApiApp/Controllers/ManagementEventController.cs b/src/AzureOpenAIProxy.ApiApp/Controllers/ManagementEventController.cs index 7060dc2a..7224223b 100644 --- a/src/AzureOpenAIProxy.ApiApp/Controllers/ManagementEventController.cs +++ b/src/AzureOpenAIProxy.ApiApp/Controllers/ManagementEventController.cs @@ -1,4 +1,5 @@ -using AzureOpenAIProxy.ApiApp.Models; +using AzureOpenAIProxy.ApiApp.Filters; +using AzureOpenAIProxy.ApiApp.Models; using AzureOpenAIProxy.ApiApp.Services; using Microsoft.AspNetCore.Mvc; @@ -31,7 +32,7 @@ public partial class ManagementController( /// Returns the instance. [HttpGet("events", Name = "GetListOfEvents")] public async Task GetEventsAsync( - [FromHeader(Name = "Authorization")] string apiKey, + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromQuery(Name = "page")] int? page = 0, [FromQuery(Name = "size")] int? size = 20) { @@ -69,7 +70,7 @@ public async Task GetEventsAsync( /// Returns the instance. [HttpPost("events", Name = "CreateEvent")] public async Task CreateEventAsync( - [FromHeader(Name = "Authorization")] string apiKey, + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromBody] EventRequest req) { this._logger.LogInformation("Received a request to generate an event"); @@ -113,7 +114,7 @@ public async Task CreateEventAsync( /// Returns the instance. [HttpGet("events/{eventId}", Name = "GetEventById")] public async Task GetEventByEventIdAsync( - [FromHeader(Name = "Authorization")] string apiKey, + [OpenApiParameterIgnore][FromHeader(Name = "api-key")] string apiKey, [FromRoute] string eventId) { this._logger.LogInformation("Received a request to get an event details by event ID"); diff --git a/src/AzureOpenAIProxy.ApiApp/Filters/OpenApiParameterIgnoreFilter.cs b/src/AzureOpenAIProxy.ApiApp/Filters/OpenApiParameterIgnoreFilter.cs new file mode 100644 index 00000000..c6d80802 --- /dev/null +++ b/src/AzureOpenAIProxy.ApiApp/Filters/OpenApiParameterIgnoreFilter.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.OpenApi.Models; + +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace AzureOpenAIProxy.ApiApp.Filters; + +/// +/// This represents the attribute entity for parameters to be ignored from the OpenAPI document generation. +/// +[AttributeUsage(AttributeTargets.Parameter)] +public class OpenApiParameterIgnoreAttribute : Attribute +{ +} + +/// +/// This represents the filter entity for parameters to be ignored from the OpenAPI document generation. +/// +/// https://stackoverflow.com/questions/69651135/hide-parameter-from-swagger-swashbuckle +public class OpenApiParameterIgnoreFilter : IOperationFilter +{ + /// + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + if (operation == null || context == null || context.ApiDescription?.ParameterDescriptions == null) + { + return; + } + + var parametersToHide = context.ApiDescription.ParameterDescriptions + .Where(ParameterHasIgnoreAttribute); + + if (parametersToHide.Any() == false) + { + return; + } + + foreach (var parameterToHide in parametersToHide) + { + var parameter = operation.Parameters + .FirstOrDefault(parameter => parameter.Name.Equals(parameterToHide.Name, StringComparison.InvariantCultureIgnoreCase)); + if (parameter != null) + { + operation.Parameters.Remove(parameter); + } + } + } + + private static bool ParameterHasIgnoreAttribute(ApiParameterDescription parameterDescription) + { + return parameterDescription.ModelMetadata is DefaultModelMetadata metadata + ? metadata.Attributes.ParameterAttributes.Any(attribute => attribute.GetType() == typeof(OpenApiParameterIgnoreAttribute)) + : false; + } +} \ No newline at end of file diff --git a/src/AzureOpenAIProxy.ApiApp/Program.cs b/src/AzureOpenAIProxy.ApiApp/Program.cs index 9065399a..3da2d3d5 100644 --- a/src/AzureOpenAIProxy.ApiApp/Program.cs +++ b/src/AzureOpenAIProxy.ApiApp/Program.cs @@ -1,7 +1,10 @@ using AzureOpenAIProxy.ApiApp.Configurations; +using AzureOpenAIProxy.ApiApp.Filters; using AzureOpenAIProxy.ApiApp.Models; using AzureOpenAIProxy.ApiApp.Services; +using Microsoft.OpenApi.Models; + var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); @@ -17,7 +20,37 @@ builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(options => +{ + options.SwaggerDoc( + "v1", + new OpenApiInfo() + { + Version = "1.0.0", + Title = "Azure OpenAI Proxy Service", + Description = "Providing a proxy service to Azure OpenAI", + }); + options.AddSecurityDefinition( + "apiKey", + new OpenApiSecurityScheme() + { + Name = "api-key", + Type = SecuritySchemeType.ApiKey, + Description = "API key needed to access the endpoints.", + In = ParameterLocation.Header, + }); + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "apiKey" } + }, + new string[] {} + } + }); + options.OperationFilter(); +}); var app = builder.Build(); diff --git a/src/AzureOpenAIProxy.ApiApp/appsettings.Development.sample.json b/src/AzureOpenAIProxy.ApiApp/appsettings.Development.sample.json index 39b0ae10..93f5c40b 100644 --- a/src/AzureOpenAIProxy.ApiApp/appsettings.Development.sample.json +++ b/src/AzureOpenAIProxy.ApiApp/appsettings.Development.sample.json @@ -14,7 +14,8 @@ "Instances": [ { "Endpoint": "https://{account_name}.openai.azure.com/", - "ApiKey": "random.apikey.aoai" + "ApiKey": "random.api-key.aoai", + "DeploymentName": "random.deployment-name.aoai" } ] } diff --git a/src/AzureOpenAIProxy.ApiApp/appsettings.json b/src/AzureOpenAIProxy.ApiApp/appsettings.json index 6a9d3d3a..739cb99c 100644 --- a/src/AzureOpenAIProxy.ApiApp/appsettings.json +++ b/src/AzureOpenAIProxy.ApiApp/appsettings.json @@ -14,7 +14,8 @@ "Instances": [ { "Endpoint": "https://{account_name}.openai.azure.com/", - "ApiKey": "random.apikey.aoai" + "ApiKey": "random.api-key.aoai", + "DeploymentName": "random.deployment-name.aoai" } ] },