diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cec6e500f..6917e3638f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for external documentation links on request execution methods (PHP Generation). [2038](https://github.com/microsoft/kiota/issues/2138) -- Adds support for nullable reference types in dotnet for projects running Netstandard 2.1/Net 6.0 and above []() +- Added support for nullable reference types in dotnet for projects running Netstandard 2.1/Net 6.0 and above [2073](https://github.com/microsoft/kiota/issues/2073) - Added support for multi-value headers to CLI generation. (Shell) -- Add support for multi-value headers for PHP Generation. [#2052](https://github.com/microsoft/kiota/issues/2052) -- Add support for Composed types (De)Serialization for PHP Generation. [#1814](https://github.com/microsoft/kiota/issues/1814) +- Added support for multi-value headers for PHP Generation. [#2052](https://github.com/microsoft/kiota/issues/2052) +- Added support for Composed types (De)Serialization for PHP Generation. [#1814](https://github.com/microsoft/kiota/issues/1814) +- Added support for backing store in Go. [466](https://github.com/microsoft/kiota/issues/466) ### Changed diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 94b29a7e39..5b15e92ee5 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1351,7 +1351,7 @@ private void CreatePropertiesForModelClass(OpenApiUrlTreeNode currentNode, OpenA private const string AdditionalDataPropName = "AdditionalData"; private const string BackingStorePropertyName = "BackingStore"; private const string BackingStoreInterface = "IBackingStore"; - private const string BackedModelInterface = "IBackedModel"; + internal const string BackedModelInterface = "IBackedModel"; private const string ParseNodeInterface = "IParseNode"; internal const string AdditionalHolderInterface = "IAdditionalDataHolder"; internal static void AddSerializationMembers(CodeClass model, bool includeAdditionalProperties, bool usesBackingStore) { diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index 1e92b45758..058ff37111 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -68,18 +68,21 @@ private static bool ReplaceSerializationModules(CodeElement generatedCode, Func< return false; } - protected static void CorrectCoreTypesForBackingStore(CodeElement currentElement, string defaultPropertyValue) { + protected static void CorrectCoreTypesForBackingStore(CodeElement currentElement, string defaultPropertyValue, Boolean hasPrefix = true) { if(currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model, CodeClassKind.RequestBuilder) && currentClass.StartBlock is ClassDeclaration currentDeclaration) { var backedModelImplements = currentDeclaration.Implements.FirstOrDefault(x => "IBackedModel".Equals(x.Name, StringComparison.OrdinalIgnoreCase)); if(backedModelImplements != null) backedModelImplements.Name = backedModelImplements.Name[1..]; //removing the "I" var backingStoreProperty = currentClass.GetPropertyOfKind(CodePropertyKind.BackingStore); - if(backingStoreProperty != null) + if (backingStoreProperty != null) + { backingStoreProperty.DefaultValue = defaultPropertyValue; + backingStoreProperty.NamePrefix = hasPrefix ? backingStoreProperty.NamePrefix : String.Empty; + } } - CrawlTree(currentElement, x => CorrectCoreTypesForBackingStore(x, defaultPropertyValue)); + CrawlTree(currentElement, x => CorrectCoreTypesForBackingStore(x, defaultPropertyValue, hasPrefix)); } private static bool DoesAnyParentHaveAPropertyWithDefaultValue(CodeClass current) { diff --git a/src/Kiota.Builder/Refiners/GoRefiner.cs b/src/Kiota.Builder/Refiners/GoRefiner.cs index 4048c39434..9b8beaa9bb 100644 --- a/src/Kiota.Builder/Refiners/GoRefiner.cs +++ b/src/Kiota.Builder/Refiners/GoRefiner.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; @@ -139,6 +140,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance RemoveHandlerFromRequestBuilder(generatedCode); AddContextParameterToGeneratorMethods(generatedCode); CorrectTypes(generatedCode); + CorrectCoreTypesForBackingStore(generatedCode, $"{conventions.StoreHash}.BackingStoreFactoryInstance()", false); + CorrectBackingStoreTypes(generatedCode); }, cancellationToken); } @@ -154,6 +157,59 @@ private string MergeOverLappedStrings(string start, string end) return $"{start}{end}"; } + + private void CorrectBackingStoreTypes(CodeElement currentElement, Dictionary result = null) + { + if (!_configuration.UsesBackingStore) + return; + + var currentMethod = currentElement as CodeMethod; + var parameters = currentMethod?.Parameters; + var codeParameters = parameters as CodeParameter[] ?? parameters?.ToArray(); + codeParameters?.Where(x => x.IsOfKind(CodeParameterKind.BackingStore) + && currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)).ToList().ForEach(x => + { + var type = (x.Type as CodeType)!; + type.Name = "BackingStoreFactory"; + type.IsNullable = false; + type.IsExternal = true; + }); + + if (currentElement is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.Model)) + { + var propertiesToCorrect = codeClass.Properties + .Where(x => x.IsOfKind(CodePropertyKind.Custom)) + .Union(codeClass.Methods + .Where(x => x.IsAccessor && (x.AccessedProperty?.IsOfKind(CodePropertyKind.Custom) ?? false)) + .Select(static x => x.AccessedProperty)) + .Distinct() + .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase); + + var targetNameSpace = codeClass.GetImmediateParentOfType(); + var modelsNameSpace = findClientNameSpace(targetNameSpace) + .FindNamespaceByName($"{_configuration.ClientNamespaceName}.models"); + + foreach (var property in propertiesToCorrect) + { + if (property.Type is CodeType codeType && codeType.TypeDefinition is CodeClass) + { + var interfaceName = $"{codeType.Name}able"; + var existing = targetNameSpace.FindChildByName(interfaceName, false) ?? + modelsNameSpace.FindChildByName(interfaceName) ?? + modelsNameSpace.FindChildByName(interfaceName.ToFirstCharacterUpperCase()); + + if (existing == null) + continue; + + CodeType type = (codeType.Clone() as CodeType)!; + type.Name = interfaceName; + type.TypeDefinition = existing; + property.Type = type; + } + } + } + CrawlTree(currentElement, x => CorrectBackingStoreTypes(x, result)); + } private static void CorrectTypes(CodeElement currentElement) { @@ -414,9 +470,15 @@ x.Type is CodeType pType && "github.com/microsoft/kiota-abstractions-go/serialization", "MergeDeserializersForIntersectionWrapper"), new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.Headers), "github.com/microsoft/kiota-abstractions-go", "RequestHeaders"), - };//TODO add backing store types once we have them defined - private static void CorrectImplements(ProprietableBlockDeclaration block) { + new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore), "github.com/microsoft/kiota-abstractions-go/store","BackingStore"), + new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.ClientConstructor) && + method.Parameters.Any(y => y.IsOfKind(CodeParameterKind.BackingStore)), + "github.com/microsoft/kiota-abstractions-go/store", "BackingStoreFactory"), + }; + + private void CorrectImplements(ProprietableBlockDeclaration block) { block.ReplaceImplementByName(KiotaBuilder.AdditionalHolderInterface, "AdditionalDataHolder"); + block.ReplaceImplementByName(KiotaBuilder.BackedModelInterface, "BackedModel"); } private static void CorrectMethodType(CodeMethod currentMethod) { var parentClass = currentMethod.Parent as CodeClass; diff --git a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs index b971463692..2790979698 100644 --- a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs @@ -381,15 +381,26 @@ private void WriteGetterBody(CodeMethod codeElement, LanguageWriter writer, Code if(!(codeElement.AccessedProperty?.Type?.IsNullable ?? true) && !(codeElement.AccessedProperty?.ReadOnly ?? true) && !string.IsNullOrEmpty(codeElement.AccessedProperty?.DefaultValue)) { - writer.WriteLines($"{conventions.GetTypeString(codeElement.AccessedProperty.Type, parentClass)} value = m.{backingStore.NamePrefix}{backingStore.Name.ToFirstCharacterLowerCase()}.Get(\"{codeElement.AccessedProperty.Name.ToFirstCharacterLowerCase()}\")", - "if value == nil {"); - writer.IncreaseIndent(); - writer.WriteLines($"value = {codeElement.AccessedProperty.DefaultValue};", + writer.WriteLines( + $"val , err := m.{backingStore.NamePrefix}{backingStore.Name.ToFirstCharacterLowerCase()}.Get(\"{codeElement.AccessedProperty.Name.ToFirstCharacterLowerCase()}\")"); + writer.WriteBlock("if err != nil {", "}", "panic(err)"); + writer.WriteBlock("if val == nil {" , "}", + $"var value = {codeElement.AccessedProperty.DefaultValue};", $"m.Set{codeElement.AccessedProperty?.Name?.ToFirstCharacterUpperCase()}(value);"); - writer.CloseBlock(); - writer.WriteLine("return value;"); - } else - writer.WriteLine($"return m.Get{backingStore.Name.ToFirstCharacterUpperCase()}().Get(\"{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()}\");"); + + writer.WriteLine($"return val.({conventions.GetTypeString(codeElement.AccessedProperty.Type, parentClass)})"); + } + else + { + var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement.Parent); + + writer.WriteLine($"val , err := m.Get{backingStore.Name.ToFirstCharacterUpperCase()}().Get(\"{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()}\")"); + + writer.WriteBlock("if val != nil {", "}", $"return val.({returnType})"); + writer.WriteBlock("if err != nil {", "}", "panic(err)"); + + writer.WriteLine("return nil"); + } } private void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { var requestAdapterProperty = parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter); @@ -483,7 +494,10 @@ private static void WriteSetterBody(CodeMethod codeElement, LanguageWriter write if(backingStore == null) writer.WriteLine($"m.{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()} = value"); else - writer.WriteLine($"m.Get{backingStore.Name.ToFirstCharacterUpperCase()}().Set(\"{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()}\", value)"); + { + writer.WriteLine($"err := m.Get{backingStore.Name.ToFirstCharacterUpperCase()}().Set(\"{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()}\", value)"); + writer.WriteBlock("if err != nil {", "}", "panic(err)"); + } } private void WriteIndexerBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer, string returnType) { var pathParametersProperty = parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters); diff --git a/src/Kiota.Builder/Writers/Go/GoConventionService.cs b/src/Kiota.Builder/Writers/Go/GoConventionService.cs index 7bbbde540f..f509894b97 100644 --- a/src/Kiota.Builder/Writers/Go/GoConventionService.cs +++ b/src/Kiota.Builder/Writers/Go/GoConventionService.cs @@ -17,6 +17,7 @@ public class GoConventionService : CommonLanguageConventionService #pragma warning disable CA1822 // Method should be static public string AbstractionsHash => "i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f"; public string SerializationHash => "i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91"; + public string StoreHash => "ie8677ce2c7e1b4c22e9c3827ecd078d41185424dd9eeb92b7d971ed2d49a392e"; public string StringsHash => "ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6"; public string ContextVarTypeName => "context.Context"; @@ -94,6 +95,7 @@ public string TranslateType(CodeTypeBase type, bool includeImportSymbol) "string" or "float32" or "float64" or "int32" or "int64" => type.Name, "String" or "Int64" or "Int32" or "Float32" or "Float64" => type.Name.ToFirstCharacterLowerCase(), //casing hack "context.Context" => "context.Context", + "BackedModel" => $"{StoreHash}.BackedModel", _ => type.Name.ToFirstCharacterUpperCase() ?? "Object", }; } diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index a266414bcc..f268bcb98d 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -83,6 +83,13 @@ internal void CloseBlock(string symbol = "}", bool decreaseIndent = true) WriteLine(symbol); } + internal void WriteBlock(string startSymbol = "{", string closeSymbol = "}", params string[] lines) + { + StartBlock(startSymbol); + WriteLines(lines); + CloseBlock(closeSymbol); + } + internal void Write(string text, bool includeIndent = true) { writer.Write(includeIndent ? GetIndent() + text : text); diff --git a/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs index 191cee9c60..6cbe4e796d 100644 --- a/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs @@ -1330,7 +1330,7 @@ public void WritesGetterToBackingStoreWithNonnullProperty() { method.Kind = CodeMethodKind.Getter; writer.Write(method); var result = tw.ToString(); - Assert.Contains("if value == nil", result); + Assert.Contains("if val == nil", result); Assert.Contains(defaultValue, result); } [Fact]