Skip to content

Commit

Permalink
Fix model import paths + Add support for enum imports (#2047)
Browse files Browse the repository at this point in the history
* Initial work on fixing paths.

* Remove unused test.

* Add imports for DateOnly, TimeOnly and Enums.

* Fix issue with spelling.

* Do slight refactor to improve style.

* Fix more issues with the snippets.

* Take care of corner cases.
  • Loading branch information
SilasKenneth authored May 16, 2024
1 parent 76db4b2 commit 4d352d4
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 16 deletions.
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
4 changes: 4 additions & 0 deletions CodeSnippetsReflection.OpenAPI.Test/PythonGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -878,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 Down Expand Up @@ -936,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 @@ -950,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
73 changes: 64 additions & 9 deletions CodeSnippetsReflection.OpenAPI/LanguageGenerators/PhpGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,33 +121,78 @@ public string GenerateCodeSnippet(SnippetModel snippetModel)
}
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.

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 @@ private static string GetFluentApiPath(IEnumerable<OpenApiUrlTreeNode> nodes, Sn

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

private static string GetPropertyTypeName(CodeProperty property)
{
return property.PropertyType switch
{
PropertyType.DateOnly => "Date",
PropertyType.TimeOnly => "Time",
_ => property.TypeDefinition
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,32 +78,64 @@ public string GenerateCodeSnippet(SnippetModel snippetModel)
}
private static HashSet<string> GetImportStatements(SnippetModel snippetModel)
{
const string modelImportPrefix = "from msgraph.generated.models";
const string requestBuilderImportPrefix = "from msgraph.generated";
var packageName = snippetModel.ApiVersion switch

Check warning on line 81 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/PythonGenerator.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.

Check warning on line 81 in CodeSnippetsReflection.OpenAPI/LanguageGenerators/PythonGenerator.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" => "msgraph",
"beta" => "msgraph_beta",
};
var modelImportPrefix = $"from {packageName}.generated.models";
var requestBuilderImportPrefix = $"from {packageName}.generated";
const string BaseRequestConfigImport = "from kiota_abstractions.base_request_configuration import RequestConfiguration";

var snippetImports = new HashSet<string>();

snippetImports.Add("from msgraph import GraphServiceClient");
snippetImports.Add($"from {packageName} import GraphServiceClient");

var imports = ImportsGenerator.GenerateImportTemplates(snippetModel);
foreach (var import in imports)
{
switch (import.Kind)
{
case ImportKind.Model:
// We don't use custom DateOnly and TimeOnly types for python snippets.
if (import.ModelProperty.PropertyType is PropertyType.DateOnly or PropertyType.TimeOnly)
continue;
var typeDefinition = import.ModelProperty.TypeDefinition;
const string modelsNamespaceName = "models.microsoft.graph";
var modelNamespaceStringLen = modelsNamespaceName.Length;
var importModelNamespace = import.ModelProperty.NamespaceName;
var inModelsNamespace = importModelNamespace.Equals(modelsNamespaceName);

var nested = !inModelsNamespace && importModelNamespace.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 => importModelNamespace[modelNamespaceStringLen..]
.Split('.', StringSplitOptions.RemoveEmptyEntries)
.Select(static x => x.ToSnakeCase())
.Aggregate(static (x, y) => $"{x}.{y}"),
false => string.Empty
};

var namespaceValue = !string.IsNullOrEmpty(othersParts) ? $".{othersParts}" : string.Empty;
if (typeDefinition != null){
if(typeDefinition.EndsWith("RequestBody",StringComparison.OrdinalIgnoreCase)){
var namespaceParts = import.ModelProperty.NamespaceName.Split('.').Select((s, i) => i == import.ModelProperty.NamespaceName.Split('.').Length - 1 ? s.ToSnakeCase() : s.ToLowerInvariant());
var namespaceParts = importModelNamespace.Split('.').Select((s, i) => i == import.ModelProperty.NamespaceName.Split('.').Length - 1 ? s.ToSnakeCase() : s.ToLowerInvariant());
var importString = $"{requestBuilderImportPrefix}.{string.Join(".", namespaceParts)}.{typeDefinition.ToSnakeCase()} import {typeDefinition}";
snippetImports.Add($"{importString.Replace(".me.", ".users.item.")}");

}
else{
snippetImports.Add($"{modelImportPrefix}.{typeDefinition.ToSnakeCase()} import {typeDefinition}");
snippetImports.Add($"{modelImportPrefix}{namespaceValue}.{typeDefinition.ToSnakeCase()} import {typeDefinition}");
}
}

if (import.ModelProperty.PropertyType == PropertyType.Enum)
{
var enumName = import.ModelProperty.Value.Split('.').First();
snippetImports.Add(
$"{modelImportPrefix}.{enumName.ToSnakeCase()} import {enumName}");
}


break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public static List<ImportTemplate> GenerateImportTemplates(SnippetModel snippet

private static void AddModelImportTemplates(CodeProperty node, List<ImportTemplate> imports)
{
if (!string.IsNullOrEmpty(node.NamespaceName))
if (!string.IsNullOrEmpty(node.NamespaceName) || (node.PropertyType is PropertyType.DateOnly or PropertyType.TimeOnly))
{
imports.Add(new ImportTemplate
{
Expand Down

0 comments on commit 4d352d4

Please sign in to comment.