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

feature/typed indexers #3058

Merged
merged 5 commits into from
Aug 4, 2023
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
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 @@
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 @@
_ => 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 Expand Up @@ -1595,7 +1629,7 @@
var schemaDescription = schema.Description.CleanupDescription();
var newEnum = new CodeEnum
{
Name = declarationName,//TODO set the flag property

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

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment.
Documentation = new()
{
Description = !string.IsNullOrEmpty(schemaDescription) || !string.IsNullOrEmpty(schema.Reference?.Id) ?
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 @@
}
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 @@
generatedCode,
false,
static x => $"By{x.ToFirstCharacterUpperCase()}",
static x => x.ToFirstCharacterUpperCase());
static x => x.ToFirstCharacterUpperCase(),
GenerationLanguage.Swift);
cancellationToken.ThrowIfCancellationRequested();
ReplaceReservedNames(
generatedCode,
Expand Down Expand Up @@ -67,7 +68,7 @@
(@class.Properties.Any(x => x.IsOfKind(CodePropertyKind.AdditionalData)) ||
@class.StartBlock.Implements.Any(x => KiotaBuilder.AdditionalHolderInterface.Equals(x.Name, StringComparison.OrdinalIgnoreCase))),
"MicrosoftKiotaAbstractions", "AdditionalDataHolder"),
};//TODO add backing store types once we have them defined

Check warning on line 71 in src/Kiota.Builder/Refiners/SwiftRefiner.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment.
private static void CorrectImplements(ProprietableBlockDeclaration block)
{
block.ReplaceImplementByName(KiotaBuilder.AdditionalHolderInterface, "AdditionalDataHolder");
Expand Down Expand Up @@ -121,14 +122,14 @@
}
private static readonly Dictionary<string, (string, CodeUsing?)> DateTypesReplacements = new(StringComparer.OrdinalIgnoreCase) {
{"DateTimeOffset", ("Date", new CodeUsing {
Name = "Date",//TODO

Check warning on line 125 in src/Kiota.Builder/Refiners/SwiftRefiner.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment.
Declaration = new CodeType {
Name = "Foundation",
IsExternal = true,
},
})},
{"TimeSpan", ("Date", new CodeUsing {
Name = "Date",//TODO

Check warning on line 132 in src/Kiota.Builder/Refiners/SwiftRefiner.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment.
Declaration = new CodeType {
Name = "Foundation",
IsExternal = true,
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
Loading