Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net: Handle missing operation id in OpenApi spec #7344

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use the option method and method to generated the function name when …
…there is no operation id
  • Loading branch information
markwallace-microsoft committed Jul 18, 2024
commit 4af517438ce38f5c3f25663296766e7988b15ae8
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -274,7 +275,7 @@ async Task<RestApiOperationResponse> ExecuteAsync(Kernel kernel, KernelFunction
method: ExecuteAsync,
new KernelFunctionFromMethodOptions
{
FunctionName = ConvertOperationIdToValidFunctionName(operation.Id, logger),
FunctionName = ConvertOperationToValidFunctionName(operation, logger),
Description = operation.Description,
Parameters = parameters,
ReturnParameter = returnParameter,
Expand All @@ -295,16 +296,42 @@ async Task<RestApiOperationResponse> ExecuteAsync(Kernel kernel, KernelFunction
/// Converts operation id to valid <see cref="KernelFunction"/> name.
/// A function name can contain only ASCII letters, digits, and underscores.
/// </summary>
/// <param name="operationId">The operation id.</param>
/// <param name="operation">The REST API operation.</param>
/// <param name="logger">The logger.</param>
/// <returns>Valid KernelFunction name.</returns>
private static string ConvertOperationIdToValidFunctionName(string operationId, ILogger logger)
private static string ConvertOperationToValidFunctionName(RestApiOperation operation, ILogger logger)
{
if (string.IsNullOrEmpty(operationId))
if (!string.IsNullOrWhiteSpace(operation.Id))
{
return CreateRandomFunctionName();
return ConvertOperationIdToValidFunctionName(operationId: operation.Id, logger: logger);
}

// Tokenize operation path on forward and back slashes
string[] tokens = operation.Path.Split('/', '\\');
StringBuilder result = new();
result.Append(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(operation.Method.ToString()));

foreach (string token in tokens)
{
// Removes all characters that are not ASCII letters, digits, and underscores.
string formattedToken = RemoveInvalidCharsRegex().Replace(token, "");
Copy link
Contributor

@RogerBarret0 RogerBarret0 Jul 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. string.Empty

Suggested change
string formattedToken = RemoveInvalidCharsRegex().Replace(token, "");
string formattedToken = RemoveInvalidCharsRegex().Replace(token, string.Empty);

result.Append(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(formattedToken.ToLower(CultureInfo.CurrentCulture)));
}

logger.LogInformation("""Operation method "{0}" with path "{1}" converted to "{2}" to comply with SK Function name requirements. Use "{2}" when invoking function.""", operation.Method, operation.Path, result, result);
markwallace-microsoft marked this conversation as resolved.
Show resolved Hide resolved

return result.ToString();
}

/// <summary>
/// Converts operation id to valid <see cref="KernelFunction"/> name.
/// A function name can contain only ASCII letters, digits, and underscores.
/// </summary>
/// <param name="operationId">The operation id.</param>
/// <param name="logger">The logger.</param>
/// <returns>Valid KernelFunction name.</returns>
private static string ConvertOperationIdToValidFunctionName(string operationId, ILogger logger)
{
try
{
Verify.ValidFunctionName(operationId);
Expand Down Expand Up @@ -341,14 +368,9 @@ private static string ConvertOperationIdToValidFunctionName(string operationId,
private static partial Regex RemoveInvalidCharsRegex();
#else
private static Regex RemoveInvalidCharsRegex() => s_removeInvalidCharsRegex;
private static readonly Regex s_removeInvalidCharsRegex = new("[^0-9A-Za-z_]", RegexOptions.Compiled);
private static readonly Regex s_removeInvalidCharsRegex = new("[^0-9A-Za-z_./-/{/}]", RegexOptions.Compiled);
#endif

/// <summary>
/// Create a random, valid function name.
/// </summary>
internal static string CreateRandomFunctionName(string? prefix = "Function") => $"{prefix}_{Guid.NewGuid():N}";

#endregion

}
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ public async Task ItShouldHandleEmptyOperationNameAsync()

// Assert
Assert.Equal(5, plugin.Count());
Assert.Single(plugin.Where(f => f.Name.StartsWith("Function_", StringComparison.Ordinal)));
Assert.True(plugin.TryGetFunction("GetSecretsSecretname", out var _));
}

[Fact]
Expand All @@ -332,7 +332,7 @@ public async Task ItShouldHandleNullOperationNameAsync()

// Assert
Assert.Equal(5, plugin.Count());
Assert.Single(plugin.Where(f => f.Name.StartsWith("Function_", StringComparison.Ordinal)));
Assert.True(plugin.TryGetFunction("GetSecretsSecretname", out var _));
}

[Fact]
Expand Down
Loading