diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f8887eb4..a5b584420b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for external documentation links within descriptions in Python. [#2041](https://github.com/microsoft/kiota/issues/2041) - Added support for API manifests. [#3104](https://github.com/microsoft/kiota/issues/3104) - Added support for reserved path parameters. [#2320](https://github.com/microsoft/kiota/issues/2320) +- Added support for csv values in enums using a mask. ### Changed diff --git a/src/Kiota.Builder/Refiners/GoRefiner.cs b/src/Kiota.Builder/Refiners/GoRefiner.cs index d78e89d981..4769417578 100644 --- a/src/Kiota.Builder/Refiners/GoRefiner.cs +++ b/src/Kiota.Builder/Refiners/GoRefiner.cs @@ -116,7 +116,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance cancellationToken.ThrowIfCancellationRequested(); MakeModelPropertiesNullable( generatedCode); - AddErrorImportForEnums( + AddErrorAndStringsImportForEnums( generatedCode); var defaultConfiguration = new GenerationConfiguration(); ReplaceDefaultSerializationModules( @@ -469,7 +469,7 @@ private static void ReplaceRequestBuilderPropertiesByMethods(CodeElement current } CrawlTree(currentElement, ReplaceRequestBuilderPropertiesByMethods); } - private static void AddErrorImportForEnums(CodeElement currentElement) + private static void AddErrorAndStringsImportForEnums(CodeElement currentElement) { if (currentElement is CodeEnum currentEnum) { @@ -477,8 +477,16 @@ private static void AddErrorImportForEnums(CodeElement currentElement) { Name = "errors", }); + + if (currentEnum.Flags) + { + currentEnum.AddUsing(new CodeUsing + { + Name = "strings", + }); + } } - CrawlTree(currentElement, AddErrorImportForEnums); + CrawlTree(currentElement, AddErrorAndStringsImportForEnums); } private static readonly GoConventionService conventions = new(); private static readonly HashSet typeToSkipStrConv = new(StringComparer.OrdinalIgnoreCase) { diff --git a/src/Kiota.Builder/Writers/Go/CodeEnumWriter.cs b/src/Kiota.Builder/Writers/Go/CodeEnumWriter.cs index a6a80116a5..546e653627 100644 --- a/src/Kiota.Builder/Writers/Go/CodeEnumWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeEnumWriter.cs @@ -16,12 +16,11 @@ public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter write if (codeElement.Parent is CodeNamespace ns) writer.WriteLine($"package {ns.Name.GetLastNamespaceSegment().Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase)}"); - writer.WriteLine("import ("); - writer.IncreaseIndent(); - foreach (var cUsing in codeElement.Usings) + writer.StartBlock("import ("); + foreach (var cUsing in codeElement.Usings.OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase)) writer.WriteLine($"\"{cUsing.Name}\""); - writer.DecreaseIndent(); - writer.WriteLine(")"); + writer.CloseBlock(")"); + var typeName = codeElement.Name.ToFirstCharacterUpperCase(); conventions.WriteShortDescription(codeElement.Documentation.Description, writer); conventions.WriteDeprecation(codeElement, writer); @@ -40,43 +39,106 @@ public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter write iotaSuffix = string.Empty; } writer.DecreaseIndent(); - writer.WriteLines(")", - string.Empty, - $"func (i {typeName}) String() string {{"); - writer.IncreaseIndent(); - var literalOptions = enumOptions - .Select(x => $"\"{x.WireName}\"") - .Aggregate((x, y) => x + ", " + y); - writer.WriteLine($"return []string{{{literalOptions}}}[i]"); - writer.DecreaseIndent(); - writer.WriteLines("}", - $"func Parse{typeName}(v string) (any, error) {{"); - writer.IncreaseIndent(); - writer.WriteLine($"result := {enumOptions.First().Name.ToUpperInvariant()}_{typeName.ToUpperInvariant()}"); - writer.WriteLine("switch v {"); - writer.IncreaseIndent(); - foreach (var item in enumOptions) + writer.WriteLines(")", string.Empty); + + var isMultiValue = codeElement.Flags; + WriteStringFunction(codeElement, writer, isMultiValue); + WriteParsableEnum(codeElement, writer, isMultiValue); + WriteSerializeFunction(codeElement, writer, isMultiValue); + WriteMultiValueFunction(codeElement, writer, isMultiValue); + } + + private void WriteStringFunction(CodeEnum codeElement, LanguageWriter writer, Boolean isMultiValue) + { + var typeName = codeElement.Name.ToFirstCharacterUpperCase(); + var enumOptions = codeElement.Options; + + if (isMultiValue) { - writer.WriteLine($"case \"{item.WireName}\":"); - writer.IncreaseIndent(); - writer.WriteLine($"result = {item.Name.ToUpperInvariant()}_{typeName.ToUpperInvariant()}"); - writer.DecreaseIndent(); + writer.StartBlock($"func (i {typeName}) String() string {{"); + writer.WriteLine("var values []string"); + var literalOptions = enumOptions + .Select(x => $"\"{x.WireName}\"") + .Aggregate((x, y) => x + ", " + y); + writer.StartBlock($"for p := {typeName}(1); p <= {enumOptions.Last().Name.ToUpperInvariant()}_{typeName.ToUpperInvariant()}; p <<= 1 {{"); + writer.StartBlock($"if i&p == p {{"); + writer.WriteLine($"values = append(values, []string{{{literalOptions}}}[p])"); + writer.CloseBlock(); + writer.CloseBlock(); + writer.WriteLine("return strings.Join(values, \",\")"); + writer.CloseBlock(); } - writer.WriteLine("default:"); - writer.IncreaseIndent(); + else + { + writer.StartBlock($"func (i {typeName}) String() string {{"); + var literalOptions = enumOptions + .Select(x => $"\"{x.WireName}\"") + .Aggregate((x, y) => x + ", " + y); + writer.WriteLine($"return []string{{{literalOptions}}}[i]"); + writer.CloseBlock(); + } + } + + private void WriteParsableEnum(CodeEnum codeElement, LanguageWriter writer, Boolean isMultiValue) + { + var typeName = codeElement.Name.ToFirstCharacterUpperCase(); + var enumOptions = codeElement.Options; + + writer.StartBlock($"func Parse{typeName}(v string) (any, error) {{"); + + if (isMultiValue) + { + writer.WriteLine($"var result {typeName}"); + writer.WriteLine("values := strings.Split(v, \",\")"); + writer.StartBlock("for _, str := range values {"); + writer.StartBlock("switch str {"); + foreach (var item in enumOptions) + { + writer.StartBlock($"case \"{item.WireName}\":"); + writer.WriteLine($"result |= {item.Name.ToUpperInvariant()}_{typeName.ToUpperInvariant()}"); + writer.DecreaseIndent(); + } + } + else + { + writer.WriteLine($"result := {enumOptions.First().Name.ToUpperInvariant()}_{typeName.ToUpperInvariant()}"); + writer.StartBlock("switch v {"); + foreach (var item in enumOptions) + { + writer.StartBlock($"case \"{item.WireName}\":"); + writer.WriteLine($"result = {item.Name.ToUpperInvariant()}_{typeName.ToUpperInvariant()}"); + writer.DecreaseIndent(); + } + } + + + writer.StartBlock("default:"); writer.WriteLine($"return 0, errors.New(\"Unknown {typeName} value: \" + v)"); writer.DecreaseIndent(); writer.CloseBlock(); + if (isMultiValue) writer.CloseBlock(); writer.WriteLine("return &result, nil"); writer.CloseBlock(); - writer.WriteLine($"func Serialize{typeName}(values []{typeName}) []string {{"); - writer.IncreaseIndent(); + } + + private void WriteSerializeFunction(CodeEnum codeElement, LanguageWriter writer, Boolean isMultiValue) + { + var typeName = codeElement.Name.ToFirstCharacterUpperCase(); + writer.StartBlock($"func Serialize{typeName}(values []{typeName}) []string {{"); writer.WriteLines("result := make([]string, len(values))", - "for i, v := range values {"); + "for i, v := range values {"); writer.IncreaseIndent(); writer.WriteLine("result[i] = v.String()"); writer.CloseBlock(); writer.WriteLine("return result"); writer.CloseBlock(); } + + private void WriteMultiValueFunction(CodeEnum codeElement, LanguageWriter writer, Boolean isMultiValue) + { + var typeName = codeElement.Name.ToFirstCharacterUpperCase(); + writer.StartBlock($"func (i {typeName}) isMultiValue() bool {{"); + writer.WriteLine($"return {isMultiValue.ToString().ToLowerInvariant()}"); + writer.CloseBlock(); + } } diff --git a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs index 3863803799..140807e7e3 100644 --- a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs @@ -901,9 +901,9 @@ private string GetDeserializationMethodName(CodeTypeBase propType, CodeClass par return $"GetCollectionOfEnumValues({conventions.GetImportedStaticMethodName(propType, parentClass, "Parse")})"; else return $"GetCollectionOfObjectValues({GetTypeFactory(propType, parentClass, propertyTypeNameWithoutImportSymbol)})"; - if (currentType.TypeDefinition is CodeEnum currentEnum) + if (currentType.TypeDefinition is CodeEnum) { - return $"GetEnum{(currentEnum.Flags ? "Set" : string.Empty)}Value({conventions.GetImportedStaticMethodName(propType, parentClass, "Parse")})"; + return $"GetEnumValue({conventions.GetImportedStaticMethodName(propType, parentClass, "Parse")})"; } } return propertyTypeNameWithoutImportSymbol switch diff --git a/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs index 3a30772b85..f614d15349 100644 --- a/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs @@ -817,15 +817,28 @@ public async Task ReplacesRequestBuilderPropertiesByMethods() Assert.Single(model.Methods.Where(x => x.IsOfKind(CodeMethodKind.RequestBuilderBackwardCompatibility))); } [Fact] - public async Task AddsErrorImportForEnums() + public async Task AddsErrorImportForEnumsForMultiValueEnum() { var testEnum = root.AddEnum(new CodeEnum { Name = "TestEnum", - + Flags = true + }).First(); + await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Go }, root); + Assert.Single(testEnum.Usings.Where(static x => "errors".Equals(x.Name, StringComparison.Ordinal))); + Assert.Single(testEnum.Usings.Where(static x => "strings".Equals(x.Name, StringComparison.Ordinal))); + } + [Fact] + public async Task AddsErrorImportForEnumsForSingleValueEnum() + { + var testEnum = root.AddEnum(new CodeEnum + { + Name = "TestEnum", + Flags = false }).First(); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Go }, root); - Assert.Single(testEnum.Usings.Where(x => x.Name == "errors")); + Assert.Single(testEnum.Usings.Where(static x => "errors".Equals(x.Name, StringComparison.Ordinal))); + Assert.Empty(testEnum.Usings.Where(static x => "strings".Equals(x.Name, StringComparison.Ordinal))); } [Fact] public async Task CorrectsCoreType() diff --git a/tests/Kiota.Builder.Tests/Writers/Go/CodeEnumWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Go/CodeEnumWriterTests.cs index bd1b44348a..e470f19ef2 100644 --- a/tests/Kiota.Builder.Tests/Writers/Go/CodeEnumWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Go/CodeEnumWriterTests.cs @@ -34,7 +34,7 @@ public void Dispose() GC.SuppressFinalize(this); } [Fact] - public void WritesEnum() + public void WritesSingleValueEnum() { const string optionName = "option1"; currentEnum.AddOption(new CodeEnumOption { Name = optionName }); @@ -45,7 +45,8 @@ public void WritesEnum() Assert.Contains($"{EnumName.ToFirstCharacterUpperCase()} = iota", result); Assert.Contains("func (i", result); Assert.Contains("String() string {", result); - Assert.Contains("return []string{", result); + Assert.Contains("return []string{\"option1\"}[i]", result); + Assert.Contains("result = OPTION1_SOMEENUM", result); Assert.Contains("[i]", result); Assert.Contains("func Parse", result); Assert.Contains("(v string) (any, error)", result); @@ -56,6 +57,46 @@ public void WritesEnum() Assert.Contains("return 0, errors.New(\"Unknown ", result); AssertExtensions.CurlyBracesAreClosed(result); Assert.Contains(optionName.ToUpperInvariant(), result); + Assert.Contains("func (i SomeEnum) isMultiValue() bool {", result); + Assert.Contains("return false", result); + } + [Fact] + public void WritesMultiValueEnum() + { + var root = CodeNamespace.InitRootNamespace(); + var myEnum = root.AddEnum(new CodeEnum + { + Name = "MultiValueEnum", + Flags = true + }).First(); + const string optionName = "option1"; + myEnum.AddOption(new CodeEnumOption { Name = optionName }); + writer.Write(myEnum); + + var result = tw.ToString(); + Assert.Contains($"type MultiValueEnum int", result); + Assert.Contains("const (", result); + Assert.Contains("OPTION1_MULTIVALUEENUM MultiValueEnum = iota", result); + Assert.Contains("func (i", result); + Assert.Contains("String() string {", result); + Assert.Contains("for p := MultiValueEnum(1); p <= OPTION1_MULTIVALUEENUM; p <<= 1 {", result); + Assert.Contains("if i&p == p {", result); + Assert.Contains("values = append(values, []string{\"option1\"}[p])", result); + Assert.Contains("for _, str := range values {", result); + Assert.Contains("strings.Join(values", result); + Assert.Contains("result |= OPTION1_MULTIVALUEENUM", result); + Assert.Contains("[i]", result); + Assert.Contains("func Parse", result); + Assert.Contains("(v string) (any, error)", result); + Assert.Contains("switch str", result); + Assert.Contains("default", result); + Assert.Contains("result :=", result); + Assert.Contains("return &result, nil", result); + Assert.Contains("return 0, errors.New(\"Unknown ", result); + Assert.Contains("func (i MultiValueEnum) isMultiValue() bool {", result); + Assert.Contains("return true", result); + AssertExtensions.CurlyBracesAreClosed(result); + Assert.Contains(optionName.ToUpperInvariant(), result); } [Fact] public void DoesntWriteAnythingOnNoOption()