Skip to content

Commit 1facf8c

Browse files
authored
Merge pull request #3058 from microsoft/feature/typed-indexers
feature/typed indexers
2 parents 227a906 + 1736364 commit 1facf8c

22 files changed

+264
-46
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Added auto-generation header for class and enums in CSharp [#2886](https://github.com/microsoft/kiota/issues/2886)
1616
- Added support for multipart form data request body in CSharp, Go, Java, and TypeScript. [#220](https://github.com/microsoft/kiota/issues/220)
1717
- Added support for base64 encoded properties in TypeScript.
18+
- Added support for type specific (non string) indexers parameters. [#2594](https://github.com/microsoft/kiota/issues/2594)
1819

1920
### Changed
2021

src/Kiota.Builder/CodeDOM/CodeClass.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,21 @@ public CodeComposedTypeBase? OriginalComposedType
4545
{
4646
get; set;
4747
}
48-
public CodeIndexer? Indexer
48+
public CodeIndexer? Indexer => InnerChildElements.Values.OfType<CodeIndexer>().FirstOrDefault(static x => !x.Deprecation?.IsDeprecated ?? true);
49+
public void AddIndexer(params CodeIndexer[] indexers)
4950
{
50-
set
51+
if (indexers == null || Array.Exists(indexers, static x => x == null))
52+
throw new ArgumentNullException(nameof(indexers));
53+
if (!indexers.Any())
54+
throw new ArgumentOutOfRangeException(nameof(indexers));
55+
56+
foreach (var value in indexers)
5157
{
52-
ArgumentNullException.ThrowIfNull(value);
53-
if (InnerChildElements.Values.OfType<CodeIndexer>().Any() || InnerChildElements.Values.OfType<CodeMethod>().Any(static x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)))
58+
var existingIndexers = InnerChildElements.Values.OfType<CodeIndexer>().ToArray();
59+
if (Array.Exists(existingIndexers, x => !x.IndexParameterName.Equals(value.IndexParameterName, StringComparison.OrdinalIgnoreCase)) ||
60+
InnerChildElements.Values.OfType<CodeMethod>().Any(static x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)))
5461
{
55-
if (Indexer is CodeIndexer existingIndexer)
62+
foreach (var existingIndexer in existingIndexers)
5663
{
5764
RemoveChildElement(existingIndexer);
5865
AddRange(CodeMethod.FromIndexer(existingIndexer, static x => $"With{x.ToFirstCharacterUpperCase()}", static x => x.ToFirstCharacterUpperCase(), true));
@@ -62,7 +69,6 @@ public CodeIndexer? Indexer
6269
else
6370
AddRange(value);
6471
}
65-
get => InnerChildElements.Values.OfType<CodeIndexer>().FirstOrDefault();
6672
}
6773
public override IEnumerable<CodeProperty> AddProperty(params CodeProperty[] properties)
6874
{

src/Kiota.Builder/CodeDOM/CodeIndexer.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22

33
namespace Kiota.Builder.CodeDOM;
4-
public class CodeIndexer : CodeTerminal, IDocumentedElement, IDeprecableElement
4+
public class CodeIndexer : CodeTerminal, IDocumentedElement, IDeprecableElement, ICloneable
55
{
66
#nullable disable // exposing property is required
77
private CodeTypeBase indexType;
@@ -44,4 +44,19 @@ public DeprecationInformation? Deprecation
4444
{
4545
get; set;
4646
}
47+
public object Clone()
48+
{
49+
return new CodeIndexer
50+
{
51+
Name = Name,
52+
Parent = Parent,
53+
IndexType = IndexType.Clone() as CodeTypeBase ?? throw new InvalidOperationException($"Cloning failed. Cloned type is invalid."),
54+
ReturnType = ReturnType.Clone() as CodeTypeBase ?? throw new InvalidOperationException($"Cloning failed. Cloned type is invalid."),
55+
IndexParameterName = IndexParameterName,
56+
SerializationName = SerializationName,
57+
Documentation = Documentation.Clone() as CodeDocumentation ?? throw new InvalidOperationException($"Cloning failed. Cloned type is invalid."),
58+
PathSegment = PathSegment,
59+
Deprecation = Deprecation == null ? null : Deprecation with { }
60+
};
61+
}
4762
}

src/Kiota.Builder/CodeDOM/CodeMethod.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using Kiota.Builder.Extensions;
56

67
namespace Kiota.Builder.CodeDOM;
78

@@ -78,7 +79,7 @@ public object Clone()
7879
public class CodeMethod : CodeTerminalWithKind<CodeMethodKind>, ICloneable, IDocumentedElement, IDeprecableElement
7980
{
8081
public static readonly CodeParameterKind ParameterKindForConvertedIndexers = CodeParameterKind.Custom;
81-
public static CodeMethod FromIndexer(CodeIndexer originalIndexer, Func<string, string> methodNameCallback, Func<string, string> parameterNameCallback, bool parameterNullable)
82+
public static CodeMethod FromIndexer(CodeIndexer originalIndexer, Func<string, string> methodNameCallback, Func<string, string> parameterNameCallback, bool parameterNullable, bool typeSpecificOverload = false)
8283
{
8384
ArgumentNullException.ThrowIfNull(originalIndexer);
8485
ArgumentNullException.ThrowIfNull(methodNameCallback);
@@ -89,7 +90,7 @@ public static CodeMethod FromIndexer(CodeIndexer originalIndexer, Func<string, s
8990
IsStatic = false,
9091
Access = AccessModifier.Public,
9192
Kind = CodeMethodKind.IndexerBackwardCompatibility,
92-
Name = methodNameCallback(originalIndexer.IndexParameterName),
93+
Name = methodNameCallback(originalIndexer.IndexParameterName) + (typeSpecificOverload ? originalIndexer.IndexType.Name.ToFirstCharacterUpperCase() : string.Empty),
9394
Documentation = new()
9495
{
9596
Description = originalIndexer.Documentation.Description,

src/Kiota.Builder/CodeDOM/DeprecationInformation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
namespace Kiota.Builder.CodeDOM;
44

5-
public record DeprecationInformation(string? Description, DateTimeOffset? Date, DateTimeOffset? RemovalDate, string? Version, bool IsDeprecated = true);
5+
public record DeprecationInformation(string? Description, DateTimeOffset? Date = null, DateTimeOffset? RemovalDate = null, string? Version = "", bool IsDeprecated = true);

src/Kiota.Builder/KiotaBuilder.cs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,10 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr
678678
var propType = child.Value.GetNavigationPropertyName(config.StructuredMimeTypes, child.Value.DoesNodeBelongToItemSubnamespace() ? ItemRequestBuilderSuffix : RequestBuilderSuffix);
679679

680680
if (child.Value.IsPathSegmentWithSingleSimpleParameter())
681-
codeClass.Indexer = CreateIndexer($"{propIdentifier}-indexer", propType, child.Value, currentNode);
681+
{
682+
var indexerParameterType = GetIndexerParameterType(child.Value, currentNode);
683+
codeClass.AddIndexer(CreateIndexer($"{propIdentifier}-indexer", propType, indexerParameterType, child.Value, currentNode));
684+
}
682685
else if (child.Value.IsComplexPathMultipleParameters())
683686
CreateMethod(propIdentifier, propType, codeClass, child.Value);
684687
else
@@ -967,23 +970,54 @@ private IEnumerable<CodeType> GetUnmappedTypeDefinitions(CodeElement codeElement
967970
_ => childElementsUnmappedTypes,
968971
};
969972
}
970-
private CodeIndexer CreateIndexer(string childIdentifier, string childType, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode)
973+
private static CodeType DefaultIndexerParameterType => new() { Name = "string", IsExternal = true };
974+
private CodeType GetIndexerParameterType(OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode)
975+
{
976+
var parameterName = currentNode.Path[parentNode.Path.Length..].Trim('\\', ForwardSlash, '{', '}');
977+
var parameter = currentNode.PathItems.TryGetValue(Constants.DefaultOpenApiLabel, out var pathItem) ? pathItem.Parameters
978+
.Select(static x => new { Parameter = x, IsPathParameter = true })
979+
.Union(currentNode.PathItems[Constants.DefaultOpenApiLabel].Operations.SelectMany(static x => x.Value.Parameters).Select(static x => new { Parameter = x, IsPathParameter = false }))
980+
.OrderBy(static x => x.IsPathParameter)
981+
.Select(static x => x.Parameter)
982+
.FirstOrDefault(x => x.Name.Equals(parameterName, StringComparison.OrdinalIgnoreCase) && x.In == ParameterLocation.Path) :
983+
default;
984+
var type = parameter switch
985+
{
986+
null => DefaultIndexerParameterType,
987+
_ => GetPrimitiveType(parameter.Schema),
988+
} ?? DefaultIndexerParameterType;
989+
type.IsNullable = false;
990+
return type;
991+
}
992+
private CodeIndexer[] CreateIndexer(string childIdentifier, string childType, CodeType parameterType, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode)
971993
{
972994
logger.LogTrace("Creating indexer {Name}", childIdentifier);
973-
return new CodeIndexer
995+
var result = new List<CodeIndexer> { new CodeIndexer
974996
{
975997
Name = childIdentifier,
976998
Documentation = new()
977999
{
9781000
Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Gets an item from the {currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName)} collection"),
9791001
},
980-
IndexType = new CodeType { Name = "string", IsExternal = true, },
1002+
IndexType = parameterType,
9811003
ReturnType = new CodeType { Name = childType },
9821004
SerializationName = currentNode.Segment.SanitizeParameterNameForUrlTemplate(),
9831005
PathSegment = parentNode.GetNodeNamespaceFromPath(string.Empty).Split('.').Last(),
9841006
IndexParameterName = currentNode.Segment.CleanupSymbolName(),
9851007
Deprecation = currentNode.GetDeprecationInformation(),
986-
};
1008+
}};
1009+
1010+
if (!"string".Equals(parameterType.Name, StringComparison.OrdinalIgnoreCase))
1011+
{ // adding a second indexer for the string version of the parameter so we keep backward compatibility
1012+
//TODO remove for v2
1013+
var backCompatibleValue = (CodeIndexer)result[0].Clone();
1014+
backCompatibleValue.Name += "-string";
1015+
backCompatibleValue.IndexType = DefaultIndexerParameterType;
1016+
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.");
1017+
result.Add(backCompatibleValue);
1018+
}
1019+
1020+
return result.ToArray();
9871021
}
9881022

9891023
private CodeProperty? CreateProperty(string childIdentifier, string childType, OpenApiSchema? propertySchema = null, CodeTypeBase? existingType = null, CodePropertyKind kind = CodePropertyKind.Custom)

src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -688,15 +688,40 @@ protected static void MoveClassesWithNamespaceNamesUnderNamespace(CodeElement cu
688688
}
689689
CrawlTree(currentElement, MoveClassesWithNamespaceNamesUnderNamespace);
690690
}
691-
protected static void ReplaceIndexersByMethodsWithParameter(CodeElement currentElement, bool parameterNullable, Func<string, string> methodNameCallback, Func<string, string> parameterNameCallback)
691+
private static readonly Func<string, CodeIndexer, bool> IsIndexerTypeSpecificVersion =
692+
(currentIndexerParameterName, existingIndexer) => currentIndexerParameterName.Equals(existingIndexer.IndexParameterName, StringComparison.OrdinalIgnoreCase) && !"string".Equals(existingIndexer.IndexType.Name, StringComparison.OrdinalIgnoreCase);
693+
protected static void ReplaceIndexersByMethodsWithParameter(CodeElement currentElement, bool parameterNullable, Func<string, string> methodNameCallback, Func<string, string> parameterNameCallback, GenerationLanguage language)
692694
{
693695
if (currentElement is CodeIndexer currentIndexer &&
694696
currentElement.Parent is CodeClass indexerParentClass)
695697
{
696-
indexerParentClass.RemoveChildElement(currentElement);
697-
indexerParentClass.AddMethod(CodeMethod.FromIndexer(currentIndexer, methodNameCallback, parameterNameCallback, parameterNullable));
698+
if (indexerParentClass.ContainsMember(currentElement.Name)) // TODO remove condition for v2 necessary because of the second case of Go block
699+
indexerParentClass.RemoveChildElement(currentElement);
700+
//TODO remove who block except for last else if body for v2
701+
var isIndexerStringBackwardCompatible = "string".Equals(currentIndexer.IndexType.Name, StringComparison.OrdinalIgnoreCase) &&
702+
currentIndexer.Deprecation is not null && currentIndexer.Deprecation.IsDeprecated &&
703+
(indexerParentClass.Methods.Any(x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility) && x.OriginalIndexer is not null && IsIndexerTypeSpecificVersion(currentIndexer.IndexParameterName, x.OriginalIndexer)) ||
704+
(indexerParentClass.Indexer != null && indexerParentClass.Indexer != currentIndexer && IsIndexerTypeSpecificVersion(currentIndexer.IndexParameterName, indexerParentClass.Indexer)));
705+
if (isIndexerStringBackwardCompatible && language == GenerationLanguage.Go)
706+
{
707+
if (indexerParentClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility) && x.OriginalIndexer is not null && IsIndexerTypeSpecificVersion(currentIndexer.IndexParameterName, x.OriginalIndexer)) is CodeMethod typeSpecificCompatibleMethod &&
708+
typeSpecificCompatibleMethod.OriginalIndexer is not null)
709+
{
710+
indexerParentClass.RenameChildElement(typeSpecificCompatibleMethod.Name, typeSpecificCompatibleMethod.Name + typeSpecificCompatibleMethod.OriginalIndexer.IndexType.Name.ToFirstCharacterUpperCase());
711+
}
712+
else if (indexerParentClass.Indexer != null && indexerParentClass.Indexer != currentIndexer && IsIndexerTypeSpecificVersion(currentIndexer.IndexParameterName, indexerParentClass.Indexer))
713+
{
714+
var specificIndexer = indexerParentClass.Indexer;
715+
indexerParentClass.RemoveChildElement(specificIndexer);
716+
indexerParentClass.AddMethod(CodeMethod.FromIndexer(specificIndexer, methodNameCallback, parameterNameCallback, parameterNullable, true));
717+
}
718+
indexerParentClass.AddMethod(CodeMethod.FromIndexer(currentIndexer, methodNameCallback, parameterNameCallback, parameterNullable));
719+
}
720+
else if (!isIndexerStringBackwardCompatible)
721+
indexerParentClass.AddMethod(CodeMethod.FromIndexer(currentIndexer, methodNameCallback, parameterNameCallback, parameterNullable));
722+
698723
}
699-
CrawlTree(currentElement, c => ReplaceIndexersByMethodsWithParameter(c, parameterNullable, methodNameCallback, parameterNameCallback));
724+
CrawlTree(currentElement, c => ReplaceIndexersByMethodsWithParameter(c, parameterNullable, methodNameCallback, parameterNameCallback, language));
700725
}
701726
internal void DisableActionOf(CodeElement current, params CodeParameterKind[] kinds)
702727
{

src/Kiota.Builder/Refiners/GoRefiner.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
3333
generatedCode,
3434
false,
3535
static x => $"By{x.ToFirstCharacterUpperCase()}",
36-
static x => x.ToFirstCharacterLowerCase());
36+
static x => x.ToFirstCharacterLowerCase(),
37+
GenerationLanguage.Go);
3738
FlattenNestedHierarchy(generatedCode);
3839
FlattenGoParamsFileNames(generatedCode);
3940
FlattenGoFileNames(generatedCode);

src/Kiota.Builder/Refiners/JavaRefiner.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
5353
ReplaceIndexersByMethodsWithParameter(generatedCode,
5454
true,
5555
static x => $"By{x.ToFirstCharacterUpperCase()}",
56-
static x => x.ToFirstCharacterLowerCase());
56+
static x => x.ToFirstCharacterLowerCase(),
57+
GenerationLanguage.Java);
5758
cancellationToken.ThrowIfCancellationRequested();
5859
RemoveCancellationParameter(generatedCode);
5960
ConvertUnionTypesToWrapper(generatedCode,

src/Kiota.Builder/Refiners/PhpRefiner.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
3737
ReplaceIndexersByMethodsWithParameter(generatedCode,
3838
false,
3939
static x => $"By{x.ToFirstCharacterUpperCase()}",
40-
static x => x.ToFirstCharacterLowerCase());
40+
static x => x.ToFirstCharacterLowerCase(),
41+
GenerationLanguage.PHP);
4142
RemoveCancellationParameter(generatedCode);
4243
ConvertUnionTypesToWrapper(generatedCode,
4344
_configuration.UsesBackingStore,

src/Kiota.Builder/Refiners/PythonRefiner.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
3333
ReplaceIndexersByMethodsWithParameter(generatedCode,
3434
false,
3535
static x => $"by_{x.ToSnakeCase()}",
36-
static x => x.ToSnakeCase());
36+
static x => x.ToSnakeCase(),
37+
GenerationLanguage.Python);
3738
RemoveCancellationParameter(generatedCode);
3839
CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType, CorrectImplements);
3940
cancellationToken.ThrowIfCancellationRequested();

src/Kiota.Builder/Refiners/RubyRefiner.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
2020
ReplaceIndexersByMethodsWithParameter(generatedCode,
2121
false,
2222
static x => $"by_{x.ToSnakeCase()}",
23-
static x => x.ToSnakeCase());
23+
static x => x.ToSnakeCase(),
24+
GenerationLanguage.Ruby);
2425
MoveRequestBuilderPropertiesToBaseType(generatedCode,
2526
new CodeUsing
2627
{

src/Kiota.Builder/Refiners/SwiftRefiner.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
2222
generatedCode,
2323
false,
2424
static x => $"By{x.ToFirstCharacterUpperCase()}",
25-
static x => x.ToFirstCharacterUpperCase());
25+
static x => x.ToFirstCharacterUpperCase(),
26+
GenerationLanguage.Swift);
2627
cancellationToken.ThrowIfCancellationRequested();
2728
ReplaceReservedNames(
2829
generatedCode,

src/Kiota.Builder/Refiners/TypeScriptRefiner.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
3232
ReplaceIndexersByMethodsWithParameter(generatedCode,
3333
false,
3434
static x => $"by{x.ToFirstCharacterUpperCase()}",
35-
static x => x.ToFirstCharacterLowerCase());
35+
static x => x.ToFirstCharacterLowerCase(),
36+
GenerationLanguage.TypeScript);
3637
RemoveCancellationParameter(generatedCode);
3738
CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType, CorrectImplements);
3839
CorrectCoreTypesForBackingStore(generatedCode, "BackingStoreFactorySingleton.instance.createBackingStore()");

0 commit comments

Comments
 (0)