Skip to content

Commit

Permalink
Merge pull request #3058 from microsoft/feature/typed-indexers
Browse files Browse the repository at this point in the history
feature/typed indexers
  • Loading branch information
baywet authored Aug 4, 2023
2 parents 227a906 + 1736364 commit 1facf8c
Show file tree
Hide file tree
Showing 22 changed files with 264 additions and 46 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added auto-generation header for class and enums in CSharp [#2886](https://github.com/microsoft/kiota/issues/2886)
- Added support for multipart form data request body in CSharp, Go, Java, and TypeScript. [#220](https://github.com/microsoft/kiota/issues/220)
- Added support for base64 encoded properties in TypeScript.
- Added support for type specific (non string) indexers parameters. [#2594](https://github.com/microsoft/kiota/issues/2594)

### Changed

Expand Down
18 changes: 12 additions & 6 deletions src/Kiota.Builder/CodeDOM/CodeClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,21 @@ public CodeComposedTypeBase? OriginalComposedType
{
get; set;
}
public CodeIndexer? Indexer
public CodeIndexer? Indexer => InnerChildElements.Values.OfType<CodeIndexer>().FirstOrDefault(static x => !x.Deprecation?.IsDeprecated ?? true);
public void AddIndexer(params CodeIndexer[] indexers)
{
set
if (indexers == null || Array.Exists(indexers, static x => x == null))
throw new ArgumentNullException(nameof(indexers));
if (!indexers.Any())
throw new ArgumentOutOfRangeException(nameof(indexers));

foreach (var value in indexers)
{
ArgumentNullException.ThrowIfNull(value);
if (InnerChildElements.Values.OfType<CodeIndexer>().Any() || InnerChildElements.Values.OfType<CodeMethod>().Any(static x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)))
var existingIndexers = InnerChildElements.Values.OfType<CodeIndexer>().ToArray();
if (Array.Exists(existingIndexers, x => !x.IndexParameterName.Equals(value.IndexParameterName, StringComparison.OrdinalIgnoreCase)) ||
InnerChildElements.Values.OfType<CodeMethod>().Any(static x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)))
{
if (Indexer is CodeIndexer existingIndexer)
foreach (var existingIndexer in existingIndexers)
{
RemoveChildElement(existingIndexer);
AddRange(CodeMethod.FromIndexer(existingIndexer, static x => $"With{x.ToFirstCharacterUpperCase()}", static x => x.ToFirstCharacterUpperCase(), true));
Expand All @@ -62,7 +69,6 @@ public CodeIndexer? Indexer
else
AddRange(value);
}
get => InnerChildElements.Values.OfType<CodeIndexer>().FirstOrDefault();
}
public override IEnumerable<CodeProperty> AddProperty(params CodeProperty[] properties)
{
Expand Down
17 changes: 16 additions & 1 deletion src/Kiota.Builder/CodeDOM/CodeIndexer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;

namespace Kiota.Builder.CodeDOM;
public class CodeIndexer : CodeTerminal, IDocumentedElement, IDeprecableElement
public class CodeIndexer : CodeTerminal, IDocumentedElement, IDeprecableElement, ICloneable
{
#nullable disable // exposing property is required
private CodeTypeBase indexType;
Expand Down Expand Up @@ -44,4 +44,19 @@ public DeprecationInformation? Deprecation
{
get; set;
}
public object Clone()
{
return new CodeIndexer
{
Name = Name,
Parent = Parent,
IndexType = IndexType.Clone() as CodeTypeBase ?? throw new InvalidOperationException($"Cloning failed. Cloned type is invalid."),
ReturnType = ReturnType.Clone() as CodeTypeBase ?? throw new InvalidOperationException($"Cloning failed. Cloned type is invalid."),
IndexParameterName = IndexParameterName,
SerializationName = SerializationName,
Documentation = Documentation.Clone() as CodeDocumentation ?? throw new InvalidOperationException($"Cloning failed. Cloned type is invalid."),
PathSegment = PathSegment,
Deprecation = Deprecation == null ? null : Deprecation with { }
};
}
}
5 changes: 3 additions & 2 deletions src/Kiota.Builder/CodeDOM/CodeMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Kiota.Builder.Extensions;

namespace Kiota.Builder.CodeDOM;

Expand Down Expand Up @@ -78,7 +79,7 @@ public object Clone()
public class CodeMethod : CodeTerminalWithKind<CodeMethodKind>, ICloneable, IDocumentedElement, IDeprecableElement
{
public static readonly CodeParameterKind ParameterKindForConvertedIndexers = CodeParameterKind.Custom;
public static CodeMethod FromIndexer(CodeIndexer originalIndexer, Func<string, string> methodNameCallback, Func<string, string> parameterNameCallback, bool parameterNullable)
public static CodeMethod FromIndexer(CodeIndexer originalIndexer, Func<string, string> methodNameCallback, Func<string, string> parameterNameCallback, bool parameterNullable, bool typeSpecificOverload = false)
{
ArgumentNullException.ThrowIfNull(originalIndexer);
ArgumentNullException.ThrowIfNull(methodNameCallback);
Expand All @@ -89,7 +90,7 @@ public static CodeMethod FromIndexer(CodeIndexer originalIndexer, Func<string, s
IsStatic = false,
Access = AccessModifier.Public,
Kind = CodeMethodKind.IndexerBackwardCompatibility,
Name = methodNameCallback(originalIndexer.IndexParameterName),
Name = methodNameCallback(originalIndexer.IndexParameterName) + (typeSpecificOverload ? originalIndexer.IndexType.Name.ToFirstCharacterUpperCase() : string.Empty),
Documentation = new()
{
Description = originalIndexer.Documentation.Description,
Expand Down
2 changes: 1 addition & 1 deletion src/Kiota.Builder/CodeDOM/DeprecationInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

namespace Kiota.Builder.CodeDOM;

public record DeprecationInformation(string? Description, DateTimeOffset? Date, DateTimeOffset? RemovalDate, string? Version, bool IsDeprecated = true);
public record DeprecationInformation(string? Description, DateTimeOffset? Date = null, DateTimeOffset? RemovalDate = null, string? Version = "", bool IsDeprecated = true);
44 changes: 39 additions & 5 deletions src/Kiota.Builder/KiotaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,10 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr
var propType = child.Value.GetNavigationPropertyName(config.StructuredMimeTypes, child.Value.DoesNodeBelongToItemSubnamespace() ? ItemRequestBuilderSuffix : RequestBuilderSuffix);

if (child.Value.IsPathSegmentWithSingleSimpleParameter())
codeClass.Indexer = CreateIndexer($"{propIdentifier}-indexer", propType, child.Value, currentNode);
{
var indexerParameterType = GetIndexerParameterType(child.Value, currentNode);
codeClass.AddIndexer(CreateIndexer($"{propIdentifier}-indexer", propType, indexerParameterType, child.Value, currentNode));
}
else if (child.Value.IsComplexPathMultipleParameters())
CreateMethod(propIdentifier, propType, codeClass, child.Value);
else
Expand Down Expand Up @@ -967,23 +970,54 @@ private IEnumerable<CodeType> GetUnmappedTypeDefinitions(CodeElement codeElement
_ => childElementsUnmappedTypes,
};
}
private CodeIndexer CreateIndexer(string childIdentifier, string childType, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode)
private static CodeType DefaultIndexerParameterType => new() { Name = "string", IsExternal = true };
private CodeType GetIndexerParameterType(OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode)
{
var parameterName = currentNode.Path[parentNode.Path.Length..].Trim('\\', ForwardSlash, '{', '}');
var parameter = currentNode.PathItems.TryGetValue(Constants.DefaultOpenApiLabel, out var pathItem) ? pathItem.Parameters
.Select(static x => new { Parameter = x, IsPathParameter = true })
.Union(currentNode.PathItems[Constants.DefaultOpenApiLabel].Operations.SelectMany(static x => x.Value.Parameters).Select(static x => new { Parameter = x, IsPathParameter = false }))
.OrderBy(static x => x.IsPathParameter)
.Select(static x => x.Parameter)
.FirstOrDefault(x => x.Name.Equals(parameterName, StringComparison.OrdinalIgnoreCase) && x.In == ParameterLocation.Path) :
default;
var type = parameter switch
{
null => DefaultIndexerParameterType,
_ => GetPrimitiveType(parameter.Schema),
} ?? DefaultIndexerParameterType;
type.IsNullable = false;
return type;
}
private CodeIndexer[] CreateIndexer(string childIdentifier, string childType, CodeType parameterType, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode)
{
logger.LogTrace("Creating indexer {Name}", childIdentifier);
return new CodeIndexer
var result = new List<CodeIndexer> { new CodeIndexer
{
Name = childIdentifier,
Documentation = new()
{
Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Gets an item from the {currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName)} collection"),
},
IndexType = new CodeType { Name = "string", IsExternal = true, },
IndexType = parameterType,
ReturnType = new CodeType { Name = childType },
SerializationName = currentNode.Segment.SanitizeParameterNameForUrlTemplate(),
PathSegment = parentNode.GetNodeNamespaceFromPath(string.Empty).Split('.').Last(),
IndexParameterName = currentNode.Segment.CleanupSymbolName(),
Deprecation = currentNode.GetDeprecationInformation(),
};
}};

if (!"string".Equals(parameterType.Name, StringComparison.OrdinalIgnoreCase))
{ // adding a second indexer for the string version of the parameter so we keep backward compatibility
//TODO remove for v2

Check warning on line 1012 in src/Kiota.Builder/KiotaBuilder.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment.
var backCompatibleValue = (CodeIndexer)result[0].Clone();
backCompatibleValue.Name += "-string";
backCompatibleValue.IndexType = DefaultIndexerParameterType;
backCompatibleValue.Deprecation = new DeprecationInformation("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.");
result.Add(backCompatibleValue);
}

return result.ToArray();
}

private CodeProperty? CreateProperty(string childIdentifier, string childType, OpenApiSchema? propertySchema = null, CodeTypeBase? existingType = null, CodePropertyKind kind = CodePropertyKind.Custom)
Expand Down
33 changes: 29 additions & 4 deletions src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -688,15 +688,40 @@ protected static void MoveClassesWithNamespaceNamesUnderNamespace(CodeElement cu
}
CrawlTree(currentElement, MoveClassesWithNamespaceNamesUnderNamespace);
}
protected static void ReplaceIndexersByMethodsWithParameter(CodeElement currentElement, bool parameterNullable, Func<string, string> methodNameCallback, Func<string, string> parameterNameCallback)
private static readonly Func<string, CodeIndexer, bool> IsIndexerTypeSpecificVersion =
(currentIndexerParameterName, existingIndexer) => currentIndexerParameterName.Equals(existingIndexer.IndexParameterName, StringComparison.OrdinalIgnoreCase) && !"string".Equals(existingIndexer.IndexType.Name, StringComparison.OrdinalIgnoreCase);
protected static void ReplaceIndexersByMethodsWithParameter(CodeElement currentElement, bool parameterNullable, Func<string, string> methodNameCallback, Func<string, string> parameterNameCallback, GenerationLanguage language)
{
if (currentElement is CodeIndexer currentIndexer &&
currentElement.Parent is CodeClass indexerParentClass)
{
indexerParentClass.RemoveChildElement(currentElement);
indexerParentClass.AddMethod(CodeMethod.FromIndexer(currentIndexer, methodNameCallback, parameterNameCallback, parameterNullable));
if (indexerParentClass.ContainsMember(currentElement.Name)) // TODO remove condition for v2 necessary because of the second case of Go block

Check warning on line 698 in src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment.
indexerParentClass.RemoveChildElement(currentElement);
//TODO remove who block except for last else if body for v2

Check warning on line 700 in src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment.
var isIndexerStringBackwardCompatible = "string".Equals(currentIndexer.IndexType.Name, StringComparison.OrdinalIgnoreCase) &&
currentIndexer.Deprecation is not null && currentIndexer.Deprecation.IsDeprecated &&
(indexerParentClass.Methods.Any(x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility) && x.OriginalIndexer is not null && IsIndexerTypeSpecificVersion(currentIndexer.IndexParameterName, x.OriginalIndexer)) ||
(indexerParentClass.Indexer != null && indexerParentClass.Indexer != currentIndexer && IsIndexerTypeSpecificVersion(currentIndexer.IndexParameterName, indexerParentClass.Indexer)));
if (isIndexerStringBackwardCompatible && language == GenerationLanguage.Go)
{
if (indexerParentClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility) && x.OriginalIndexer is not null && IsIndexerTypeSpecificVersion(currentIndexer.IndexParameterName, x.OriginalIndexer)) is CodeMethod typeSpecificCompatibleMethod &&
typeSpecificCompatibleMethod.OriginalIndexer is not null)
{
indexerParentClass.RenameChildElement(typeSpecificCompatibleMethod.Name, typeSpecificCompatibleMethod.Name + typeSpecificCompatibleMethod.OriginalIndexer.IndexType.Name.ToFirstCharacterUpperCase());
}
else if (indexerParentClass.Indexer != null && indexerParentClass.Indexer != currentIndexer && IsIndexerTypeSpecificVersion(currentIndexer.IndexParameterName, indexerParentClass.Indexer))
{
var specificIndexer = indexerParentClass.Indexer;
indexerParentClass.RemoveChildElement(specificIndexer);
indexerParentClass.AddMethod(CodeMethod.FromIndexer(specificIndexer, methodNameCallback, parameterNameCallback, parameterNullable, true));
}
indexerParentClass.AddMethod(CodeMethod.FromIndexer(currentIndexer, methodNameCallback, parameterNameCallback, parameterNullable));
}
else if (!isIndexerStringBackwardCompatible)
indexerParentClass.AddMethod(CodeMethod.FromIndexer(currentIndexer, methodNameCallback, parameterNameCallback, parameterNullable));

}
CrawlTree(currentElement, c => ReplaceIndexersByMethodsWithParameter(c, parameterNullable, methodNameCallback, parameterNameCallback));
CrawlTree(currentElement, c => ReplaceIndexersByMethodsWithParameter(c, parameterNullable, methodNameCallback, parameterNameCallback, language));
}
internal void DisableActionOf(CodeElement current, params CodeParameterKind[] kinds)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Kiota.Builder/Refiners/GoRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
generatedCode,
false,
static x => $"By{x.ToFirstCharacterUpperCase()}",
static x => x.ToFirstCharacterLowerCase());
static x => x.ToFirstCharacterLowerCase(),
GenerationLanguage.Go);
FlattenNestedHierarchy(generatedCode);
FlattenGoParamsFileNames(generatedCode);
FlattenGoFileNames(generatedCode);
Expand Down
3 changes: 2 additions & 1 deletion src/Kiota.Builder/Refiners/JavaRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
ReplaceIndexersByMethodsWithParameter(generatedCode,
true,
static x => $"By{x.ToFirstCharacterUpperCase()}",
static x => x.ToFirstCharacterLowerCase());
static x => x.ToFirstCharacterLowerCase(),
GenerationLanguage.Java);
cancellationToken.ThrowIfCancellationRequested();
RemoveCancellationParameter(generatedCode);
ConvertUnionTypesToWrapper(generatedCode,
Expand Down
3 changes: 2 additions & 1 deletion src/Kiota.Builder/Refiners/PhpRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
ReplaceIndexersByMethodsWithParameter(generatedCode,
false,
static x => $"By{x.ToFirstCharacterUpperCase()}",
static x => x.ToFirstCharacterLowerCase());
static x => x.ToFirstCharacterLowerCase(),
GenerationLanguage.PHP);
RemoveCancellationParameter(generatedCode);
ConvertUnionTypesToWrapper(generatedCode,
_configuration.UsesBackingStore,
Expand Down
3 changes: 2 additions & 1 deletion src/Kiota.Builder/Refiners/PythonRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
ReplaceIndexersByMethodsWithParameter(generatedCode,
false,
static x => $"by_{x.ToSnakeCase()}",
static x => x.ToSnakeCase());
static x => x.ToSnakeCase(),
GenerationLanguage.Python);
RemoveCancellationParameter(generatedCode);
CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType, CorrectImplements);
cancellationToken.ThrowIfCancellationRequested();
Expand Down
3 changes: 2 additions & 1 deletion src/Kiota.Builder/Refiners/RubyRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
ReplaceIndexersByMethodsWithParameter(generatedCode,
false,
static x => $"by_{x.ToSnakeCase()}",
static x => x.ToSnakeCase());
static x => x.ToSnakeCase(),
GenerationLanguage.Ruby);
MoveRequestBuilderPropertiesToBaseType(generatedCode,
new CodeUsing
{
Expand Down
3 changes: 2 additions & 1 deletion src/Kiota.Builder/Refiners/SwiftRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
generatedCode,
false,
static x => $"By{x.ToFirstCharacterUpperCase()}",
static x => x.ToFirstCharacterUpperCase());
static x => x.ToFirstCharacterUpperCase(),
GenerationLanguage.Swift);
cancellationToken.ThrowIfCancellationRequested();
ReplaceReservedNames(
generatedCode,
Expand Down
3 changes: 2 additions & 1 deletion src/Kiota.Builder/Refiners/TypeScriptRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
ReplaceIndexersByMethodsWithParameter(generatedCode,
false,
static x => $"by{x.ToFirstCharacterUpperCase()}",
static x => x.ToFirstCharacterLowerCase());
static x => x.ToFirstCharacterLowerCase(),
GenerationLanguage.TypeScript);
RemoveCancellationParameter(generatedCode);
CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType, CorrectImplements);
CorrectCoreTypesForBackingStore(generatedCode, "BackingStoreFactorySingleton.instance.createBackingStore()");
Expand Down
Loading

0 comments on commit 1facf8c

Please sign in to comment.