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

Latest dev changes. #2053

Merged
merged 5 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 14 additions & 0 deletions CodeSnippetsReflection.OpenAPI.Test/GoGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,20 @@ public async Task WritesEmptyPrimitiveArrays() {
Assert.Contains("SetIncludeUserActions", result);
}
[Fact]
public async Task WriteCorrectFunctionNameWithParametersAsModelName()
{
const string messageObject = "{\r\n\"displayName\": \"Display name\"\r\n}";
using var requestPayload = new HttpRequestMessage(HttpMethod.Patch, $"{ServiceRootUrl}/applications(uniqueName='app-65278')")
{
Content = new StringContent(messageObject, Encoding.UTF8, "application/json")
};
requestPayload.Headers.Add("Prefer", "create-if-missing");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetBetaSnippetMetadata());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("graphapplicationswithuniquename \"github.com/microsoftgraph/msgraph-sdk-go/applicationswithuniquename\"", result);
Assert.Contains("graphapplicationswithuniquename.ApplicationsWithUniqueNameRequestBuilderPatchRequestConfiguration", result);
}
[Fact]
public async Task WriteCorrectTypesForFilterParameters()
{
using var requestPayload = new HttpRequestMessage(HttpMethod.Get,
Expand Down
5 changes: 5 additions & 0 deletions CodeSnippetsReflection.OpenAPI.Test/PhpGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public async Task IncludesRequestBodyClassName()
var snippetModel = new SnippetModel(requestPayload, ServiceRootBetaUrl, await GetBetaSnippetMetadata());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("AddPasswordPostRequestBody", result);
Assert.Contains(@"use Microsoft\Graph\Beta\GraphServiceClient;", result);
Assert.Contains(@"use Microsoft\Graph\Beta\Generated\Models\PasswordCredential;", result);
}

[Fact]
Expand Down Expand Up @@ -847,6 +849,9 @@ public async Task GenerateWithCustomDateAndTimeTypes()
};
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains(@"use Microsoft\Kiota\Abstractions\Types\Date;", result);
Assert.Contains(@"use Microsoft\Kiota\Abstractions\Types\Time;", result);
Assert.Contains(@"use Microsoft\Graph\Generated\Models\AutomaticUpdateMode;", result);
Assert.Contains("->setQualityUpdatesPauseStartDate(new Date('2016-12-31'))", result);
Assert.Contains("->setScheduledInstallTime(new Time('11:59:31.3170000'))", result);
}
Expand Down
20 changes: 13 additions & 7 deletions CodeSnippetsReflection.OpenAPI.Test/PythonGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public async Task GeneratesSelectQueryParameters()
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("RequestBuilderGetQueryParameters(", result);
Assert.Contains("select = [\"displayName\",\"id\"]", result);
Assert.Contains("RequestBuilderGetRequestConfiguration(", result);
Assert.Contains("RequestConfiguration(", result);
Assert.Contains("query_parameters = query_params,", result);
}
[Fact]
Expand All @@ -182,7 +182,7 @@ public async Task GeneratesCountBooleanQueryParameters()
Assert.Contains("count = True", result);
Assert.Contains("select = [\"displayName\",\"id\"]", result);
Assert.DoesNotContain("\"true\"", result);
Assert.Contains("RequestBuilderGetRequestConfiguration(", result);
Assert.Contains("RequestConfiguration(", result);
Assert.Contains("query_parameters = query_params,", result);
}
[Fact]
Expand Down Expand Up @@ -211,7 +211,8 @@ public async Task GeneratesRequestHeaders()
requestPayload.Headers.Add("ConsistencyLevel", "eventual");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("request_configuration = GroupsRequestBuilder.GroupsRequestBuilderGetRequestConfiguration()", result);
Assert.Contains("request_configuration = RequestConfiguration()", result);
Assert.Contains("from kiota_abstractions.base_request_configuration import RequestConfiguration", result);
Assert.Contains("request_configuration.headers.add(\"ConsistencyLevel\", \"eventual\")", result);
}
[Fact]
Expand Down Expand Up @@ -244,7 +245,8 @@ public async Task GeneratesSnippetForRequestWithDeltaAndSkipToken()
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("DeltaRequestBuilderGetQueryParameters(", result);
Assert.Contains("skiptoken = \"R0usmcCM996atia_s\",", result);
Assert.Contains("request_configuration = DeltaRequestBuilder.DeltaRequestBuilderGetRequestConfiguration(", result);
Assert.Contains("request_configuration = RequestConfiguration(", result);
Assert.Contains("from kiota_abstractions.base_request_configuration import RequestConfiguration", result);
Assert.Contains("query_parameters = query_params,", result);
Assert.Contains("request_configuration.headers.add(\"Prefer\", \"odata.maxpagesize=2\")", result);
Assert.Contains("result = await graph_client.me.calendar_view.delta.get", result);
Expand Down Expand Up @@ -651,7 +653,7 @@ public async Task CorrectlyHandlesOdataFunction()
Assert.Contains("await graph_client.users.delta.get(request_configuration = request_configuration)", result);
Assert.Contains("query_params = DeltaRequestBuilder.DeltaRequestBuilderGetQueryParameters(", result);
Assert.Contains("select = [\"displayName\",\"jobTitle\",\"mobilePhone\"]", result);
Assert.Contains("request_configuration = DeltaRequestBuilder.DeltaRequestBuilderGetRequestConfiguration(", result);
Assert.Contains("request_configuration = RequestConfiguration(", result);
}
[Fact]
public async Task CorrectlyHandlesDateTimeOffsetInUrl()
Expand Down Expand Up @@ -876,6 +878,7 @@ public async Task GeneratesCorrectTypeInCollectionInitializer() {
Assert.Contains("scope = RuleBasedSubjectSet(", result);
Assert.Contains("tasks = [", result);
Assert.Contains("Task(", result);
Assert.Contains("from msgraph.generated.models.lifecycle_workflow_category import LifecycleWorkflowCategory", result);
}
[Fact]
public async Task CorrectlyHandlesTypeFromInUrl()
Expand All @@ -886,7 +889,7 @@ public async Task CorrectlyHandlesTypeFromInUrl()

Assert.Contains("query_params = MailFoldersRequestBuilder.MailFoldersRequestBuilderGetQueryParameters(", result);
Assert.Contains("include_hidden_folders = \"true\"", result);
Assert.Contains("request_configuration = MailFoldersRequestBuilder.MailFoldersRequestBuilderGetRequestConfiguration(", result);
Assert.Contains("request_configuration = RequestConfiguration(", result);
}
[Fact]
public async Task MatchesPathWithPathParameter()
Expand Down Expand Up @@ -934,6 +937,7 @@ public async Task GeneratesObjectInitializationWithCallToSetters()
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("select = [\"displayName\",\"mailNickName\"],", result);
Assert.Contains("account_enabled = True", result);
Assert.Contains("from msgraph import GraphServiceClient", result);
}
[Fact]
public async Task IncludesRequestBodyClassName()
Expand All @@ -948,6 +952,8 @@ public async Task IncludesRequestBodyClassName()
var snippetModel = new SnippetModel(requestPayload, ServiceRootBetaUrl, await GetBetaSnippetMetadata());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("request_body = AddPasswordPostRequestBody(", result);
Assert.Contains("from msgraph_beta.generated.models.password_credential import PasswordCredential", result);
Assert.Contains("from msgraph_beta import GraphServiceClient", result);
}
[Fact]
public async Task FindsPathItemsWithDifferentCasing()
Expand Down Expand Up @@ -1197,6 +1203,6 @@ public async Task GeneratesCorrectRequestBuilderNameForIndexedCollections()
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("query_params = UserItemRequestBuilder.UserItemRequestBuilderGetQueryParameters(", result);
Assert.Contains("request_configuration = UserItemRequestBuilder.UserItemRequestBuilderGetRequestConfiguration(", result);
Assert.Contains("request_configuration = RequestConfiguration(", result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public async Task GeneratesRequestBuilderImports()
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("from msgraph import GraphServiceClient", result);
Assert.Contains("from kiota_abstractions.base_request_configuration import RequestConfiguration", result);
Assert.Contains("from msgraph.generated.users.item.calendar.events.events_request_builder import EventsRequestBuilder", result);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using CodeSnippetsReflection.OpenAPI.ModelGraph;
using CodeSnippetsReflection.StringExtensions;
using Microsoft.OpenApi.Services;
using System.Text.RegularExpressions;
using System.Collections;

namespace CodeSnippetsReflection.OpenAPI.LanguageGenerators
{
public class GoGenerator : ILanguageGenerator<SnippetModel, OpenApiUrlTreeNode>
{
private const string clientVarName = "graphClient";
private const string clientVarType = "GraphServiceClientWithCredentials";
private const string clientFactoryVariables = "cred, scopes";
private const string requestBodyVarName = "requestBody";
private const string requestHeadersVarName = "headers";
private const string optionsParameterVarName = "options";
Expand All @@ -29,6 +27,10 @@

private static readonly Regex PropertyNameRegex = new Regex(@"@(.*)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(200));

private static readonly Regex FunctionRegex = new Regex(@"(\w+)\(([^)]*)\)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(200));

private static readonly Regex ParamRegex = new Regex(@"(\w+)\s*=\s*'[^']*'", RegexOptions.Compiled, TimeSpan.FromMilliseconds(200));

static IImmutableSet<string> GetNativeTypes()
{
return ImmutableHashSet.Create("string", "int", "float");
Expand Down Expand Up @@ -142,9 +144,24 @@

private static String ProcessNameSpaceName(String nameSpace)
{
return (nameSpace != null ? nameSpace.Split(".", StringSplitOptions.RemoveEmptyEntries)
if (String.IsNullOrEmpty(nameSpace))
return "";

// process function names and parameters
var functionNameMatch = FunctionRegex.Match(nameSpace);
if (functionNameMatch.Success)
{
var paramMatches = ParamRegex.Matches(functionNameMatch.Groups[2].Value);
var paramNames = paramMatches.Cast<Match>().Select(static m => m.Groups[1].Value).ToList();

return functionNameMatch.Groups[1].Value + "With" + string.Join("With", paramNames);
}

var processedName = (nameSpace.Split(".", StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Equals("Me", StringComparison.OrdinalIgnoreCase) ? "Users" : x)
.Aggregate((current, next) => current + "." + next) : "models").Replace(".microsoft.graph", "");
.Aggregate(static (current, next) => current + "." + next)).Replace(".microsoft.graph", "");

return processedName;
}

private static String ProcessFinalNameSpaceName(String nameSpace)
Expand Down Expand Up @@ -182,7 +199,7 @@
{
if (property.Children != null && property.Children.Count != 0)
{
var existingChild = property.Children.FirstOrDefault(x => x.PropertyType == propertyType);

Check warning on line 202 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

"Find" method should be used instead of the "FirstOrDefault" extension method. (https://rules.sonarsource.com/csharp/RSPEC-6602)
return propertyType == existingChild.PropertyType;
}
return property.PropertyType == propertyType;
Expand Down Expand Up @@ -299,14 +316,14 @@
var isCollection = nodes.First().Segment.IsCollectionIndex();
var isSingleElement = nodes.Count() == 1;

var filteredNodes = (isCollection && !isSingleElement) ? nodes.Skip(2) : isCollection ? nodes.Skip(1) : nodes; // skip first element if its not only element

Check warning on line 319 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Extract this nested ternary operation into an independent statement. (https://rules.sonarsource.com/csharp/RSPEC-3358)
if (!(filteredNodes?.Any() ?? false)) return string.Empty;

Check warning on line 320 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this unnecessary check for null. (https://rules.sonarsource.com/csharp/RSPEC-2589)

Check warning on line 320 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this unnecessary check for null. Some code paths are unreachable. (https://rules.sonarsource.com/csharp/RSPEC-2583)
return filteredNodes.Select(static x =>
{
if (x.Segment.IsCollectionIndex())
return "Item";
else
return x.Segment.ToFirstCharacterUpperCase();
return EscapeFunctionNames(x.Segment.ToFirstCharacterUpperCase());
})
.Aggregate(static (x, y) =>
{
Expand All @@ -316,6 +333,22 @@
});
}

private static string EscapeFunctionNames(String objectName)
{
if (String.IsNullOrEmpty(objectName))
return objectName;

var match = FunctionRegex.Match(objectName);
if (match.Success)
{
var paramMatches = ParamRegex.Matches(match.Groups[2].Value);
var paramNames = paramMatches.Cast<Match>().Select(static m => m.Groups[1].Value.ToFirstCharacterUpperCase()).ToList();

return match.Groups[1].Value + "With" + string.Join("With", paramNames);
}
return objectName;
}

private static string evaluateParameter(CodeProperty param)
{
if (param.PropertyType == PropertyType.Array)
Expand Down Expand Up @@ -358,7 +391,7 @@

private static string GetResultVarName(SnippetCodeGraph codeGraph)
{
var path = codeGraph.Nodes.LastOrDefault(x => !x.IsParameter)?.Path?.Split("\\").Last(static x => !string.IsNullOrWhiteSpace(x)).Split("(").First()

Check warning on line 394 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Indexing at 0 should be used instead of the "Enumerable" extension method "First" (https://rules.sonarsource.com/csharp/RSPEC-6608)
.Split(".")
.Select(static s => s.ToFirstCharacterUpperCase())
.Aggregate(static (a, b) => $"{a}{b}")
Expand Down Expand Up @@ -423,7 +456,7 @@
builder.AppendLine(objectBuilder.ToString());
}

var typeDefinition = codeProperty.TypeDefinition?.ToLowerInvariant()?.Trim();

Check warning on line 459 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this unnecessary check for null. (https://rules.sonarsource.com/csharp/RSPEC-2589)

String typeName;
if (NativeTypes.Contains(typeDefinition))
Expand All @@ -447,7 +480,7 @@

}

private static void WriteCodeProperty(string propertyAssignment, StringBuilder builder, CodeProperty codeProperty, CodeProperty child, IndentManager indentManager, int childPosition = 0, bool declarationOnly = false)

Check warning on line 483 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 23 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
var isArray = codeProperty.PropertyType == PropertyType.Array;
var isMap = codeProperty.PropertyType == PropertyType.Map;
Expand Down Expand Up @@ -606,7 +639,7 @@
.Aggregate(new List<String>(), (current, next) =>
{
var element = next.Contains("ByTypeId", StringComparison.OrdinalIgnoreCase) ?
next.Replace("ByTypeId", $"By{current.Last().Replace("s().", string.Empty, StringComparison.OrdinalIgnoreCase)}Id") :

Check warning on line 642 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Indexing at Count-1 should be used instead of the "Enumerable" extension method "Last" (https://rules.sonarsource.com/csharp/RSPEC-6608)
$"{next.Replace("$", string.Empty, StringComparison.OrdinalIgnoreCase).ToFirstCharacterUpperCase()}";

current.Add(element);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,33 +121,78 @@
}
private static HashSet<string> GetImportStatements(SnippetModel snippetModel)
{
const string modelImportPrefix = "use Microsoft\\Graph\\Generated\\Models";
const string requestBuilderImportPrefix = "use Microsoft\\Graph\\Generated";

var snippetImports = new HashSet<string>();
var packagePrefix = snippetModel.ApiVersion switch

Check warning on line 124 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/PhpGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '""' is not covered.
{
"v1.0" => @"Microsoft\Graph",
"beta" => @"Microsoft\Graph\Beta",
};
var modelImportPrefix = $@"use {packagePrefix}\Generated\Models";
var requestBuilderImportPrefix = $@"use {packagePrefix}\Generated";
const string customTypesPrefix = @"use Microsoft\Kiota\Abstractions\Types";

snippetImports.Add("use Microsoft\\Graph\\GraphServiceClient;");
var snippetImports = new HashSet<string> { $@"use {packagePrefix}\GraphServiceClient;" };

var imports = ImportsGenerator.GenerateImportTemplates(snippetModel);
foreach (var import in imports)
{
switch (import.Kind)
{
case ImportKind.Model:
if (import.ModelProperty.PropertyType is PropertyType.DateOnly or PropertyType.TimeOnly)
{
snippetImports.Add($"{customTypesPrefix}\\{GetPropertyTypeName(import.ModelProperty)};");
continue;
}
var typeDefinition = import.ModelProperty.TypeDefinition;
if (typeDefinition != null){
snippetImports.Add($"{modelImportPrefix}\\{typeDefinition};");
const string modelsNamespaceName = "models.microsoft.graph";
var modelNamespaceStringLen = modelsNamespaceName.Length;
var modelNamespace = import.ModelProperty.NamespaceName;
var inModelsNamespace =
modelNamespace.Equals(modelsNamespaceName,
StringComparison.OrdinalIgnoreCase);
var nested = !inModelsNamespace && modelNamespace.StartsWith(modelsNamespaceName);
// This takes care of models in nested namespaces inside the model namespace for instance
// models inside IdentityGovernance namespace
var othersParts = nested switch
{
true => import.ModelProperty.NamespaceName[modelNamespaceStringLen..]
.Split('.', StringSplitOptions.RemoveEmptyEntries)
.Select(static x => x.ToFirstCharacterUpperCase())
.Aggregate(static (x, y) => $@"{x}\{y}"),
false => string.Empty
};

var namespaceValue = !string.IsNullOrEmpty(othersParts) ? $@"\{othersParts}" : string.Empty;
if (typeDefinition != null)
{
if (inModelsNamespace)
{
snippetImports.Add($@"{modelImportPrefix}{namespaceValue}\{typeDefinition};");
}
else
{
var imported = import.ModelProperty.NamespaceName.Split('.')
.Select(x => x.ToFirstCharacterUpperCase())
.Aggregate(static (a, b) => $@"{a}\{b}")
.Replace(@"Me\", @"Users\Item\");
snippetImports.Add($@"{requestBuilderImportPrefix}\{imported}\{typeDefinition}");
}
// check if model has a nested namespace and append it to the import statement
continue; // Move to the next import.
}

if (import.ModelProperty.PropertyType == PropertyType.Enum)
{
snippetImports.Add($@"{modelImportPrefix}{namespaceValue}\{import.ModelProperty.Name.ToFirstCharacterUpperCase()};");
}
break;

case ImportKind.Path:
if (!string.IsNullOrEmpty(import.Path) && !string.IsNullOrEmpty(import.RequestBuilderName))
{
//construct path to request builder
var importPath = import.Path.Split('.')
.Select(static s => s.ToFirstCharacterUpperCase()).ToArray();
snippetImports.Add($"{requestBuilderImportPrefix}{string.Join("\\", importPath).Replace("\\Me\\", "\\Users\\Item\\")}\\{import.RequestBuilderName}{import.HttpMethod.ToLowerInvariant().ToFirstCharacterUpperCase()}RequestConfiguration;");
snippetImports.Add($@"{requestBuilderImportPrefix}{string.Join(@"\", importPath).Replace(@"\Me\", @"\Users\Item\")}\{import.RequestBuilderName}{import.HttpMethod.ToLowerInvariant().ToFirstCharacterUpperCase()}RequestConfiguration;");
}
break;
}
Expand Down Expand Up @@ -567,4 +612,14 @@

return result.EndsWith("()()") ? result[..^2] : result;
}

private static string GetPropertyTypeName(CodeProperty property)
{
return property.PropertyType switch
{
PropertyType.DateOnly => "Date",
PropertyType.TimeOnly => "Time",
_ => property.TypeDefinition
};
}
}
Loading
Loading