From ad5cf7d4ec66fd2833d80b0d2af31cf46ea67c53 Mon Sep 17 00:00:00 2001 From: calebkiage Date: Mon, 18 Oct 2021 19:50:56 +0300 Subject: [PATCH 01/69] Add shell writer based on C# writer --- src/Kiota.Builder/CodeDOM/CodeClass.cs | 4 + src/Kiota.Builder/GenerationLanguage.cs | 3 +- .../Refiners/ILanguageRefiner.cs | 5 +- src/Kiota.Builder/Refiners/ShellRefiner.cs | 185 ++++++++++++++++++ .../Writers/CSharp/CodeMethodWriter.cs | 6 +- src/Kiota.Builder/Writers/LanguageWriter.cs | 5 +- .../Shell/ShellCodeClassDeclarationWriter.cs | 42 ++++ .../Writers/Shell/ShellCodeMethodWriter.cs | 40 ++++ .../Writers/Shell/ShellWriter.cs | 24 +++ 9 files changed, 308 insertions(+), 6 deletions(-) create mode 100644 src/Kiota.Builder/Refiners/ShellRefiner.cs create mode 100644 src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs create mode 100644 src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs create mode 100644 src/Kiota.Builder/Writers/Shell/ShellWriter.cs diff --git a/src/Kiota.Builder/CodeDOM/CodeClass.cs b/src/Kiota.Builder/CodeDOM/CodeClass.cs index c040e9c799..f5ce83ad3d 100644 --- a/src/Kiota.Builder/CodeDOM/CodeClass.cs +++ b/src/Kiota.Builder/CodeDOM/CodeClass.cs @@ -113,6 +113,10 @@ public void AddImplements(params CodeType[] types) { implements.AddRange(types); } public IEnumerable Implements => implements; + public Boolean IsStatic + { + get; set; + } } public class End : BlockEnd diff --git a/src/Kiota.Builder/GenerationLanguage.cs b/src/Kiota.Builder/GenerationLanguage.cs index 22ddcb5397..021f50d2e5 100644 --- a/src/Kiota.Builder/GenerationLanguage.cs +++ b/src/Kiota.Builder/GenerationLanguage.cs @@ -1,4 +1,4 @@ -namespace Kiota.Builder { +namespace Kiota.Builder { public enum GenerationLanguage { CSharp, Java, @@ -7,5 +7,6 @@ public enum GenerationLanguage { Python, Go, Ruby, + Shell } } diff --git a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs index a4fd01d99f..9257aff0c6 100644 --- a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs @@ -1,4 +1,4 @@ -namespace Kiota.Builder.Refiners +namespace Kiota.Builder.Refiners { public interface ILanguageRefiner { @@ -21,6 +21,9 @@ public static void Refine(GenerationConfiguration config, CodeNamespace generate case GenerationLanguage.Go: new GoRefiner(config).Refine(generatedCode); break; + case GenerationLanguage.Shell: + new ShellRefiner(config).Refine(generatedCode); + break; default: break; //Do nothing } diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs new file mode 100644 index 0000000000..12c005ffbc --- /dev/null +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.Refiners +{ + public class ShellRefiner : CommonLanguageRefiner, ILanguageRefiner + { + public ShellRefiner(GenerationConfiguration configuration) : base(configuration) { } + public override void Refine(CodeNamespace generatedCode) + { + // Remove PathSegment field + // Convert Properties to AddCommand + + + AddDefaultImports(generatedCode); + MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); + ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore); + AddPropertiesAndMethodTypesImports(generatedCode, false, false, false); + RemoveModelClasses(generatedCode); + RemoveEnums(generatedCode); + RemoveConstructors(generatedCode); + TurnRequestBuildersIntoCommandBuilders(generatedCode); + AddAsyncSuffix(generatedCode); + AddInnerClasses(generatedCode, false); + CapitalizeNamespacesFirstLetters(generatedCode); + ReplaceBinaryByNativeType(generatedCode, "Stream", "System.IO"); + MakeEnumPropertiesNullable(generatedCode); + ReplaceReservedNames(generatedCode, new CSharpReservedNamesProvider(), x => $"@{x.ToFirstCharacterUpperCase()}"); + DisambiguatePropertiesWithClassNames(generatedCode); + AddConstructorsForDefaultValues(generatedCode, false); + AddSerializationModulesImport(generatedCode); + } + private static void DisambiguatePropertiesWithClassNames(CodeElement currentElement) + { + if (currentElement is CodeClass currentClass) + { + var sameNameProperty = currentClass + .GetChildElements(true) + .OfType() + .FirstOrDefault(x => x.Name.Equals(currentClass.Name, StringComparison.OrdinalIgnoreCase)); + if (sameNameProperty != null) + { + currentClass.RemoveChildElement(sameNameProperty); + sameNameProperty.SerializationName ??= sameNameProperty.Name; + sameNameProperty.Name = $"{sameNameProperty.Name}_prop"; + currentClass.AddProperty(sameNameProperty); + } + } + CrawlTree(currentElement, DisambiguatePropertiesWithClassNames); + } + private static void MakeEnumPropertiesNullable(CodeElement currentElement) + { + if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model)) + currentClass.GetChildElements(true) + .OfType() + .Where(x => x.Type is CodeType propType && propType.TypeDefinition is CodeEnum) + .ToList() + .ForEach(x => x.Type.IsNullable = true); + CrawlTree(currentElement, MakeEnumPropertiesNullable); + } + private static void RemoveModelClasses(CodeElement currentElement) + { + if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model)) + { + var codeNamespace = currentClass.Parent as CodeNamespace; + codeNamespace.RemoveChildElement(currentClass); + } + CrawlTree(currentElement, RemoveModelClasses); + } + + private static void RemoveEnums(CodeElement currentElement) + { + if (currentElement is CodeEnum currentEnum) + { + var codeNamespace = currentElement.Parent as CodeNamespace; + codeNamespace.RemoveChildElement(currentEnum); + } + CrawlTree(currentElement, RemoveEnums); + } + + private static void RemoveConstructors(CodeElement currentElement) + { + if (currentElement is CodeMethod currentMethod && currentMethod.IsOfKind(CodeMethodKind.Constructor)) + { + var codeClass = currentElement.Parent as CodeClass; + codeClass.RemoveChildElement(currentMethod); + } + CrawlTree(currentElement, RemoveConstructors); + } + + private static void TurnRequestBuildersIntoCommandBuilders(CodeElement currentElement) + { + if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder)) + { + + (currentClass.StartBlock as CodeClass.Declaration).IsStatic = true; + + // Replace Nav Properties with BuildXXXCommand methods + var navProperties = currentClass.GetChildElements().Where(e => e is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestBuilder)).Cast(); + foreach (var navProp in navProperties) + { + var method = CreateBuildCommandMethod(navProp, currentClass); + currentClass.AddMethod(method); + currentClass.RemoveChildElement(navProp); + } + // Change signtature of RequestExecutors + var requestMethods = currentClass.GetChildElements().Where(e => e is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor)).Cast(); + foreach (var requestMethod in requestMethods) + { + requestMethod.IsAsync = false; + requestMethod.IsStatic = true; + requestMethod.Name = $"Build{requestMethod.Name}Command"; + requestMethod.ReturnType = CreateCommandType(requestMethod); + } + + var buildMethod = new CodeMethod + { + Name = "Build", + IsStatic = true, + IsAsync = false + }; + buildMethod.AddParameter(new CodeParameter { Name = "httpCore", Type = new CodeType { Name = "IHttpCore", IsExternal = true } }); + // Add calls to BuildMethods here.. + buildMethod.ReturnType = new CodeType + { + Name = "Command", + IsExternal = true + }; + currentClass.AddMethod(buildMethod); + + } + CrawlTree(currentElement, TurnRequestBuildersIntoCommandBuilders); + } + + private static CodeType CreateCommandType(CodeElement parent) + { + return new CodeType + { + Name = "Command", + IsExternal = true + }; + } + + private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, CodeClass parent) + { + var codeMethod = new CodeMethod(); + codeMethod.IsAsync = false; + codeMethod.IsStatic = true; + codeMethod.Name = $"Build{navProperty.Name}Command"; + codeMethod.MethodKind = CodeMethodKind.RequestBuilderBackwardCompatibility; + codeMethod.ReturnType = CreateCommandType(codeMethod); + return codeMethod; + } + + private static readonly string[] defaultNamespacesForClasses = new string[] { "System", "System.Collections.Generic", "System.Linq" }; + private static readonly string[] defaultNamespacesForRequestBuilders = new string[] { "System.Threading.Tasks", "System.IO", "Microsoft.Kiota.Abstractions", "Microsoft.Kiota.Abstractions.Serialization", "System.CommandLine", "System.CommandLine.Invocation" }; + + private static void AddDefaultImports(CodeElement current) + { + if (current is CodeClass currentClass) + { + currentClass.AddUsing(defaultNamespacesForClasses.Select(x => new CodeUsing { Name = x }).ToArray()); + if (currentClass.IsOfKind(CodeClassKind.RequestBuilder)) + currentClass.AddUsing(defaultNamespacesForRequestBuilders.Select(x => new CodeUsing { Name = x }).ToArray()); + } + CrawlTree(current, AddDefaultImports); + } + private static void CapitalizeNamespacesFirstLetters(CodeElement current) + { + if (current is CodeNamespace currentNamespace) + currentNamespace.Name = currentNamespace.Name?.Split('.')?.Select(x => x.ToFirstCharacterUpperCase())?.Aggregate((x, y) => $"{x}.{y}"); + CrawlTree(current, CapitalizeNamespacesFirstLetters); + } + private static void AddAsyncSuffix(CodeElement currentElement) + { + if (currentElement is CodeMethod currentMethod && currentMethod.IsAsync) + currentMethod.Name += "Async"; + CrawlTree(currentElement, AddAsyncSuffix); + } + } +} diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index 150985eaaa..d2caaf7a2e 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Kiota.Builder.Extensions; @@ -146,7 +146,7 @@ _ when conventions.IsPrimitiveType(propertyType) => $"Get{propertyType.TrimEnd(C _ => $"GetObjectValue<{propertyType.ToFirstCharacterUpperCase()}>", }; } - private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) { + protected virtual void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); @@ -209,7 +209,7 @@ private void WriteSerializerBody(bool shouldHide, CodeMethod method, CodeClass p if(additionalDataProperty != null) writer.WriteLine($"writer.WriteAdditionalData({additionalDataProperty.Name});"); } - private string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) { + protected string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) { if(isVoid) return "SendNoContentAsync"; else if(isStream || conventions.IsPrimitiveType(returnType)) if(isCollection) diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index 9bcc519303..2874596e84 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -7,6 +7,7 @@ using Kiota.Builder.Writers.Go; using Kiota.Builder.Writers.Java; using Kiota.Builder.Writers.Ruby; +using Kiota.Builder.Writers.Shell; using Kiota.Builder.Writers.TypeScript; namespace Kiota.Builder.Writers @@ -105,7 +106,8 @@ public void Write(T code) where T : CodeElement throw new InvalidOperationException($"Dispatcher missing for type {code.GetType()}"); } protected void AddCodeElementWriter(ICodeElementWriter writer) where T: CodeElement { - Writers.Add(typeof(T), writer); + if (!Writers.TryAdd(typeof(T), writer)) + Writers[typeof(T)] = writer; } private readonly Dictionary Writers = new(); // we have to type as object because dotnet doesn't have type capture i.e eq for `? extends CodeElement` public static LanguageWriter GetLanguageWriter(GenerationLanguage language, string outputPath, string clientNamespaceName) { @@ -116,6 +118,7 @@ public static LanguageWriter GetLanguageWriter(GenerationLanguage language, stri GenerationLanguage.TypeScript => new TypeScriptWriter(outputPath, clientNamespaceName), GenerationLanguage.Ruby => new RubyWriter(outputPath, clientNamespaceName), GenerationLanguage.Go => new GoWriter(outputPath, clientNamespaceName), + GenerationLanguage.Shell => new ShellWriter(outputPath, clientNamespaceName), _ => throw new InvalidEnumArgumentException($"{language} language currently not supported."), }; } diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs new file mode 100644 index 0000000000..f449f3bc48 --- /dev/null +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Kiota.Builder.Extensions; +using Kiota.Builder.Writers.CSharp; + +namespace Kiota.Builder.Writers.Shell +{ + class ShellCodeClassDeclarationWriter : CodeClassDeclarationWriter + { + public ShellCodeClassDeclarationWriter(CSharpConventionService conventionService) : base(conventionService) + { + } + + public override void WriteCodeElement(CodeClass.Declaration codeElement, LanguageWriter writer) + { + codeElement.Usings + .Where(x => (x.Declaration?.IsExternal ?? true) || !x.Declaration.Name.Equals(codeElement.Name, StringComparison.OrdinalIgnoreCase)) // needed for circular requests patterns like message folder + .Select(x => x.Declaration?.IsExternal ?? false ? + $"using {x.Declaration.Name.NormalizeNameSpaceName(".")};" : + $"using {x.Name.NormalizeNameSpaceName(".")};") + .Distinct() + .OrderBy(x => x) + .ToList() + .ForEach(x => writer.WriteLine(x)); + if (codeElement?.Parent?.Parent is CodeNamespace) + { + writer.WriteLine($"namespace {codeElement.Parent.Parent.Name} {{"); + writer.IncreaseIndent(); + } + var derivedTypes = new List { codeElement.Inherits?.Name } + .Union(codeElement.Implements.Select(x => x.Name)) + .Where(x => x != null); + var derivation = derivedTypes.Any() ? ": " + derivedTypes.Select(x => x.ToFirstCharacterUpperCase()).Aggregate((x, y) => $"{x}, {y}") + " " : string.Empty; + if (codeElement.Parent is CodeClass parentClass) + conventions.WriteShortDescription(parentClass.Description, writer); + var staticModifier = codeElement.IsStatic ? "static " : string.Empty; + writer.WriteLine($"public {staticModifier}class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); + writer.IncreaseIndent(); + } + } +} diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs new file mode 100644 index 0000000000..4259551cb6 --- /dev/null +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kiota.Builder.Writers.CSharp; + +namespace Kiota.Builder.Writers.Shell +{ + class ShellCodeMethodWriter : CodeMethodWriter + { + public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(conventionService) + { + } + + protected override void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) + { + if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); + + var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); + var generatorMethodName = (codeElement.Parent as CodeClass) + .Methods + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) + ?.Name; + var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } + .Select(x => x?.Name).Where(x => x != null).Aggregate((x, y) => $"{x}, {y}"); + writer.WriteLine($"var command = new Command(\"\") {{"); + writer.IncreaseIndent(); + writer.WriteLine($"Handler = CommandHandler.Create<>(async () => {{"); + writer.IncreaseIndent(); + writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});"); + writer.WriteLine($"{(isVoid ? string.Empty : "return ")}await HttpCore.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo, responseHandler);"); + writer.DecreaseIndent(); + writer.WriteLine("})"); + writer.DecreaseIndent(); + writer.WriteLine("};"); + writer.WriteLine("// Create options for all the parameters"); + } + } +} diff --git a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs new file mode 100644 index 0000000000..460ab11663 --- /dev/null +++ b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kiota.Builder.Writers.CSharp; + +namespace Kiota.Builder.Writers.Shell +{ + class ShellWriter : CSharpWriter + { + public ShellWriter(string rootPath, string clientNamespaceName) : base(rootPath, clientNamespaceName) + { + var conventionService = new CSharpConventionService(); + AddCodeElementWriter(new ShellCodeClassDeclarationWriter(conventionService)); + AddCodeElementWriter(new CodeClassEndWriter(conventionService)); + AddCodeElementWriter(new CodeEnumWriter(conventionService)); + AddCodeElementWriter(new CodeIndexerWriter(conventionService)); + AddCodeElementWriter(new CodeMethodWriter(conventionService)); + AddCodeElementWriter(new CodePropertyWriter(conventionService)); + AddCodeElementWriter(new CodeTypeWriter(conventionService)); + } + } +} From 7ec5869e22cc7fda119cbd3ae8713c52981859b3 Mon Sep 17 00:00:00 2001 From: calebkiage Date: Mon, 18 Oct 2021 19:55:24 +0300 Subject: [PATCH 02/69] Fix method writer reference --- src/Kiota.Builder/Writers/Shell/ShellWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs index 460ab11663..c7482b5194 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs @@ -16,7 +16,7 @@ public ShellWriter(string rootPath, string clientNamespaceName) : base(rootPath, AddCodeElementWriter(new CodeClassEndWriter(conventionService)); AddCodeElementWriter(new CodeEnumWriter(conventionService)); AddCodeElementWriter(new CodeIndexerWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService)); + AddCodeElementWriter(new ShellCodeMethodWriter(conventionService)); AddCodeElementWriter(new CodePropertyWriter(conventionService)); AddCodeElementWriter(new CodeTypeWriter(conventionService)); } From 5ee05a0e969df73078f881f28b29a15270ebb068 Mon Sep 17 00:00:00 2001 From: calebkiage Date: Mon, 18 Oct 2021 19:50:56 +0300 Subject: [PATCH 03/69] Add shell writer based on C# writer --- src/Kiota.Builder/CodeDOM/CodeClass.cs | 4 + src/Kiota.Builder/GenerationLanguage.cs | 3 +- .../Refiners/ILanguageRefiner.cs | 5 +- src/Kiota.Builder/Refiners/ShellRefiner.cs | 185 ++++++++++++++++++ .../Writers/CSharp/CodeMethodWriter.cs | 4 +- src/Kiota.Builder/Writers/LanguageWriter.cs | 5 +- .../Shell/ShellCodeClassDeclarationWriter.cs | 42 ++++ .../Writers/Shell/ShellCodeMethodWriter.cs | 40 ++++ .../Writers/Shell/ShellWriter.cs | 24 +++ 9 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 src/Kiota.Builder/Refiners/ShellRefiner.cs create mode 100644 src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs create mode 100644 src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs create mode 100644 src/Kiota.Builder/Writers/Shell/ShellWriter.cs diff --git a/src/Kiota.Builder/CodeDOM/CodeClass.cs b/src/Kiota.Builder/CodeDOM/CodeClass.cs index 1c90b1fe0d..d7e3da2dfb 100644 --- a/src/Kiota.Builder/CodeDOM/CodeClass.cs +++ b/src/Kiota.Builder/CodeDOM/CodeClass.cs @@ -115,6 +115,10 @@ public void AddImplements(params CodeType[] types) { implements.AddRange(types); } public IEnumerable Implements => implements; + public Boolean IsStatic + { + get; set; + } } public class End : BlockEnd diff --git a/src/Kiota.Builder/GenerationLanguage.cs b/src/Kiota.Builder/GenerationLanguage.cs index 22ddcb5397..021f50d2e5 100644 --- a/src/Kiota.Builder/GenerationLanguage.cs +++ b/src/Kiota.Builder/GenerationLanguage.cs @@ -1,4 +1,4 @@ -namespace Kiota.Builder { +namespace Kiota.Builder { public enum GenerationLanguage { CSharp, Java, @@ -7,5 +7,6 @@ public enum GenerationLanguage { Python, Go, Ruby, + Shell } } diff --git a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs index a4fd01d99f..9257aff0c6 100644 --- a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs @@ -1,4 +1,4 @@ -namespace Kiota.Builder.Refiners +namespace Kiota.Builder.Refiners { public interface ILanguageRefiner { @@ -21,6 +21,9 @@ public static void Refine(GenerationConfiguration config, CodeNamespace generate case GenerationLanguage.Go: new GoRefiner(config).Refine(generatedCode); break; + case GenerationLanguage.Shell: + new ShellRefiner(config).Refine(generatedCode); + break; default: break; //Do nothing } diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs new file mode 100644 index 0000000000..12c005ffbc --- /dev/null +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.Refiners +{ + public class ShellRefiner : CommonLanguageRefiner, ILanguageRefiner + { + public ShellRefiner(GenerationConfiguration configuration) : base(configuration) { } + public override void Refine(CodeNamespace generatedCode) + { + // Remove PathSegment field + // Convert Properties to AddCommand + + + AddDefaultImports(generatedCode); + MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); + ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore); + AddPropertiesAndMethodTypesImports(generatedCode, false, false, false); + RemoveModelClasses(generatedCode); + RemoveEnums(generatedCode); + RemoveConstructors(generatedCode); + TurnRequestBuildersIntoCommandBuilders(generatedCode); + AddAsyncSuffix(generatedCode); + AddInnerClasses(generatedCode, false); + CapitalizeNamespacesFirstLetters(generatedCode); + ReplaceBinaryByNativeType(generatedCode, "Stream", "System.IO"); + MakeEnumPropertiesNullable(generatedCode); + ReplaceReservedNames(generatedCode, new CSharpReservedNamesProvider(), x => $"@{x.ToFirstCharacterUpperCase()}"); + DisambiguatePropertiesWithClassNames(generatedCode); + AddConstructorsForDefaultValues(generatedCode, false); + AddSerializationModulesImport(generatedCode); + } + private static void DisambiguatePropertiesWithClassNames(CodeElement currentElement) + { + if (currentElement is CodeClass currentClass) + { + var sameNameProperty = currentClass + .GetChildElements(true) + .OfType() + .FirstOrDefault(x => x.Name.Equals(currentClass.Name, StringComparison.OrdinalIgnoreCase)); + if (sameNameProperty != null) + { + currentClass.RemoveChildElement(sameNameProperty); + sameNameProperty.SerializationName ??= sameNameProperty.Name; + sameNameProperty.Name = $"{sameNameProperty.Name}_prop"; + currentClass.AddProperty(sameNameProperty); + } + } + CrawlTree(currentElement, DisambiguatePropertiesWithClassNames); + } + private static void MakeEnumPropertiesNullable(CodeElement currentElement) + { + if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model)) + currentClass.GetChildElements(true) + .OfType() + .Where(x => x.Type is CodeType propType && propType.TypeDefinition is CodeEnum) + .ToList() + .ForEach(x => x.Type.IsNullable = true); + CrawlTree(currentElement, MakeEnumPropertiesNullable); + } + private static void RemoveModelClasses(CodeElement currentElement) + { + if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model)) + { + var codeNamespace = currentClass.Parent as CodeNamespace; + codeNamespace.RemoveChildElement(currentClass); + } + CrawlTree(currentElement, RemoveModelClasses); + } + + private static void RemoveEnums(CodeElement currentElement) + { + if (currentElement is CodeEnum currentEnum) + { + var codeNamespace = currentElement.Parent as CodeNamespace; + codeNamespace.RemoveChildElement(currentEnum); + } + CrawlTree(currentElement, RemoveEnums); + } + + private static void RemoveConstructors(CodeElement currentElement) + { + if (currentElement is CodeMethod currentMethod && currentMethod.IsOfKind(CodeMethodKind.Constructor)) + { + var codeClass = currentElement.Parent as CodeClass; + codeClass.RemoveChildElement(currentMethod); + } + CrawlTree(currentElement, RemoveConstructors); + } + + private static void TurnRequestBuildersIntoCommandBuilders(CodeElement currentElement) + { + if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder)) + { + + (currentClass.StartBlock as CodeClass.Declaration).IsStatic = true; + + // Replace Nav Properties with BuildXXXCommand methods + var navProperties = currentClass.GetChildElements().Where(e => e is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestBuilder)).Cast(); + foreach (var navProp in navProperties) + { + var method = CreateBuildCommandMethod(navProp, currentClass); + currentClass.AddMethod(method); + currentClass.RemoveChildElement(navProp); + } + // Change signtature of RequestExecutors + var requestMethods = currentClass.GetChildElements().Where(e => e is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor)).Cast(); + foreach (var requestMethod in requestMethods) + { + requestMethod.IsAsync = false; + requestMethod.IsStatic = true; + requestMethod.Name = $"Build{requestMethod.Name}Command"; + requestMethod.ReturnType = CreateCommandType(requestMethod); + } + + var buildMethod = new CodeMethod + { + Name = "Build", + IsStatic = true, + IsAsync = false + }; + buildMethod.AddParameter(new CodeParameter { Name = "httpCore", Type = new CodeType { Name = "IHttpCore", IsExternal = true } }); + // Add calls to BuildMethods here.. + buildMethod.ReturnType = new CodeType + { + Name = "Command", + IsExternal = true + }; + currentClass.AddMethod(buildMethod); + + } + CrawlTree(currentElement, TurnRequestBuildersIntoCommandBuilders); + } + + private static CodeType CreateCommandType(CodeElement parent) + { + return new CodeType + { + Name = "Command", + IsExternal = true + }; + } + + private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, CodeClass parent) + { + var codeMethod = new CodeMethod(); + codeMethod.IsAsync = false; + codeMethod.IsStatic = true; + codeMethod.Name = $"Build{navProperty.Name}Command"; + codeMethod.MethodKind = CodeMethodKind.RequestBuilderBackwardCompatibility; + codeMethod.ReturnType = CreateCommandType(codeMethod); + return codeMethod; + } + + private static readonly string[] defaultNamespacesForClasses = new string[] { "System", "System.Collections.Generic", "System.Linq" }; + private static readonly string[] defaultNamespacesForRequestBuilders = new string[] { "System.Threading.Tasks", "System.IO", "Microsoft.Kiota.Abstractions", "Microsoft.Kiota.Abstractions.Serialization", "System.CommandLine", "System.CommandLine.Invocation" }; + + private static void AddDefaultImports(CodeElement current) + { + if (current is CodeClass currentClass) + { + currentClass.AddUsing(defaultNamespacesForClasses.Select(x => new CodeUsing { Name = x }).ToArray()); + if (currentClass.IsOfKind(CodeClassKind.RequestBuilder)) + currentClass.AddUsing(defaultNamespacesForRequestBuilders.Select(x => new CodeUsing { Name = x }).ToArray()); + } + CrawlTree(current, AddDefaultImports); + } + private static void CapitalizeNamespacesFirstLetters(CodeElement current) + { + if (current is CodeNamespace currentNamespace) + currentNamespace.Name = currentNamespace.Name?.Split('.')?.Select(x => x.ToFirstCharacterUpperCase())?.Aggregate((x, y) => $"{x}.{y}"); + CrawlTree(current, CapitalizeNamespacesFirstLetters); + } + private static void AddAsyncSuffix(CodeElement currentElement) + { + if (currentElement is CodeMethod currentMethod && currentMethod.IsAsync) + currentMethod.Name += "Async"; + CrawlTree(currentElement, AddAsyncSuffix); + } + } +} diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index cb71130181..6f7ac996e1 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -166,7 +166,7 @@ _ when conventions.IsPrimitiveType(propertyType) => $"Get{propertyType.TrimEnd(C _ => $"GetObjectValue<{propertyType.ToFirstCharacterUpperCase()}>", }; } - private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) { + protected virtual void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); @@ -229,7 +229,7 @@ private void WriteSerializerBody(bool shouldHide, CodeMethod method, CodeClass p if(additionalDataProperty != null) writer.WriteLine($"writer.WriteAdditionalData({additionalDataProperty.Name});"); } - private string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) { + protected string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) { if(isVoid) return "SendNoContentAsync"; else if(isStream || conventions.IsPrimitiveType(returnType)) if(isCollection) diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index 60e71a8c04..acb9a4bc63 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -7,6 +7,7 @@ using Kiota.Builder.Writers.Go; using Kiota.Builder.Writers.Java; using Kiota.Builder.Writers.Ruby; +using Kiota.Builder.Writers.Shell; using Kiota.Builder.Writers.TypeScript; namespace Kiota.Builder.Writers @@ -111,7 +112,8 @@ public void Write(T code) where T : CodeElement throw new InvalidOperationException($"Dispatcher missing for type {code.GetType()}"); } protected void AddCodeElementWriter(ICodeElementWriter writer) where T: CodeElement { - Writers.Add(typeof(T), writer); + if (!Writers.TryAdd(typeof(T), writer)) + Writers[typeof(T)] = writer; } private readonly Dictionary Writers = new(); // we have to type as object because dotnet doesn't have type capture i.e eq for `? extends CodeElement` public static LanguageWriter GetLanguageWriter(GenerationLanguage language, string outputPath, string clientNamespaceName) { @@ -122,6 +124,7 @@ public static LanguageWriter GetLanguageWriter(GenerationLanguage language, stri GenerationLanguage.TypeScript => new TypeScriptWriter(outputPath, clientNamespaceName), GenerationLanguage.Ruby => new RubyWriter(outputPath, clientNamespaceName), GenerationLanguage.Go => new GoWriter(outputPath, clientNamespaceName), + GenerationLanguage.Shell => new ShellWriter(outputPath, clientNamespaceName), _ => throw new InvalidEnumArgumentException($"{language} language currently not supported."), }; } diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs new file mode 100644 index 0000000000..f449f3bc48 --- /dev/null +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Kiota.Builder.Extensions; +using Kiota.Builder.Writers.CSharp; + +namespace Kiota.Builder.Writers.Shell +{ + class ShellCodeClassDeclarationWriter : CodeClassDeclarationWriter + { + public ShellCodeClassDeclarationWriter(CSharpConventionService conventionService) : base(conventionService) + { + } + + public override void WriteCodeElement(CodeClass.Declaration codeElement, LanguageWriter writer) + { + codeElement.Usings + .Where(x => (x.Declaration?.IsExternal ?? true) || !x.Declaration.Name.Equals(codeElement.Name, StringComparison.OrdinalIgnoreCase)) // needed for circular requests patterns like message folder + .Select(x => x.Declaration?.IsExternal ?? false ? + $"using {x.Declaration.Name.NormalizeNameSpaceName(".")};" : + $"using {x.Name.NormalizeNameSpaceName(".")};") + .Distinct() + .OrderBy(x => x) + .ToList() + .ForEach(x => writer.WriteLine(x)); + if (codeElement?.Parent?.Parent is CodeNamespace) + { + writer.WriteLine($"namespace {codeElement.Parent.Parent.Name} {{"); + writer.IncreaseIndent(); + } + var derivedTypes = new List { codeElement.Inherits?.Name } + .Union(codeElement.Implements.Select(x => x.Name)) + .Where(x => x != null); + var derivation = derivedTypes.Any() ? ": " + derivedTypes.Select(x => x.ToFirstCharacterUpperCase()).Aggregate((x, y) => $"{x}, {y}") + " " : string.Empty; + if (codeElement.Parent is CodeClass parentClass) + conventions.WriteShortDescription(parentClass.Description, writer); + var staticModifier = codeElement.IsStatic ? "static " : string.Empty; + writer.WriteLine($"public {staticModifier}class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); + writer.IncreaseIndent(); + } + } +} diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs new file mode 100644 index 0000000000..4259551cb6 --- /dev/null +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kiota.Builder.Writers.CSharp; + +namespace Kiota.Builder.Writers.Shell +{ + class ShellCodeMethodWriter : CodeMethodWriter + { + public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(conventionService) + { + } + + protected override void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) + { + if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); + + var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); + var generatorMethodName = (codeElement.Parent as CodeClass) + .Methods + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) + ?.Name; + var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } + .Select(x => x?.Name).Where(x => x != null).Aggregate((x, y) => $"{x}, {y}"); + writer.WriteLine($"var command = new Command(\"\") {{"); + writer.IncreaseIndent(); + writer.WriteLine($"Handler = CommandHandler.Create<>(async () => {{"); + writer.IncreaseIndent(); + writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});"); + writer.WriteLine($"{(isVoid ? string.Empty : "return ")}await HttpCore.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo, responseHandler);"); + writer.DecreaseIndent(); + writer.WriteLine("})"); + writer.DecreaseIndent(); + writer.WriteLine("};"); + writer.WriteLine("// Create options for all the parameters"); + } + } +} diff --git a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs new file mode 100644 index 0000000000..460ab11663 --- /dev/null +++ b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kiota.Builder.Writers.CSharp; + +namespace Kiota.Builder.Writers.Shell +{ + class ShellWriter : CSharpWriter + { + public ShellWriter(string rootPath, string clientNamespaceName) : base(rootPath, clientNamespaceName) + { + var conventionService = new CSharpConventionService(); + AddCodeElementWriter(new ShellCodeClassDeclarationWriter(conventionService)); + AddCodeElementWriter(new CodeClassEndWriter(conventionService)); + AddCodeElementWriter(new CodeEnumWriter(conventionService)); + AddCodeElementWriter(new CodeIndexerWriter(conventionService)); + AddCodeElementWriter(new CodeMethodWriter(conventionService)); + AddCodeElementWriter(new CodePropertyWriter(conventionService)); + AddCodeElementWriter(new CodeTypeWriter(conventionService)); + } + } +} From e0061ea1b0cedf573ad450c0aeaec0787606774a Mon Sep 17 00:00:00 2001 From: calebkiage Date: Mon, 18 Oct 2021 19:55:24 +0300 Subject: [PATCH 04/69] Fix method writer reference --- src/Kiota.Builder/Writers/Shell/ShellWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs index 460ab11663..c7482b5194 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs @@ -16,7 +16,7 @@ public ShellWriter(string rootPath, string clientNamespaceName) : base(rootPath, AddCodeElementWriter(new CodeClassEndWriter(conventionService)); AddCodeElementWriter(new CodeEnumWriter(conventionService)); AddCodeElementWriter(new CodeIndexerWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService)); + AddCodeElementWriter(new ShellCodeMethodWriter(conventionService)); AddCodeElementWriter(new CodePropertyWriter(conventionService)); AddCodeElementWriter(new CodeTypeWriter(conventionService)); } From f3bf3f6cd95bacbea1bbfd0594c498fd8cbd19be Mon Sep 17 00:00:00 2001 From: calebkiage Date: Tue, 19 Oct 2021 16:15:45 +0300 Subject: [PATCH 05/69] Fix generation error --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 12c005ffbc..ca5639ef05 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -150,8 +150,8 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod var codeMethod = new CodeMethod(); codeMethod.IsAsync = false; codeMethod.IsStatic = true; - codeMethod.Name = $"Build{navProperty.Name}Command"; - codeMethod.MethodKind = CodeMethodKind.RequestBuilderBackwardCompatibility; + codeMethod.Name = $"Build{navProperty.Name.ToFirstCharacterUpperCase()}Command"; + codeMethod.MethodKind = CodeMethodKind.RequestBuilderWithParameters; codeMethod.ReturnType = CreateCommandType(codeMethod); return codeMethod; } From b36cf9202ed457a1b4ff1b831fcce194347945aa Mon Sep 17 00:00:00 2001 From: calebkiage Date: Thu, 21 Oct 2021 20:40:15 +0300 Subject: [PATCH 06/69] Add code class kind for shell language --- src/Kiota.Builder/CodeDOM/CodeClass.cs | 1 + .../Writers/Shell/ShellCodeMethodWriter.cs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeClass.cs b/src/Kiota.Builder/CodeDOM/CodeClass.cs index d7e3da2dfb..dbc6596f31 100644 --- a/src/Kiota.Builder/CodeDOM/CodeClass.cs +++ b/src/Kiota.Builder/CodeDOM/CodeClass.cs @@ -6,6 +6,7 @@ namespace Kiota.Builder { public enum CodeClassKind { Custom, + CommandBuilder, RequestBuilder, Model, QueryParameters, diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 4259551cb6..69bf0a164f 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -24,7 +24,7 @@ protected override void WriteRequestExecutorBody(CodeMethod codeElement, Request ?.Name; var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } .Select(x => x?.Name).Where(x => x != null).Aggregate((x, y) => $"{x}, {y}"); - writer.WriteLine($"var command = new Command(\"\") {{"); + writer.WriteLine($"var command = new Command(\"{codeElement.HttpMethod.ToString().ToLower()}\") {{"); writer.IncreaseIndent(); writer.WriteLine($"Handler = CommandHandler.Create<>(async () => {{"); writer.IncreaseIndent(); @@ -34,6 +34,18 @@ protected override void WriteRequestExecutorBody(CodeMethod codeElement, Request writer.WriteLine("})"); writer.DecreaseIndent(); writer.WriteLine("};"); + + CodeElement element = codeElement; + + //while (element != null) + //{ + // foreach (CodeParameter p in element.) + // { + + // } + + // element = element.Parent; + //} writer.WriteLine("// Create options for all the parameters"); } } From e7ce85f88bd47aa9f92198e873e55208e7458b10 Mon Sep 17 00:00:00 2001 From: calebkiage Date: Thu, 21 Oct 2021 20:59:08 +0300 Subject: [PATCH 07/69] Rename AddCodeElementWriter function to reflect updated behavior which includes replacement --- src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs | 14 +++++++------- src/Kiota.Builder/Writers/Go/GoWriter.cs | 10 +++++----- src/Kiota.Builder/Writers/Java/JavaWriter.cs | 12 ++++++------ src/Kiota.Builder/Writers/LanguageWriter.cs | 2 +- src/Kiota.Builder/Writers/Ruby/RubyWriter.cs | 12 ++++++------ src/Kiota.Builder/Writers/Shell/ShellWriter.cs | 14 +++++++------- .../Writers/TypeScript/TypeScriptWriter.cs | 12 ++++++------ 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs b/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs index d37bc33762..ea5e72aebc 100644 --- a/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs @@ -6,13 +6,13 @@ public CSharpWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new CSharpPathSegmenter(rootPath, clientNamespaceName); var conventionService = new CSharpConventionService(); - AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); - AddCodeElementWriter(new CodeClassEndWriter(conventionService)); - AddCodeElementWriter(new CodeEnumWriter(conventionService)); - AddCodeElementWriter(new CodeIndexerWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService)); - AddCodeElementWriter(new CodePropertyWriter(conventionService)); - AddCodeElementWriter(new CodeTypeWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassEndWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeIndexerWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); } } diff --git a/src/Kiota.Builder/Writers/Go/GoWriter.cs b/src/Kiota.Builder/Writers/Go/GoWriter.cs index dd90d6b4c8..185efd13cd 100644 --- a/src/Kiota.Builder/Writers/Go/GoWriter.cs +++ b/src/Kiota.Builder/Writers/Go/GoWriter.cs @@ -4,11 +4,11 @@ public GoWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new GoPathSegmenter(rootPath, clientNamespaceName); var conventionService = new GoConventionService(); - AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); - AddCodeElementWriter(new CodeClassEndWriter()); - AddCodeElementWriter(new CodePropertyWriter(conventionService)); - AddCodeElementWriter(new CodeEnumWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassEndWriter()); + AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); } } } diff --git a/src/Kiota.Builder/Writers/Java/JavaWriter.cs b/src/Kiota.Builder/Writers/Java/JavaWriter.cs index b34b5d0441..537a2af81c 100644 --- a/src/Kiota.Builder/Writers/Java/JavaWriter.cs +++ b/src/Kiota.Builder/Writers/Java/JavaWriter.cs @@ -6,12 +6,12 @@ public JavaWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new JavaPathSegmenter(rootPath, clientNamespaceName); var conventionService = new JavaConventionService(); - AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); - AddCodeElementWriter(new CodeClassEndWriter()); - AddCodeElementWriter(new CodeEnumWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService)); - AddCodeElementWriter(new CodePropertyWriter(conventionService)); - AddCodeElementWriter(new CodeTypeWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassEndWriter()); + AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); } } } diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index acb9a4bc63..99be286bbd 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -111,7 +111,7 @@ public void Write(T code) where T : CodeElement else if(!(code is CodeClass) && !(code is CodeNamespace.BlockDeclaration) && !(code is CodeNamespace.BlockEnd)) throw new InvalidOperationException($"Dispatcher missing for type {code.GetType()}"); } - protected void AddCodeElementWriter(ICodeElementWriter writer) where T: CodeElement { + protected void AddOrReplaceCodeElementWriter(ICodeElementWriter writer) where T: CodeElement { if (!Writers.TryAdd(typeof(T), writer)) Writers[typeof(T)] = writer; } diff --git a/src/Kiota.Builder/Writers/Ruby/RubyWriter.cs b/src/Kiota.Builder/Writers/Ruby/RubyWriter.cs index 32a7ee8acc..16ebc32a32 100644 --- a/src/Kiota.Builder/Writers/Ruby/RubyWriter.cs +++ b/src/Kiota.Builder/Writers/Ruby/RubyWriter.cs @@ -6,12 +6,12 @@ public RubyWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new RubyPathSegmenter(rootPath, clientNamespaceName); var conventionService = new RubyConventionService(); - AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName)); - AddCodeElementWriter(new CodeClassEndWriter(conventionService)); - AddCodeElementWriter(new CodeNamespaceWriter(conventionService)); - AddCodeElementWriter(new CodeEnumWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService)); - AddCodeElementWriter(new CodePropertyWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName)); + AddOrReplaceCodeElementWriter(new CodeClassEndWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeNamespaceWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); } } } diff --git a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs index c7482b5194..f3a61eaaa0 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs @@ -12,13 +12,13 @@ class ShellWriter : CSharpWriter public ShellWriter(string rootPath, string clientNamespaceName) : base(rootPath, clientNamespaceName) { var conventionService = new CSharpConventionService(); - AddCodeElementWriter(new ShellCodeClassDeclarationWriter(conventionService)); - AddCodeElementWriter(new CodeClassEndWriter(conventionService)); - AddCodeElementWriter(new CodeEnumWriter(conventionService)); - AddCodeElementWriter(new CodeIndexerWriter(conventionService)); - AddCodeElementWriter(new ShellCodeMethodWriter(conventionService)); - AddCodeElementWriter(new CodePropertyWriter(conventionService)); - AddCodeElementWriter(new CodeTypeWriter(conventionService)); + AddOrReplaceCodeElementWriter(new ShellCodeClassDeclarationWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassEndWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeIndexerWriter(conventionService)); + AddOrReplaceCodeElementWriter(new ShellCodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); } } } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index 269cfcf981..234259fa94 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -6,12 +6,12 @@ public TypeScriptWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new TypeScriptPathSegmenter(rootPath,clientNamespaceName); var conventionService = new TypeScriptConventionService(null); - AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName)); - AddCodeElementWriter(new CodeClassEndWriter()); - AddCodeElementWriter(new CodeEnumWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService)); - AddCodeElementWriter(new CodePropertyWriter(conventionService)); - AddCodeElementWriter(new CodeTypeWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName)); + AddOrReplaceCodeElementWriter(new CodeClassEndWriter()); + AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); } } } From 0802de4e2c7667b15411ef1a6b929ee0af930108 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 21 Oct 2021 14:39:35 -0400 Subject: [PATCH 08/69] - adds path and query parameters for non fluent languages Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 15 +++++++++++ .../OpenApiUrlTreeNodeExtensions.cs | 6 ++++- src/Kiota.Builder/KiotaBuilder.cs | 27 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 961992dca4..92ead70ecd 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -55,6 +55,21 @@ public void RemoveParametersByKind(params CodeParameterKind[] kinds) { /// The property this method accesses to when it's a getter or setter. /// public CodeProperty AccessedProperty { get; set; } + /// + /// The combination of the path and query parameters for the current URL. + /// Only use this property if the language you are generating for doesn't support fluent API style (e.g. Shell/CLI) + /// + public IEnumerable PathAndQueryParameters { get; private set; } + public void AddPathOrQueryParameter(params CodeParameter[] parameters) { + if(parameters == null || !parameters.Any()) return; + foreach (var parameter in parameters) { + EnsureElementsAreChildren(parameter); + } + if(PathAndQueryParameters == null) + PathAndQueryParameters = new List(parameters); + else if (PathAndQueryParameters is List cast) + cast.AddRange(parameters); + } public bool IsOfKind(params CodeMethodKind[] kinds) { return kinds?.Contains(MethodKind) ?? false; } diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs index 59413402fa..d0a9602643 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -115,8 +115,12 @@ private static string SanitizePathParameterNames(string original) { if(string.IsNullOrEmpty(original) || !original.Contains('{')) return original; var parameters = pathParamMatcher.Matches(original); foreach(var value in parameters.Select(x => x.Value)) - original = original.Replace(value, value.Replace('-', '_')); + original = original.SanitizePathParameterName(); return original; } + public static string SanitizePathParameterName(this string original) { + if(string.IsNullOrEmpty(original)) return original; + return original.Replace('-', '_'); + } } } diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index a5fdb9fb44..a04f1e52ab 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -586,10 +586,37 @@ private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationTyp Description = operation.Description ?? operation.Summary, ReturnType = new CodeType { Name = "RequestInformation", IsNullable = false, IsExternal = true}, }; + if(config.Language == GenerationLanguage.Shell) + SetPathAndQueryParameters(generatorMethod, currentNode, operation); parentClass.AddMethod(generatorMethod); AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, generatorMethod); logger.LogTrace("Creating method {name} of {type}", generatorMethod.Name, generatorMethod.ReturnType); } + private static void SetPathAndQueryParameters(CodeMethod target, OpenApiUrlTreeNode currentNode, OpenApiOperation operation) { + var pathAndQueryParameters = currentNode + .PathItems[Constants.DefaultOpenApiLabel] + .Parameters + .Where(x => x.In == ParameterLocation.Path || x.In == ParameterLocation.Query) + .Select(x => new CodeParameter{ + Name = x.Name.TrimStart('$').SanitizePathParameterName(), + Type = GetPrimitiveType(x.Schema), + Description = x.Description, + ParameterKind = x.In == ParameterLocation.Path ? CodeParameterKind.Path : CodeParameterKind.QueryParameter, + Optional = !x.Required + }) + .Union(operation + .Parameters + .Where(x => x.In == ParameterLocation.Path || x.In == ParameterLocation.Query) + .Select(x => new CodeParameter{ + Name = x.Name.TrimStart('$').SanitizePathParameterName(), + Type = GetPrimitiveType(x.Schema), + Description = x.Description, + ParameterKind = x.In == ParameterLocation.Path ? CodeParameterKind.Path : CodeParameterKind.QueryParameter, + Optional = !x.Required + })) + .ToArray(); + target.AddPathOrQueryParameter(pathAndQueryParameters); + } private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) { var nonBinaryRequestBody = operation.RequestBody?.Content?.FirstOrDefault(x => !RequestBodyBinaryContentType.Equals(x.Key, StringComparison.OrdinalIgnoreCase)); if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null) From 0c668ec940007773dbad3237c153ec7f236b1968 Mon Sep 17 00:00:00 2001 From: calebkiage Date: Mon, 25 Oct 2021 19:47:15 +0300 Subject: [PATCH 09/69] Update shell refiner to add command builders --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 3 +- .../Extensions/StringExtensions.cs | 6 +- src/Kiota.Builder/Refiners/ShellRefiner.cs | 27 +++--- .../Writers/CSharp/CodeMethodWriter.cs | 29 ++++--- .../Shell/ShellCodeClassDeclarationWriter.cs | 3 +- .../Writers/Shell/ShellCodeMethodWriter.cs | 84 ++++++++++++++----- 6 files changed, 102 insertions(+), 50 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 961992dca4..cef37d9d14 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -18,7 +18,8 @@ public enum CodeMethodKind ClientConstructor, RequestBuilderBackwardCompatibility, RequestBuilderWithParameters, - RawUrlConstructor + RawUrlConstructor, + CommandBuilder } public enum HttpMethod { Get, diff --git a/src/Kiota.Builder/Extensions/StringExtensions.cs b/src/Kiota.Builder/Extensions/StringExtensions.cs index 6c297d0825..0b5de8d336 100644 --- a/src/Kiota.Builder/Extensions/StringExtensions.cs +++ b/src/Kiota.Builder/Extensions/StringExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -26,7 +26,7 @@ public static string ReplaceValueIdentifier(this string original) => public static string TrimQuotes(this string original) => original?.Trim('\'', '"'); - public static string ToSnakeCase(this string name) + public static string ToSnakeCase(this string name, char separator = '_') { if(string.IsNullOrEmpty(name)) return name; var chunks = name.Split('-', StringSplitOptions.RemoveEmptyEntries); @@ -40,7 +40,7 @@ public static string ToSnakeCase(this string name) foreach (var item in identifier[1..]) { if(char.IsUpper(item)) { - sb.Append('_'); + sb.Append(separator); sb.Append(char.ToLowerInvariant(item)); } else { sb.Append(item); diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index ca5639ef05..a554dd1913 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -23,7 +23,7 @@ public override void Refine(CodeNamespace generatedCode) RemoveModelClasses(generatedCode); RemoveEnums(generatedCode); RemoveConstructors(generatedCode); - TurnRequestBuildersIntoCommandBuilders(generatedCode); + CreateCommandBuilders(generatedCode); AddAsyncSuffix(generatedCode); AddInnerClasses(generatedCode, false); CapitalizeNamespacesFirstLetters(generatedCode); @@ -92,13 +92,10 @@ private static void RemoveConstructors(CodeElement currentElement) CrawlTree(currentElement, RemoveConstructors); } - private static void TurnRequestBuildersIntoCommandBuilders(CodeElement currentElement) + private static void CreateCommandBuilders(CodeElement currentElement) { if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder)) { - - (currentClass.StartBlock as CodeClass.Declaration).IsStatic = true; - // Replace Nav Properties with BuildXXXCommand methods var navProperties = currentClass.GetChildElements().Where(e => e is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestBuilder)).Cast(); foreach (var navProp in navProperties) @@ -107,21 +104,23 @@ private static void TurnRequestBuildersIntoCommandBuilders(CodeElement currentEl currentClass.AddMethod(method); currentClass.RemoveChildElement(navProp); } - // Change signtature of RequestExecutors + // Clone executors & convert to build command var requestMethods = currentClass.GetChildElements().Where(e => e is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor)).Cast(); foreach (var requestMethod in requestMethods) { - requestMethod.IsAsync = false; - requestMethod.IsStatic = true; - requestMethod.Name = $"Build{requestMethod.Name}Command"; - requestMethod.ReturnType = CreateCommandType(requestMethod); + CodeMethod clone = requestMethod.Clone() as CodeMethod; + clone.IsAsync = false; + clone.Name = $"Build{clone.Name}Command"; + clone.ReturnType = CreateCommandType(requestMethod); + clone.MethodKind = CodeMethodKind.CommandBuilder; + currentClass.AddMethod(clone); } var buildMethod = new CodeMethod { Name = "Build", - IsStatic = true, - IsAsync = false + IsAsync = false, + MethodKind = CodeMethodKind.CommandBuilder }; buildMethod.AddParameter(new CodeParameter { Name = "httpCore", Type = new CodeType { Name = "IHttpCore", IsExternal = true } }); // Add calls to BuildMethods here.. @@ -133,7 +132,7 @@ private static void TurnRequestBuildersIntoCommandBuilders(CodeElement currentEl currentClass.AddMethod(buildMethod); } - CrawlTree(currentElement, TurnRequestBuildersIntoCommandBuilders); + CrawlTree(currentElement, CreateCommandBuilders); } private static CodeType CreateCommandType(CodeElement parent) @@ -151,7 +150,7 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod codeMethod.IsAsync = false; codeMethod.IsStatic = true; codeMethod.Name = $"Build{navProperty.Name.ToFirstCharacterUpperCase()}Command"; - codeMethod.MethodKind = CodeMethodKind.RequestBuilderWithParameters; + codeMethod.MethodKind = CodeMethodKind.CommandBuilder; codeMethod.ReturnType = CreateCommandType(codeMethod); return codeMethod; } diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index 6f7ac996e1..a2b0f36450 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -22,11 +22,6 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri WriteMethodDocumentation(codeElement, writer); WriteMethodPrototype(codeElement, writer, returnType, inherits, isVoid); writer.IncreaseIndent(); - var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); - var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter); - var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers); - var optionsParam = codeElement.Parameters.OfKind(CodeParameterKind.Options); - var requestParams = new RequestParams(requestBodyParam, queryStringParam, headersParam, optionsParam); foreach(var parameter in codeElement.Parameters.Where(x => !x.Optional).OrderBy(x => x.Name)) { var parameterName = parameter.Name.ToFirstCharacterLowerCase(); if(nameof(String).Equals(parameter.Type.Name, StringComparison.OrdinalIgnoreCase)) @@ -34,10 +29,24 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri else writer.WriteLine($"_ = {parameterName} ?? throw new ArgumentNullException(nameof({parameterName}));"); } - switch(codeElement.MethodKind) { + HandleMethodKind(codeElement, writer, inherits, parentClass, isVoid); + writer.DecreaseIndent(); + writer.WriteLine("}"); + } + + protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter writer, bool inherits, CodeClass parentClass, bool isVoid) + { + var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); + var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); + var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter); + var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers); + var optionsParam = codeElement.Parameters.OfKind(CodeParameterKind.Options); + var requestParams = new RequestParams(requestBodyParam, queryStringParam, headersParam, optionsParam); + switch (codeElement.MethodKind) + { case CodeMethodKind.Serializer: WriteSerializerBody(inherits, codeElement, parentClass, writer); - break; + break; case CodeMethodKind.RequestGenerator: WriteRequestGeneratorBody(codeElement, requestParams, parentClass, writer); break; @@ -63,12 +72,12 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri throw new InvalidOperationException("getters and setters are automatically added on fields in dotnet"); case CodeMethodKind.RequestBuilderBackwardCompatibility: throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties."); + case CodeMethodKind.CommandBuilder: + break; default: writer.WriteLine("return null;"); - break; + break; } - writer.DecreaseIndent(); - writer.WriteLine("}"); } private void WriteRequestBuilderBody(CodeClass parentClass, CodeMethod codeElement, LanguageWriter writer) { diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs index f449f3bc48..8638507ebb 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs @@ -34,8 +34,7 @@ public override void WriteCodeElement(CodeClass.Declaration codeElement, Languag var derivation = derivedTypes.Any() ? ": " + derivedTypes.Select(x => x.ToFirstCharacterUpperCase()).Aggregate((x, y) => $"{x}, {y}") + " " : string.Empty; if (codeElement.Parent is CodeClass parentClass) conventions.WriteShortDescription(parentClass.Description, writer); - var staticModifier = codeElement.IsStatic ? "static " : string.Empty; - writer.WriteLine($"public {staticModifier}class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); + writer.WriteLine($"public class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); writer.IncreaseIndent(); } } diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index b076896ef6..34380bf744 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -13,28 +13,72 @@ public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(c { } - protected override void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) + protected override void HandleMethodKind(CodeMethod codeElement, LanguageWriter writer, bool inherits, CodeClass parentClass, bool isVoid) { - if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); + base.HandleMethodKind(codeElement, writer, inherits, parentClass, isVoid); + if (codeElement.MethodKind == CodeMethodKind.CommandBuilder) + { + var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); + var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); + var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter); + var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers); + var optionsParam = codeElement.Parameters.OfKind(CodeParameterKind.Options); + var requestParams = new RequestParams(requestBodyParam, queryStringParam, headersParam, optionsParam); + WriteCommandBuilderBody(codeElement, requestParams, isVoid, returnType, writer); + } + } + + protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) + { + if (codeElement.HttpMethod == null) + { + // Build method + // Puts together the BuildXXCommand objects. Needs a nav property name e.g. users + // Command("users") -> Command("get") + } else + { + var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); + var generatorMethodName = (codeElement.Parent as CodeClass) + .Methods + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) + ?.Name; + var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } + .Select(x => x?.Name).Where(x => x != null).Aggregate((x, y) => $"{x}, {y}"); + writer.WriteLine($"var command = new Command(\"{codeElement.HttpMethod.ToString().ToLower()}\") {{"); + writer.IncreaseIndent(); + writer.WriteLine($"Handler = CommandHandler.Create<>(async () => {{"); + writer.IncreaseIndent(); + writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});"); + writer.WriteLine($"{(isVoid ? string.Empty : "return ")}await HttpCore.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo, responseHandler);"); + writer.DecreaseIndent(); + writer.WriteLine("})"); + writer.DecreaseIndent(); + writer.WriteLine("};"); + writer.WriteLine("// Create options for all the parameters"); // investigate exploding query params + + foreach (var option in codeElement.Parameters) + { + if (option.ParameterKind == CodeParameterKind.ResponseHandler) + { + continue; + } + var optionBuilder = new StringBuilder("new Option("); + optionBuilder.Append($"\"{option.Name}\""); + if (option.DefaultValue != null) + { + optionBuilder.Append($", getDefaultValue: ()=> {option.DefaultValue}"); + } + + if (!String.IsNullOrEmpty(option.Description)) + { + optionBuilder.Append($", description: \"{option.Description}\""); + } - var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); - var generatorMethodName = (codeElement.Parent as CodeClass) - .Methods - .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) - ?.Name; - var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } - .Select(x => x?.Name).Where(x => x != null).Aggregate((x, y) => $"{x}, {y}"); - writer.WriteLine($"var command = new Command(\"{codeElement.HttpMethod.ToString().ToLower()}\") {{"); - writer.IncreaseIndent(); - writer.WriteLine($"Handler = CommandHandler.Create<>(async () => {{"); - writer.IncreaseIndent(); - writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});"); - writer.WriteLine($"{(isVoid ? string.Empty : "return ")}await HttpCore.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo, responseHandler);"); - writer.DecreaseIndent(); - writer.WriteLine("})"); - writer.DecreaseIndent(); - writer.WriteLine("};"); - writer.WriteLine("// Create options for all the parameters"); + optionBuilder.Append(')'); + writer.WriteLine($"command.AddOption({optionBuilder});"); + writer.WriteLine($"// {option.Type.Name}"); //GetTypeString + } + } } } } From 87d12e672847558ab6b65e109e293a4caae0b929 Mon Sep 17 00:00:00 2001 From: calebkiage Date: Mon, 25 Oct 2021 20:16:00 +0300 Subject: [PATCH 10/69] Remove parameters from build command --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 5 +++++ src/Kiota.Builder/Refiners/ShellRefiner.cs | 4 +++- .../Writers/Shell/ShellCodeMethodWriter.cs | 11 ++++++++--- src/kiota/Properties/launchSettings.json | 6 +++++- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 7307728e1a..15ec96382b 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -48,6 +48,11 @@ public class CodeMethod : CodeTerminal, ICloneable, IDocumentedElement public void RemoveParametersByKind(params CodeParameterKind[] kinds) { parameters.RemoveAll(p => p.IsOfKind(kinds)); } + + public void ClearParameters() + { + parameters.Clear(); + } public IEnumerable Parameters { get => parameters; } public bool IsStatic {get;set;} = false; public bool IsAsync {get;set;} = true; diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index a554dd1913..e7d60e7767 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -111,8 +111,10 @@ private static void CreateCommandBuilders(CodeElement currentElement) CodeMethod clone = requestMethod.Clone() as CodeMethod; clone.IsAsync = false; clone.Name = $"Build{clone.Name}Command"; - clone.ReturnType = CreateCommandType(requestMethod); + clone.ReturnType = CreateCommandType(clone); clone.MethodKind = CodeMethodKind.CommandBuilder; + clone.OriginalMethod = requestMethod; + clone.ClearParameters(); currentClass.AddMethod(clone); } diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 34380bf744..ac4c1e70cd 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -42,8 +42,13 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req .Methods .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) ?.Name; - var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } - .Select(x => x?.Name).Where(x => x != null).Aggregate((x, y) => $"{x}, {y}"); + var origParams = codeElement.OriginalMethod.Parameters; + var parametersList = new CodeParameter[] { + origParams.OfKind(CodeParameterKind.RequestBody), + origParams.OfKind(CodeParameterKind.QueryParameter), + origParams.OfKind(CodeParameterKind.Headers), + origParams.OfKind(CodeParameterKind.Options) + }.Select(x => x?.Name).Where(x => x != null).Aggregate((x, y) => $"{x}, {y}"); writer.WriteLine($"var command = new Command(\"{codeElement.HttpMethod.ToString().ToLower()}\") {{"); writer.IncreaseIndent(); writer.WriteLine($"Handler = CommandHandler.Create<>(async () => {{"); @@ -56,7 +61,7 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req writer.WriteLine("};"); writer.WriteLine("// Create options for all the parameters"); // investigate exploding query params - foreach (var option in codeElement.Parameters) + foreach (var option in origParams) { if (option.ParameterKind == CodeParameterKind.ResponseHandler) { diff --git a/src/kiota/Properties/launchSettings.json b/src/kiota/Properties/launchSettings.json index 12e3f758fe..573e0d650e 100644 --- a/src/kiota/Properties/launchSettings.json +++ b/src/kiota/Properties/launchSettings.json @@ -2,7 +2,11 @@ "profiles": { "kiota": { "commandName": "Project", - "commandLineArgs": "--openapi C:\\src\\msgraph-sdk-powershell\\openApiDocs\\v1.0\\mail.yml -o C:\\Users\\darrmi\\source\\github\\darrelmiller\\OpenApiClient\\Generated -c GraphClient --loglevel Information" + "commandLineArgs": "--output output/shell --openapi C:\\src\\msgraph-sdk-powershell\\openApiDocs\\beta\\Users.yml --language Shell --loglevel Information" + }, + "kiotaLocal": { + "commandName": "Project", + "commandLineArgs": "--openapi C:\\\\Users\\\\calebmagiya\\\\Documents\\\\projects\\\\microsoft\\\\microsoftgraph\\\\msgraph-sdk-powershell\\\\openApiDocs\\\\v1.0\\\\mail.yml -o C:\\\\Users\\\\calebmagiya\\\\Documents\\\\projects\\\\practice\\\\msgraph-cli\\\\generated -c GraphClient --loglevel Debug --language Shell" } } } \ No newline at end of file From cdd23bbb189fe3166ef92019361e0dcd99ae642b Mon Sep 17 00:00:00 2001 From: calebkiage Date: Mon, 25 Oct 2021 22:06:38 +0300 Subject: [PATCH 11/69] Update command handler --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 6 +-- .../Shell/ShellCodeClassDeclarationWriter.cs | 41 ------------------- .../Writers/Shell/ShellCodeMethodWriter.cs | 28 ++++++------- .../Writers/Shell/ShellWriter.cs | 2 +- src/kiota/Properties/launchSettings.json | 6 +-- 5 files changed, 19 insertions(+), 64 deletions(-) delete mode 100644 src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index e7d60e7767..69767b5e72 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -20,9 +20,9 @@ public override void Refine(CodeNamespace generatedCode) MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore); AddPropertiesAndMethodTypesImports(generatedCode, false, false, false); - RemoveModelClasses(generatedCode); - RemoveEnums(generatedCode); - RemoveConstructors(generatedCode); + //RemoveModelClasses(generatedCode); + //RemoveEnums(generatedCode); + //RemoveConstructors(generatedCode); CreateCommandBuilders(generatedCode); AddAsyncSuffix(generatedCode); AddInnerClasses(generatedCode, false); diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs deleted file mode 100644 index 8638507ebb..0000000000 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeClassDeclarationWriter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Kiota.Builder.Extensions; -using Kiota.Builder.Writers.CSharp; - -namespace Kiota.Builder.Writers.Shell -{ - class ShellCodeClassDeclarationWriter : CodeClassDeclarationWriter - { - public ShellCodeClassDeclarationWriter(CSharpConventionService conventionService) : base(conventionService) - { - } - - public override void WriteCodeElement(CodeClass.Declaration codeElement, LanguageWriter writer) - { - codeElement.Usings - .Where(x => (x.Declaration?.IsExternal ?? true) || !x.Declaration.Name.Equals(codeElement.Name, StringComparison.OrdinalIgnoreCase)) // needed for circular requests patterns like message folder - .Select(x => x.Declaration?.IsExternal ?? false ? - $"using {x.Declaration.Name.NormalizeNameSpaceName(".")};" : - $"using {x.Name.NormalizeNameSpaceName(".")};") - .Distinct() - .OrderBy(x => x) - .ToList() - .ForEach(x => writer.WriteLine(x)); - if (codeElement?.Parent?.Parent is CodeNamespace) - { - writer.WriteLine($"namespace {codeElement.Parent.Parent.Name} {{"); - writer.IncreaseIndent(); - } - var derivedTypes = new List { codeElement.Inherits?.Name } - .Union(codeElement.Implements.Select(x => x.Name)) - .Where(x => x != null); - var derivation = derivedTypes.Any() ? ": " + derivedTypes.Select(x => x.ToFirstCharacterUpperCase()).Aggregate((x, y) => $"{x}, {y}") + " " : string.Empty; - if (codeElement.Parent is CodeClass parentClass) - conventions.WriteShortDescription(parentClass.Description, writer); - writer.WriteLine($"public class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); - writer.IncreaseIndent(); - } - } -} diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index ac4c1e70cd..cd576c696b 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -38,9 +38,9 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req } else { var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); - var generatorMethodName = (codeElement.Parent as CodeClass) + var executorMethodName = (codeElement.Parent as CodeClass) .Methods - .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor) && x.HttpMethod == codeElement.HttpMethod) ?.Name; var origParams = codeElement.OriginalMethod.Parameters; var parametersList = new CodeParameter[] { @@ -48,17 +48,8 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req origParams.OfKind(CodeParameterKind.QueryParameter), origParams.OfKind(CodeParameterKind.Headers), origParams.OfKind(CodeParameterKind.Options) - }.Select(x => x?.Name).Where(x => x != null).Aggregate((x, y) => $"{x}, {y}"); - writer.WriteLine($"var command = new Command(\"{codeElement.HttpMethod.ToString().ToLower()}\") {{"); - writer.IncreaseIndent(); - writer.WriteLine($"Handler = CommandHandler.Create<>(async () => {{"); - writer.IncreaseIndent(); - writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});"); - writer.WriteLine($"{(isVoid ? string.Empty : "return ")}await HttpCore.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo, responseHandler);"); - writer.DecreaseIndent(); - writer.WriteLine("})"); - writer.DecreaseIndent(); - writer.WriteLine("};"); + }.Where(x => x?.Name != null); + writer.WriteLine($"var command = new Command(\"{codeElement.HttpMethod.ToString().ToLower()}\");"); writer.WriteLine("// Create options for all the parameters"); // investigate exploding query params foreach (var option in origParams) @@ -81,8 +72,17 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req optionBuilder.Append(')'); writer.WriteLine($"command.AddOption({optionBuilder});"); - writer.WriteLine($"// {option.Type.Name}"); //GetTypeString } + + var paramTypes = parametersList.Select(x => conventions.GetTypeString(x.Type, x)).Aggregate((x, y) => $"{x}, {y}"); + var paramNames = parametersList.Select(x => x.Name).Aggregate((x, y) => $"{x}, {y}"); + writer.WriteLine($"command.Handler = CommandHandler.Create<{paramTypes}>(async ({paramNames}) => {{"); + writer.IncreaseIndent(); + writer.WriteLine($"var result = await {executorMethodName}({paramNames});"); + writer.WriteLine("// Print request output"); + writer.DecreaseIndent(); + writer.WriteLine("});"); + writer.WriteLine("return command;"); } } } diff --git a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs index f3a61eaaa0..b1c3bc3812 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs @@ -12,7 +12,7 @@ class ShellWriter : CSharpWriter public ShellWriter(string rootPath, string clientNamespaceName) : base(rootPath, clientNamespaceName) { var conventionService = new CSharpConventionService(); - AddOrReplaceCodeElementWriter(new ShellCodeClassDeclarationWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeClassEndWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeIndexerWriter(conventionService)); diff --git a/src/kiota/Properties/launchSettings.json b/src/kiota/Properties/launchSettings.json index 573e0d650e..12e3f758fe 100644 --- a/src/kiota/Properties/launchSettings.json +++ b/src/kiota/Properties/launchSettings.json @@ -2,11 +2,7 @@ "profiles": { "kiota": { "commandName": "Project", - "commandLineArgs": "--output output/shell --openapi C:\\src\\msgraph-sdk-powershell\\openApiDocs\\beta\\Users.yml --language Shell --loglevel Information" - }, - "kiotaLocal": { - "commandName": "Project", - "commandLineArgs": "--openapi C:\\\\Users\\\\calebmagiya\\\\Documents\\\\projects\\\\microsoft\\\\microsoftgraph\\\\msgraph-sdk-powershell\\\\openApiDocs\\\\v1.0\\\\mail.yml -o C:\\\\Users\\\\calebmagiya\\\\Documents\\\\projects\\\\practice\\\\msgraph-cli\\\\generated -c GraphClient --loglevel Debug --language Shell" + "commandLineArgs": "--openapi C:\\src\\msgraph-sdk-powershell\\openApiDocs\\v1.0\\mail.yml -o C:\\Users\\darrmi\\source\\github\\darrelmiller\\OpenApiClient\\Generated -c GraphClient --loglevel Information" } } } \ No newline at end of file From 4a78a556ffe1481ab7ebed3414cddd3e1bdbb36b Mon Sep 17 00:00:00 2001 From: calebkiage Date: Tue, 26 Oct 2021 12:59:15 +0300 Subject: [PATCH 12/69] Apply suggested Linq optimization --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 69767b5e72..e9bcda83e5 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -97,7 +97,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder)) { // Replace Nav Properties with BuildXXXCommand methods - var navProperties = currentClass.GetChildElements().Where(e => e is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestBuilder)).Cast(); + var navProperties = currentClass.GetChildElements().OfType().Where(e => e.IsOfKind(CodePropertyKind.RequestBuilder)); foreach (var navProp in navProperties) { var method = CreateBuildCommandMethod(navProp, currentClass); @@ -105,7 +105,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) currentClass.RemoveChildElement(navProp); } // Clone executors & convert to build command - var requestMethods = currentClass.GetChildElements().Where(e => e is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor)).Cast(); + var requestMethods = currentClass.GetChildElements().OfType().Where(e => e.IsOfKind(CodeMethodKind.RequestExecutor)); foreach (var requestMethod in requestMethods) { CodeMethod clone = requestMethod.Clone() as CodeMethod; From c43d8fadfe2097620efc5ca569c181981e0b4feb Mon Sep 17 00:00:00 2001 From: calebkiage Date: Tue, 26 Oct 2021 23:20:54 +0300 Subject: [PATCH 13/69] Add code to set up command tree --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 60 +++++++++---- .../Writers/Shell/ShellCodeMethodWriter.cs | 84 +++++++++++++++++-- 2 files changed, 117 insertions(+), 27 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index e9bcda83e5..5f31e45b82 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -14,8 +14,6 @@ public override void Refine(CodeNamespace generatedCode) { // Remove PathSegment field // Convert Properties to AddCommand - - AddDefaultImports(generatedCode); MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore); @@ -104,13 +102,40 @@ private static void CreateCommandBuilders(CodeElement currentElement) currentClass.AddMethod(method); currentClass.RemoveChildElement(navProp); } + + // Build command for indexers + var indexers = currentClass.GetChildElements().OfType(); + var classHasIndexers = indexers.Any(); + foreach (var indexer in indexers) + { + var method = new CodeMethod + { + Name = "BuildCommand", + IsAsync = false, + MethodKind = CodeMethodKind.CommandBuilder, + OriginalIndexer = indexer + }; + + method.ReturnType = CreateCommandType(method); + method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + currentClass.AddMethod(method); + currentClass.RemoveChildElement(indexer); + } + // Clone executors & convert to build command var requestMethods = currentClass.GetChildElements().OfType().Where(e => e.IsOfKind(CodeMethodKind.RequestExecutor)); foreach (var requestMethod in requestMethods) { CodeMethod clone = requestMethod.Clone() as CodeMethod; + var cmdName = clone.Name; + if (classHasIndexers) + { + if (clone.HttpMethod == HttpMethod.Get) cmdName = "List"; + if (clone.HttpMethod == HttpMethod.Post) cmdName = "Create"; + } + clone.IsAsync = false; - clone.Name = $"Build{clone.Name}Command"; + clone.Name = $"Build{cmdName}Command"; clone.ReturnType = CreateCommandType(clone); clone.MethodKind = CodeMethodKind.CommandBuilder; clone.OriginalMethod = requestMethod; @@ -118,21 +143,20 @@ private static void CreateCommandBuilders(CodeElement currentElement) currentClass.AddMethod(clone); } - var buildMethod = new CodeMethod + // Build root command + var clientConstructor = currentClass.GetChildElements().OfType().FirstOrDefault(m => m.MethodKind == CodeMethodKind.ClientConstructor); + if (clientConstructor != null) { - Name = "Build", - IsAsync = false, - MethodKind = CodeMethodKind.CommandBuilder - }; - buildMethod.AddParameter(new CodeParameter { Name = "httpCore", Type = new CodeType { Name = "IHttpCore", IsExternal = true } }); - // Add calls to BuildMethods here.. - buildMethod.ReturnType = new CodeType - { - Name = "Command", - IsExternal = true - }; - currentClass.AddMethod(buildMethod); - + var rootMethod = new CodeMethod + { + Name = "BuildCommand", + IsAsync = false, + MethodKind = CodeMethodKind.CommandBuilder, + ReturnType = new CodeType { Name = "Command", IsExternal = true }, + OriginalMethod = clientConstructor + }; + currentClass.AddMethod(rootMethod); + } } CrawlTree(currentElement, CreateCommandBuilders); } @@ -150,10 +174,10 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod { var codeMethod = new CodeMethod(); codeMethod.IsAsync = false; - codeMethod.IsStatic = true; codeMethod.Name = $"Build{navProperty.Name.ToFirstCharacterUpperCase()}Command"; codeMethod.MethodKind = CodeMethodKind.CommandBuilder; codeMethod.ReturnType = CreateCommandType(codeMethod); + codeMethod.AccessedProperty = navProperty; return codeMethod; } diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index cd576c696b..55c5382b12 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -1,8 +1,7 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; +using System.Text.RegularExpressions; using Kiota.Builder.Writers.CSharp; namespace Kiota.Builder.Writers.Shell @@ -30,26 +29,92 @@ protected override void HandleMethodKind(CodeMethod codeElement, LanguageWriter protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) { + var parent = codeElement.Parent as CodeClass; + var classMethods = parent.Methods; + var nameRegex = new Regex("(?:[Bb]uild|[Cc]ommand)"); // Use convention for command builder + var uppercaseRegex = new Regex("([A-Z])"); + var name = nameRegex.Replace(codeElement.Name, ""); + name = uppercaseRegex.Replace(name, "-$1").TrimStart('-').ToLower(); + if (codeElement.HttpMethod == null) { // Build method // Puts together the BuildXXCommand objects. Needs a nav property name e.g. users // Command("users") -> Command("get") + if (String.IsNullOrWhiteSpace(name)) + { + // BuildCommand function + if (codeElement.OriginalMethod?.MethodKind == CodeMethodKind.ClientConstructor) + { + var commandBuilderMethods = classMethods.Where(m => m.MethodKind == CodeMethodKind.CommandBuilder && m != codeElement); + writer.WriteLine($"var command = new RootCommand();"); + foreach (var method in commandBuilderMethods) + { + writer.WriteLine($"command.AddCommand({method.Name}());"); + } + + writer.WriteLine("return command;"); + } + else if (codeElement.OriginalIndexer != null) + { + var targetClass = conventions.GetTypeString(codeElement.OriginalIndexer.ReturnType, codeElement); + var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType().Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)).ToList(); + conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); + writer.WriteLine("return new Command[] { "); + writer.IncreaseIndent(); + for (int i = 0; i < builderMethods.Count; i++) + { + writer.WriteLine($"builder.{builderMethods[i].Name}(),"); + } + writer.DecreaseIndent(); + writer.WriteLine("};"); + } + } else + { + CodeType codeReturnType = (codeElement.AccessedProperty?.Type) as CodeType; + + writer.WriteLine($"var command = new Command(\"{name}\");"); + + if (codeReturnType != null) + { + var targetClass = conventions.GetTypeString(codeReturnType, codeElement); + var builderMethods = codeReturnType.TypeDefinition.GetChildElements(true).OfType().Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)); + conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); + + writer.WriteLine("var allCommands = new List();"); + foreach (var method in builderMethods) + { + if (method.ReturnType.IsCollection) + { + writer.WriteLine($"allCommands.AddRange(builder.{method.Name}());"); + } else + { + writer.WriteLine($"allCommands.Add(builder.{method.Name}());"); + } + } + writer.WriteLine("foreach (var cmd in allCommands) {"); + writer.IncreaseIndent(); + writer.WriteLine("command.AddCommand(cmd);"); + writer.DecreaseIndent(); + writer.WriteLine("}"); + // SubCommands + } + + writer.WriteLine("return command;"); + } } else { var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); - var executorMethodName = (codeElement.Parent as CodeClass) - .Methods - .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor) && x.HttpMethod == codeElement.HttpMethod) - ?.Name; + var executorMethod = classMethods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor) && x.HttpMethod == codeElement.HttpMethod); var origParams = codeElement.OriginalMethod.Parameters; + var pathAndQueryParams = codeElement.OriginalMethod.PathAndQueryParameters; // Investigate why this is null var parametersList = new CodeParameter[] { origParams.OfKind(CodeParameterKind.RequestBody), origParams.OfKind(CodeParameterKind.QueryParameter), origParams.OfKind(CodeParameterKind.Headers), origParams.OfKind(CodeParameterKind.Options) }.Where(x => x?.Name != null); - writer.WriteLine($"var command = new Command(\"{codeElement.HttpMethod.ToString().ToLower()}\");"); + writer.WriteLine($"var command = new Command(\"{name}\");"); writer.WriteLine("// Create options for all the parameters"); // investigate exploding query params foreach (var option in origParams) @@ -76,10 +141,11 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req var paramTypes = parametersList.Select(x => conventions.GetTypeString(x.Type, x)).Aggregate((x, y) => $"{x}, {y}"); var paramNames = parametersList.Select(x => x.Name).Aggregate((x, y) => $"{x}, {y}"); + var isExecutorVoid = conventions.VoidTypeName.Equals(executorMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); writer.WriteLine($"command.Handler = CommandHandler.Create<{paramTypes}>(async ({paramNames}) => {{"); writer.IncreaseIndent(); - writer.WriteLine($"var result = await {executorMethodName}({paramNames});"); - writer.WriteLine("// Print request output"); + writer.WriteLine($"{(isExecutorVoid ? String.Empty : "var result = ")}await {executorMethod.Name}({paramNames});"); + writer.WriteLine("// Print request output. What if the request has no return?"); writer.DecreaseIndent(); writer.WriteLine("});"); writer.WriteLine("return command;"); From 14995f969d7f70031cc2d871093aca472a01d7da Mon Sep 17 00:00:00 2001 From: calebkiage Date: Wed, 27 Oct 2021 21:17:46 +0300 Subject: [PATCH 14/69] Update import declarations in generated code --- src/Kiota.Builder/CodeDOM/CodeClass.cs | 1 - src/Kiota.Builder/CodeDOM/CodeMethod.cs | 6 + src/Kiota.Builder/Refiners/ShellRefiner.cs | 74 ++++++++-- .../Writers/Shell/ShellCodeMethodWriter.cs | 126 +++++++++++++----- 4 files changed, 159 insertions(+), 48 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeClass.cs b/src/Kiota.Builder/CodeDOM/CodeClass.cs index dbc6596f31..d7e3da2dfb 100644 --- a/src/Kiota.Builder/CodeDOM/CodeClass.cs +++ b/src/Kiota.Builder/CodeDOM/CodeClass.cs @@ -6,7 +6,6 @@ namespace Kiota.Builder { public enum CodeClassKind { Custom, - CommandBuilder, RequestBuilder, Model, QueryParameters, diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 15ec96382b..37878ae0ff 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -100,6 +100,12 @@ public bool IsSerializationMethod { /// public CodeIndexer OriginalIndexer { get; set; } + /// + /// This is currently used for CommandBuilder methods to get the originall name without the Build prefix & Command suffix. + /// Avoids regex operations + /// + public string SimpleName { get; set; } = String.Empty; + public object Clone() { var method = new CodeMethod { diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 5f31e45b82..f898f033ff 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -14,16 +14,14 @@ public override void Refine(CodeNamespace generatedCode) { // Remove PathSegment field // Convert Properties to AddCommand - AddDefaultImports(generatedCode); + AddDefaultImports(generatedCode, defaultUsingEvaluators); MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore); AddPropertiesAndMethodTypesImports(generatedCode, false, false, false); - //RemoveModelClasses(generatedCode); - //RemoveEnums(generatedCode); - //RemoveConstructors(generatedCode); CreateCommandBuilders(generatedCode); AddAsyncSuffix(generatedCode); AddInnerClasses(generatedCode, false); + AddParsableInheritanceForModelClasses(generatedCode); CapitalizeNamespacesFirstLetters(generatedCode); ReplaceBinaryByNativeType(generatedCode, "Stream", "System.IO"); MakeEnumPropertiesNullable(generatedCode); @@ -139,6 +137,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) clone.ReturnType = CreateCommandType(clone); clone.MethodKind = CodeMethodKind.CommandBuilder; clone.OriginalMethod = requestMethod; + clone.SimpleName = cmdName; clone.ClearParameters(); currentClass.AddMethod(clone); } @@ -153,7 +152,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) IsAsync = false, MethodKind = CodeMethodKind.CommandBuilder, ReturnType = new CodeType { Name = "Command", IsExternal = true }, - OriginalMethod = clientConstructor + OriginalMethod = clientConstructor, }; currentClass.AddMethod(rootMethod); } @@ -178,22 +177,69 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod codeMethod.MethodKind = CodeMethodKind.CommandBuilder; codeMethod.ReturnType = CreateCommandType(codeMethod); codeMethod.AccessedProperty = navProperty; + codeMethod.SimpleName = navProperty.Name; return codeMethod; } - private static readonly string[] defaultNamespacesForClasses = new string[] { "System", "System.Collections.Generic", "System.Linq" }; - private static readonly string[] defaultNamespacesForRequestBuilders = new string[] { "System.Threading.Tasks", "System.IO", "Microsoft.Kiota.Abstractions", "Microsoft.Kiota.Abstractions.Serialization", "System.CommandLine", "System.CommandLine.Invocation" }; - - private static void AddDefaultImports(CodeElement current) + private static void AddParsableInheritanceForModelClasses(CodeElement currentElement) { - if (current is CodeClass currentClass) + if (currentElement is CodeClass currentClass && + currentClass.IsOfKind(CodeClassKind.Model) && + currentClass.StartBlock is CodeClass.Declaration declaration) { - currentClass.AddUsing(defaultNamespacesForClasses.Select(x => new CodeUsing { Name = x }).ToArray()); - if (currentClass.IsOfKind(CodeClassKind.RequestBuilder)) - currentClass.AddUsing(defaultNamespacesForRequestBuilders.Select(x => new CodeUsing { Name = x }).ToArray()); + declaration.AddImplements(new CodeType + { + IsExternal = true, + Name = $"IParsable", + }); + (currentClass.Parent is CodeClass parentClass && + parentClass.StartBlock is CodeClass.Declaration parentDeclaration ? + parentDeclaration : + declaration) + .AddUsings(new CodeUsing + { + Name = "Microsoft.Kiota.Abstractions.Serialization" + }); } - CrawlTree(current, AddDefaultImports); + CrawlTree(currentElement, AddParsableInheritanceForModelClasses); } + + private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = new AdditionalUsingEvaluator[] { + new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter), + "Microsoft.Kiota.Abstractions", "IRequestAdapter"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), + "Microsoft.Kiota.Abstractions", "HttpMethod", "RequestInformation", "IRequestOption"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), + "Microsoft.Kiota.Abstractions", "IResponseHandler"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Serializer), + "Microsoft.Kiota.Abstractions.Serialization", "ISerializationWriter"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer), + "Microsoft.Kiota.Abstractions.Serialization", "IParseNode"), + new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model), + "Microsoft.Kiota.Abstractions.Serialization", "IParsable"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), + "Microsoft.Kiota.Abstractions.Serialization", "IParsable"), + new (x => x is CodeClass || x is CodeEnum, + "System", "String"), + new (x => x is CodeClass, + "System.Collections.Generic", "List", "Dictionary"), + new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model, CodeClassKind.RequestBuilder), + "System.IO", "Stream"), + new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), + "System.Threading.Tasks", "Task"), + new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model, CodeClassKind.RequestBuilder), + "System.Linq", "Enumerable"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.ClientConstructor) && + method.Parameters.Any(y => y.IsOfKind(CodeParameterKind.BackingStore)), + "Microsoft.Kiota.Abstractions.Store", "IBackingStoreFactory", "IBackingStoreFactorySingleton"), + new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore), + "Microsoft.Kiota.Abstractions.Store", "IBackingStore", "IBackedModel", "BackingStoreFactorySingleton" ), + new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), + "System.CommandLine", "Command", "RootCommand"), + new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), + "System.CommandLine.Invocation", "CommandHandler"), + }; + private static void CapitalizeNamespacesFirstLetters(CodeElement current) { if (current is CodeNamespace currentNamespace) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 55c5382b12..0b4913fd40 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -3,11 +3,17 @@ using System.Text; using System.Text.RegularExpressions; using Kiota.Builder.Writers.CSharp; +using Kiota.Builder.Extensions; +using System.Collections.Generic; namespace Kiota.Builder.Writers.Shell { class ShellCodeMethodWriter : CodeMethodWriter { + private static Regex delimitedRegex = new Regex("(?<=[a-z])[-_\\.]([A-Za-z])"); + private static Regex camelCaseRegex = new Regex("(?<=[a-z])([A-Z])"); + private static Regex identifierRegex = new Regex("(?:[-_\\.]([a-zA-Z]))"); + public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(conventionService) { } @@ -18,11 +24,12 @@ protected override void HandleMethodKind(CodeMethod codeElement, LanguageWriter if (codeElement.MethodKind == CodeMethodKind.CommandBuilder) { var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); - var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); - var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter); - var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers); - var optionsParam = codeElement.Parameters.OfKind(CodeParameterKind.Options); - var requestParams = new RequestParams(requestBodyParam, queryStringParam, headersParam, optionsParam); + var origParams = codeElement.OriginalMethod?.Parameters ?? codeElement.Parameters; + var requestBodyParam = origParams.OfKind(CodeParameterKind.RequestBody); + //var queryStringParam = origParams.OfKind(CodeParameterKind.QueryParameter); + //var headersParam = origParams.OfKind(CodeParameterKind.Headers); + //var optionsParam = origParams.OfKind(CodeParameterKind.Options); + var requestParams = new RequestParams(requestBodyParam, null, null, null); WriteCommandBuilderBody(codeElement, requestParams, isVoid, returnType, writer); } } @@ -31,9 +38,8 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req { var parent = codeElement.Parent as CodeClass; var classMethods = parent.Methods; - var nameRegex = new Regex("(?:[Bb]uild|[Cc]ommand)"); // Use convention for command builder var uppercaseRegex = new Regex("([A-Z])"); - var name = nameRegex.Replace(codeElement.Name, ""); + var name = codeElement.SimpleName; name = uppercaseRegex.Replace(name, "-$1").TrimStart('-').ToLower(); if (codeElement.HttpMethod == null) @@ -81,22 +87,20 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req var builderMethods = codeReturnType.TypeDefinition.GetChildElements(true).OfType().Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); - writer.WriteLine("var allCommands = new List();"); foreach (var method in builderMethods) { if (method.ReturnType.IsCollection) { - writer.WriteLine($"allCommands.AddRange(builder.{method.Name}());"); + writer.WriteLine($"foreach (var cmd in builder.{method.Name}()) {{"); + writer.IncreaseIndent(); + writer.WriteLine("command.AddCommand(cmd);"); + writer.DecreaseIndent(); + writer.WriteLine("}"); } else { - writer.WriteLine($"allCommands.Add(builder.{method.Name}());"); + writer.WriteLine($"command.AddCommand(builder.{method.Name}());"); } } - writer.WriteLine("foreach (var cmd in allCommands) {"); - writer.IncreaseIndent(); - writer.WriteLine("command.AddCommand(cmd);"); - writer.DecreaseIndent(); - writer.WriteLine("}"); // SubCommands } @@ -105,26 +109,29 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req } else { var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); - var executorMethod = classMethods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor) && x.HttpMethod == codeElement.HttpMethod); + var generatorMethod = (codeElement.Parent as CodeClass) + .Methods + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); + var pathAndQueryParams = generatorMethod.PathAndQueryParameters; var origParams = codeElement.OriginalMethod.Parameters; - var pathAndQueryParams = codeElement.OriginalMethod.PathAndQueryParameters; // Investigate why this is null - var parametersList = new CodeParameter[] { - origParams.OfKind(CodeParameterKind.RequestBody), - origParams.OfKind(CodeParameterKind.QueryParameter), - origParams.OfKind(CodeParameterKind.Headers), - origParams.OfKind(CodeParameterKind.Options) - }.Where(x => x?.Name != null); + var parametersList = new List(); + parametersList.AddRange(generatorMethod.PathAndQueryParameters.Where(p => p.Name != null)); + if (origParams.Any(p => p.IsOfKind(CodeParameterKind.RequestBody))) + { + parametersList.Add(origParams.OfKind(CodeParameterKind.RequestBody)); + } writer.WriteLine($"var command = new Command(\"{name}\");"); writer.WriteLine("// Create options for all the parameters"); // investigate exploding query params + // Check the possible formatting options for headers in a cli. + // -h A=b -h + // -h A:B,B:C + // -h {"A": "B"} - foreach (var option in origParams) + foreach (var option in parametersList) { - if (option.ParameterKind == CodeParameterKind.ResponseHandler) - { - continue; - } - var optionBuilder = new StringBuilder("new Option("); - optionBuilder.Append($"\"{option.Name}\""); + var optionBuilder = new StringBuilder("new Option(\""); + if (option.Name.Length > 1) optionBuilder.Append('-'); + optionBuilder.Append($"-{NormalizeToOption(option.Name)}\""); if (option.DefaultValue != null) { optionBuilder.Append($", getDefaultValue: ()=> {option.DefaultValue}"); @@ -140,16 +147,69 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req } var paramTypes = parametersList.Select(x => conventions.GetTypeString(x.Type, x)).Aggregate((x, y) => $"{x}, {y}"); - var paramNames = parametersList.Select(x => x.Name).Aggregate((x, y) => $"{x}, {y}"); - var isExecutorVoid = conventions.VoidTypeName.Equals(executorMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); + var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).Aggregate((x, y) => $"{x}, {y}"); + var isHandlerVoid = conventions.VoidTypeName.Equals(codeElement.OriginalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); writer.WriteLine($"command.Handler = CommandHandler.Create<{paramTypes}>(async ({paramNames}) => {{"); writer.IncreaseIndent(); - writer.WriteLine($"{(isExecutorVoid ? String.Empty : "var result = ")}await {executorMethod.Name}({paramNames});"); + returnType = conventions.GetTypeString(codeElement.OriginalMethod.ReturnType, codeElement.OriginalMethod); + WriteCommandHandlerBody(codeElement.OriginalMethod, requestParams, isHandlerVoid, returnType, writer); + // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp writer.WriteLine("// Print request output. What if the request has no return?"); writer.DecreaseIndent(); writer.WriteLine("});"); writer.WriteLine("return command;"); } } + + protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) + { + if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); + + var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); + var generatorMethod = (codeElement.Parent as CodeClass) + .Methods + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); + var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } + .Select(x => x?.Name).Where(x => x != null).DefaultIfEmpty().Aggregate((x, y) => $"{x}, {y}"); + writer.WriteLine($"var requestInfo = {generatorMethod?.Name}({parametersList});"); + foreach (var param in generatorMethod.PathAndQueryParameters) + { + if (param.IsOfKind(CodeParameterKind.Path)) + { + writer.WriteLine($"requestInfo.PathParameters.Add(\"{param.Name}\", {NormalizeToIdentifier(param.Name)});"); + } else if (param.IsOfKind(CodeParameterKind.QueryParameter)) + { + writer.WriteLine($"requestInfo.QueryParameters.Add(\"{param.Name}\", {NormalizeToIdentifier(param.Name)});"); + } + } + + + writer.WriteLine($"{(isVoid ? string.Empty : "var result = ")}await RequestAdapter.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo);"); + } + + /// + /// Converts delimited string into camel case for use as identifiers + /// + /// + /// + private string NormalizeToIdentifier(string input) + { + return identifierRegex.Replace(input, m => m.Groups[1].Value.ToUpper()); + } + + /// + /// Converts camel-case or delimited string to '-' delimited string for use as a command option + /// + /// + /// + private string NormalizeToOption(string input) + { + var result = input; + result = camelCaseRegex.Replace(input, "-$1"); + // 2 passes for cases like "singleValueLegacyExtendedProperty_id" + result = delimitedRegex.Replace(input, "-$1"); + + return result.ToLower(); + } } } From 7162adbb22269f70e58f88165aba50a4e173b908 Mon Sep 17 00:00:00 2001 From: calebkiage Date: Mon, 1 Nov 2021 10:43:40 +0300 Subject: [PATCH 15/69] Generate code to output command results --- .../Writers/Shell/ShellCodeMethodWriter.cs | 64 +++++++++++++++++-- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 0b4913fd40..14b9aa9ea2 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -113,7 +113,8 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req .Methods .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); var pathAndQueryParams = generatorMethod.PathAndQueryParameters; - var origParams = codeElement.OriginalMethod.Parameters; + var originalMethod = codeElement.OriginalMethod; + var origParams = originalMethod.Parameters; var parametersList = new List(); parametersList.AddRange(generatorMethod.PathAndQueryParameters.Where(p => p.Name != null)); if (origParams.Any(p => p.IsOfKind(CodeParameterKind.RequestBody))) @@ -129,7 +130,13 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req foreach (var option in parametersList) { - var optionBuilder = new StringBuilder("new Option(\""); + var optionType = conventions.GetTypeString(option.Type, option); + var optionBuilder = new StringBuilder("new Option"); + if (!String.IsNullOrEmpty(optionType)) + { + optionBuilder.Append($"<{optionType}>"); + } + optionBuilder.Append("(\""); if (option.Name.Length > 1) optionBuilder.Append('-'); optionBuilder.Append($"-{NormalizeToOption(option.Name)}\""); if (option.DefaultValue != null) @@ -148,13 +155,51 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req var paramTypes = parametersList.Select(x => conventions.GetTypeString(x.Type, x)).Aggregate((x, y) => $"{x}, {y}"); var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).Aggregate((x, y) => $"{x}, {y}"); - var isHandlerVoid = conventions.VoidTypeName.Equals(codeElement.OriginalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); + var isHandlerVoid = conventions.VoidTypeName.Equals(originalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); writer.WriteLine($"command.Handler = CommandHandler.Create<{paramTypes}>(async ({paramNames}) => {{"); writer.IncreaseIndent(); - returnType = conventions.GetTypeString(codeElement.OriginalMethod.ReturnType, codeElement.OriginalMethod); - WriteCommandHandlerBody(codeElement.OriginalMethod, requestParams, isHandlerVoid, returnType, writer); + returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod); + WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer); // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp writer.WriteLine("// Print request output. What if the request has no return?"); + if (isHandlerVoid) + { + writer.WriteLine("Console.WriteLine(\"Success\");"); + } else + { + var contentType = originalMethod.ContentType ?? "application/json"; + writer.WriteLine($"using var serializer = RequestAdapter.SerializationWriterFactory.GetSerializationWriter(\"{contentType}\");"); + + var type = originalMethod.ReturnType as CodeType; + var typeString = conventions.GetTypeString(type, originalMethod); + if (type.TypeDefinition is CodeEnum) + { + if (type.IsCollection) + writer.WriteLine($"serializer.WriteCollectionOfEnumValues(null, result);"); + else + writer.WriteLine($"serializer.WriteEnumValue(null, result);"); + } + else if (conventions.IsPrimitiveType(typeString)) + { + if (type.IsCollection) + writer.WriteLine($"serializer.WriteCollectionOfPrimitiveValues(null, result);"); + else + writer.WriteLine($"serializer.Write{typeString.ToFirstCharacterUpperCase()}Value(null, result);"); + } + else + { + if (type.IsCollection) + writer.WriteLine($"serializer.WriteCollectionOfObjectValues(null, result);"); + else if (typeString == "Stream") + writer.WriteLine($"//serializer.WriteObjectValue(null, result);"); + else + writer.WriteLine($"serializer.WriteObjectValue(null, result);"); + } + writer.WriteLine("using var content = serializer.GetSerializedContent();"); + writer.WriteLine("using var reader = new StreamReader(content);"); + writer.WriteLine("var strContent = await reader.ReadToEndAsync();"); + writer.WriteLine("Console.Write(strContent + \"\\n\");"); + } writer.DecreaseIndent(); writer.WriteLine("});"); writer.WriteLine("return command;"); @@ -174,13 +219,18 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa writer.WriteLine($"var requestInfo = {generatorMethod?.Name}({parametersList});"); foreach (var param in generatorMethod.PathAndQueryParameters) { + var paramName = NormalizeToIdentifier(param.Name); + bool isStringParam = param.Type.Name?.ToLower() == "string"; + if (isStringParam) writer.Write($"if (!String.IsNullOrEmpty({paramName})) "); if (param.IsOfKind(CodeParameterKind.Path)) { - writer.WriteLine($"requestInfo.PathParameters.Add(\"{param.Name}\", {NormalizeToIdentifier(param.Name)});"); + writer.Write($"requestInfo.PathParameters.Add(\"{param.Name}\", {paramName});", !isStringParam); } else if (param.IsOfKind(CodeParameterKind.QueryParameter)) { - writer.WriteLine($"requestInfo.QueryParameters.Add(\"{param.Name}\", {NormalizeToIdentifier(param.Name)});"); + writer.Write($"requestInfo.QueryParameters.Add(\"{param.Name}\", {paramName});", !isStringParam); } + + writer.WriteLine(); } From 2bb0dad547202dfea1e6f248b1a5c14d33e35b06 Mon Sep 17 00:00:00 2001 From: calebkiage Date: Tue, 2 Nov 2021 14:13:30 +0300 Subject: [PATCH 16/69] Read request body input as json string --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 2 + .../Writers/Shell/ShellCodeMethodWriter.cs | 52 ++++++++++++++++--- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index f898f033ff..d891aaa232 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -238,6 +238,8 @@ parentClass.StartBlock is CodeClass.Declaration parentDeclaration ? "System.CommandLine", "Command", "RootCommand"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.CommandLine.Invocation", "CommandHandler"), + new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), + "System.Text", "Encoding"), }; private static void CapitalizeNamespacesFirstLetters(CodeElement current) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 14b9aa9ea2..09944054fd 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -130,7 +130,10 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req foreach (var option in parametersList) { + var type = option.Type as CodeType; var optionType = conventions.GetTypeString(option.Type, option); + if (option.ParameterKind == CodeParameterKind.RequestBody && type.TypeDefinition is CodeClass) optionType = "string"; + var optionBuilder = new StringBuilder("new Option"); if (!String.IsNullOrEmpty(optionType)) { @@ -153,7 +156,16 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req writer.WriteLine($"command.AddOption({optionBuilder});"); } - var paramTypes = parametersList.Select(x => conventions.GetTypeString(x.Type, x)).Aggregate((x, y) => $"{x}, {y}"); + var paramTypes = parametersList.Select(x => + { + var codeType = x.Type as CodeType; + if (x.ParameterKind == CodeParameterKind.RequestBody && codeType.TypeDefinition is CodeClass) + { + return "string"; + } + + return conventions.GetTypeString(x.Type, x); + }).Aggregate((x, y) => $"{x}, {y}"); var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).Aggregate((x, y) => $"{x}, {y}"); var isHandlerVoid = conventions.VoidTypeName.Equals(originalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); writer.WriteLine($"command.Handler = CommandHandler.Create<{paramTypes}>(async ({paramNames}) => {{"); @@ -167,11 +179,12 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req writer.WriteLine("Console.WriteLine(\"Success\");"); } else { - var contentType = originalMethod.ContentType ?? "application/json"; - writer.WriteLine($"using var serializer = RequestAdapter.SerializationWriterFactory.GetSerializationWriter(\"{contentType}\");"); - var type = originalMethod.ReturnType as CodeType; var typeString = conventions.GetTypeString(type, originalMethod); + var contentType = originalMethod.ContentType ?? "application/json"; + if (typeString != "Stream") + writer.WriteLine($"using var serializer = RequestAdapter.SerializationWriterFactory.GetSerializationWriter(\"{contentType}\");"); + if (type.TypeDefinition is CodeEnum) { if (type.IsCollection) @@ -190,13 +203,17 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req { if (type.IsCollection) writer.WriteLine($"serializer.WriteCollectionOfObjectValues(null, result);"); - else if (typeString == "Stream") - writer.WriteLine($"//serializer.WriteObjectValue(null, result);"); + else if (typeString == "Stream") { } else writer.WriteLine($"serializer.WriteObjectValue(null, result);"); } - writer.WriteLine("using var content = serializer.GetSerializedContent();"); - writer.WriteLine("using var reader = new StreamReader(content);"); + + if (typeString != "Stream") + writer.WriteLine("using var content = serializer.GetSerializedContent();"); + + // Assume string content as stream here + var argName = typeString != "Stream" ? "content" : "result"; + writer.WriteLine($"using var reader = new StreamReader({argName});"); writer.WriteLine("var strContent = await reader.ReadToEndAsync();"); writer.WriteLine("Console.Write(strContent + \"\\n\");"); } @@ -214,6 +231,25 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa var generatorMethod = (codeElement.Parent as CodeClass) .Methods .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); + var requestBodyParam = requestParams.requestBody; + var requestBodyParamType = requestBodyParam?.Type as CodeType; + if (requestBodyParamType?.TypeDefinition is CodeClass) + { + writer.WriteLine($"using var stream = new MemoryStream(Encoding.UTF8.GetBytes({requestBodyParam.Name}));"); + writer.WriteLine("var parseNode = ParseNodeFactoryRegistry.DefaultInstance.GetRootParseNode(\"application/json\", stream);"); + + var typeString = conventions.GetTypeString(requestBodyParamType, requestBodyParam, false); + + if (requestBodyParamType.IsCollection) + { + writer.WriteLine($"var model = parseNode.GetCollectionOfObjectValues<{typeString}>();"); + } else + { + writer.WriteLine($"var model = parseNode.GetObjectValue<{typeString}>();"); + } + + requestBodyParam.Name = "model"; + } var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } .Select(x => x?.Name).Where(x => x != null).DefaultIfEmpty().Aggregate((x, y) => $"{x}, {y}"); writer.WriteLine($"var requestInfo = {generatorMethod?.Name}({parametersList});"); From d65d2478f9aa2d99a9f334c1d2ecb9990456be0d Mon Sep 17 00:00:00 2001 From: calebkiage Date: Wed, 3 Nov 2021 18:10:31 +0300 Subject: [PATCH 17/69] Add file IO support for stream data upload & download --- .../Writers/Shell/ShellCodeMethodWriter.cs | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 09944054fd..943f836044 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -134,6 +134,11 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req var optionType = conventions.GetTypeString(option.Type, option); if (option.ParameterKind == CodeParameterKind.RequestBody && type.TypeDefinition is CodeClass) optionType = "string"; + // Binary body handling + if (option.ParameterKind == CodeParameterKind.RequestBody && conventions.StreamTypeName.Equals(option.Type?.Name, StringComparison.OrdinalIgnoreCase)) { + option.Name = "file"; + } + var optionBuilder = new StringBuilder("new Option"); if (!String.IsNullOrEmpty(optionType)) { @@ -162,15 +167,24 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req if (x.ParameterKind == CodeParameterKind.RequestBody && codeType.TypeDefinition is CodeClass) { return "string"; + } else if (conventions.StreamTypeName.Equals(x.Type?.Name, StringComparison.OrdinalIgnoreCase)) + { + return "FileInfo"; } return conventions.GetTypeString(x.Type, x); }).Aggregate((x, y) => $"{x}, {y}"); var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).Aggregate((x, y) => $"{x}, {y}"); var isHandlerVoid = conventions.VoidTypeName.Equals(originalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); + returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod); + if (conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) + { + writer.WriteLine("command.AddOption(new Option(\"--output\"));"); + paramTypes = $"{paramTypes}, FileInfo"; + paramNames = $"{paramNames}, output"; + } writer.WriteLine($"command.Handler = CommandHandler.Create<{paramTypes}>(async ({paramNames}) => {{"); writer.IncreaseIndent(); - returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod); WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer); // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp writer.WriteLine("// Print request output. What if the request has no return?"); @@ -209,13 +223,25 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req } if (typeString != "Stream") + { writer.WriteLine("using var content = serializer.GetSerializedContent();"); + WriteResponseToConsole(writer, "content"); + } else + { + writer.WriteLine("if (output == null) {"); + writer.IncreaseIndent(); + WriteResponseToConsole(writer, "result"); + writer.CloseBlock(); + writer.WriteLine("else {"); + writer.IncreaseIndent(); + writer.WriteLine("using var stream = output.OpenWrite();"); + writer.WriteLine("await result.CopyToAsync(stream);"); + writer.WriteLine("Console.WriteLine($\"Content written to {output.FullName}.\");"); + writer.CloseBlock(); + } // Assume string content as stream here - var argName = typeString != "Stream" ? "content" : "result"; - writer.WriteLine($"using var reader = new StreamReader({argName});"); - writer.WriteLine("var strContent = await reader.ReadToEndAsync();"); - writer.WriteLine("Console.Write(strContent + \"\\n\");"); + } writer.DecreaseIndent(); writer.WriteLine("});"); @@ -223,6 +249,13 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req } } + private void WriteResponseToConsole(LanguageWriter writer, string argName) + { + writer.WriteLine($"using var reader = new StreamReader({argName});"); + writer.WriteLine("var strContent = await reader.ReadToEndAsync();"); + writer.WriteLine("Console.Write(strContent + \"\\n\");"); + } + protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) { if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); @@ -249,6 +282,11 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa } requestBodyParam.Name = "model"; + } else if (conventions.StreamTypeName.Equals(requestBodyParamType?.Name, StringComparison.OrdinalIgnoreCase)) + { + var name = requestBodyParam.Name; + requestBodyParam.Name = "stream"; + writer.WriteLine($"using var {requestBodyParam.Name} = {name}.OpenRead();"); } var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } .Select(x => x?.Name).Where(x => x != null).DefaultIfEmpty().Aggregate((x, y) => $"{x}, {y}"); From 84b2fc5611250df69eda0c9c3bbd949f706cb779 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 2 Dec 2021 17:37:17 +0300 Subject: [PATCH 18/69] Fix NullReferenceException when there are no parameters --- .../Writers/Shell/ShellCodeMethodWriter.cs | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 943f836044..8adb92e279 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -115,8 +115,7 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req var pathAndQueryParams = generatorMethod.PathAndQueryParameters; var originalMethod = codeElement.OriginalMethod; var origParams = originalMethod.Parameters; - var parametersList = new List(); - parametersList.AddRange(generatorMethod.PathAndQueryParameters.Where(p => p.Name != null)); + var parametersList = pathAndQueryParams?.Where(p => p.Name != null)?.ToList() ?? new List(); if (origParams.Any(p => p.IsOfKind(CodeParameterKind.RequestBody))) { parametersList.Add(origParams.OfKind(CodeParameterKind.RequestBody)); @@ -161,7 +160,7 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req writer.WriteLine($"command.AddOption({optionBuilder});"); } - var paramTypes = parametersList.Select(x => + var paramTypes = parametersList.Any() ? parametersList.Select(x => { var codeType = x.Type as CodeType; if (x.ParameterKind == CodeParameterKind.RequestBody && codeType.TypeDefinition is CodeClass) @@ -173,17 +172,18 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req } return conventions.GetTypeString(x.Type, x); - }).Aggregate((x, y) => $"{x}, {y}"); - var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).Aggregate((x, y) => $"{x}, {y}"); + }).Aggregate((x, y) => $"{x}, {y}") : ""; + var paramNames = parametersList.Any() ? parametersList.Select(x => NormalizeToIdentifier(x.Name)).Aggregate((x, y) => $"{x}, {y}") : ""; var isHandlerVoid = conventions.VoidTypeName.Equals(originalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod); if (conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) { writer.WriteLine("command.AddOption(new Option(\"--output\"));"); - paramTypes = $"{paramTypes}, FileInfo"; - paramNames = $"{paramNames}, output"; + paramTypes = String.Join(paramTypes, "FileInfo"); + paramNames = String.Join(paramNames, "output"); } - writer.WriteLine($"command.Handler = CommandHandler.Create<{paramTypes}>(async ({paramNames}) => {{"); + var genericParameter = paramTypes.Length > 0 ? String.Join("", "<", paramTypes, ">") : ""; + writer.WriteLine($"command.Handler = CommandHandler.Create{genericParameter}(async ({paramNames}) => {{"); writer.IncreaseIndent(); WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer); // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp @@ -211,7 +211,7 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req if (type.IsCollection) writer.WriteLine($"serializer.WriteCollectionOfPrimitiveValues(null, result);"); else - writer.WriteLine($"serializer.Write{typeString.ToFirstCharacterUpperCase()}Value(null, result);"); + writer.WriteLine($"serializer.Write{typeString.ToFirstCharacterUpperCase().Replace("?", "")}Value(null, result);"); } else { @@ -291,20 +291,24 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } .Select(x => x?.Name).Where(x => x != null).DefaultIfEmpty().Aggregate((x, y) => $"{x}, {y}"); writer.WriteLine($"var requestInfo = {generatorMethod?.Name}({parametersList});"); - foreach (var param in generatorMethod.PathAndQueryParameters) + if (generatorMethod.PathAndQueryParameters != null) { - var paramName = NormalizeToIdentifier(param.Name); - bool isStringParam = param.Type.Name?.ToLower() == "string"; - if (isStringParam) writer.Write($"if (!String.IsNullOrEmpty({paramName})) "); - if (param.IsOfKind(CodeParameterKind.Path)) + foreach (var param in generatorMethod.PathAndQueryParameters) { - writer.Write($"requestInfo.PathParameters.Add(\"{param.Name}\", {paramName});", !isStringParam); - } else if (param.IsOfKind(CodeParameterKind.QueryParameter)) - { - writer.Write($"requestInfo.QueryParameters.Add(\"{param.Name}\", {paramName});", !isStringParam); - } + var paramName = NormalizeToIdentifier(param.Name); + bool isStringParam = param.Type.Name?.ToLower() == "string"; + if (isStringParam) writer.Write($"if (!String.IsNullOrEmpty({paramName})) "); + if (param.IsOfKind(CodeParameterKind.Path)) + { + writer.Write($"requestInfo.PathParameters.Add(\"{param.Name}\", {paramName});", !isStringParam); + } + else if (param.IsOfKind(CodeParameterKind.QueryParameter)) + { + writer.Write($"requestInfo.QueryParameters.Add(\"{param.Name}\", {paramName});", !isStringParam); + } - writer.WriteLine(); + writer.WriteLine(); + } } From 9b418e7a92a752f1709ab473768db68d0f8033fc Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 2 Dec 2021 18:28:32 +0300 Subject: [PATCH 19/69] Apply suggested code changes --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 2 +- .../Writers/CSharp/CodeMethodWriter.cs | 3 +-- .../Writers/Shell/ShellCodeMethodWriter.cs | 13 +++++-------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index d35e2275e3..5d055870aa 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -107,7 +107,7 @@ public bool IsSerializationMethod { public string BaseUrl { get; set; } /// - /// This is currently used for CommandBuilder methods to get the originall name without the Build prefix & Command suffix. + /// This is currently used for CommandBuilder methods to get the original name without the Build prefix & Command suffix. /// Avoids regex operations /// public string SimpleName { get; set; } = String.Empty; diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index d306f05a5b..b28ce792e9 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -30,8 +30,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri writer.WriteLine($"_ = {parameterName} ?? throw new ArgumentNullException(nameof({parameterName}));"); } HandleMethodKind(codeElement, writer, inherits, parentClass, isVoid); - writer.DecreaseIndent(); - writer.WriteLine("}"); + writer.CloseBlock(); } protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter writer, bool inherits, CodeClass parentClass, bool isVoid) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 8adb92e279..071298d89c 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -10,9 +10,10 @@ namespace Kiota.Builder.Writers.Shell { class ShellCodeMethodWriter : CodeMethodWriter { - private static Regex delimitedRegex = new Regex("(?<=[a-z])[-_\\.]([A-Za-z])"); - private static Regex camelCaseRegex = new Regex("(?<=[a-z])([A-Z])"); - private static Regex identifierRegex = new Regex("(?:[-_\\.]([a-zA-Z]))"); + private static Regex delimitedRegex = new Regex("(?<=[a-z])[-_\\.]([A-Za-z])", RegexOptions.Compiled); + private static Regex camelCaseRegex = new Regex("(?<=[a-z])([A-Z])", RegexOptions.Compiled); + private static Regex identifierRegex = new Regex("(?:[-_\\.]([a-zA-Z]))", RegexOptions.Compiled); + private static Regex uppercaseRegex = new Regex("([A-Z])", RegexOptions.Compiled); public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(conventionService) { @@ -26,9 +27,6 @@ protected override void HandleMethodKind(CodeMethod codeElement, LanguageWriter var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); var origParams = codeElement.OriginalMethod?.Parameters ?? codeElement.Parameters; var requestBodyParam = origParams.OfKind(CodeParameterKind.RequestBody); - //var queryStringParam = origParams.OfKind(CodeParameterKind.QueryParameter); - //var headersParam = origParams.OfKind(CodeParameterKind.Headers); - //var optionsParam = origParams.OfKind(CodeParameterKind.Options); var requestParams = new RequestParams(requestBodyParam, null, null, null); WriteCommandBuilderBody(codeElement, requestParams, isVoid, returnType, writer); } @@ -38,7 +36,6 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req { var parent = codeElement.Parent as CodeClass; var classMethods = parent.Methods; - var uppercaseRegex = new Regex("([A-Z])"); var name = codeElement.SimpleName; name = uppercaseRegex.Replace(name, "-$1").TrimStart('-').ToLower(); @@ -77,7 +74,7 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req } } else { - CodeType codeReturnType = (codeElement.AccessedProperty?.Type) as CodeType; + var codeReturnType = (codeElement.AccessedProperty?.Type) as CodeType; writer.WriteLine($"var command = new Command(\"{name}\");"); From e567be53d804c2b7ebadf86314a84dbaad0fc842 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 3 Dec 2021 15:04:01 +0300 Subject: [PATCH 20/69] Handle edge cases that broke the generated code build --- .../Writers/Shell/ShellCodeMethodWriter.cs | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 071298d89c..86389c24fa 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -14,6 +14,8 @@ class ShellCodeMethodWriter : CodeMethodWriter private static Regex camelCaseRegex = new Regex("(?<=[a-z])([A-Z])", RegexOptions.Compiled); private static Regex identifierRegex = new Regex("(?:[-_\\.]([a-zA-Z]))", RegexOptions.Compiled); private static Regex uppercaseRegex = new Regex("([A-Z])", RegexOptions.Compiled); + private const string fileParamType = "FileInfo"; + private const string fileParamName = "output"; public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(conventionService) { @@ -61,16 +63,26 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req else if (codeElement.OriginalIndexer != null) { var targetClass = conventions.GetTypeString(codeElement.OriginalIndexer.ReturnType, codeElement); - var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType().Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)).ToList(); + var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType().Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)); + var listBuilderMethods = builderMethods.Where(m => m.ReturnType.IsCollection).ToList(); + var itemBuilderMethods = builderMethods.Where(m => !m.ReturnType.IsCollection).ToList(); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); - writer.WriteLine("return new Command[] { "); + writer.WriteLine("var commands = new List { "); writer.IncreaseIndent(); - for (int i = 0; i < builderMethods.Count; i++) + + foreach (var method in itemBuilderMethods) + { + writer.WriteLine($"builder.{method.Name}(),"); + } + + writer.CloseBlock("};"); + + foreach (var method in listBuilderMethods) { - writer.WriteLine($"builder.{builderMethods[i].Name}(),"); + writer.WriteLine($"commands.AddRange({method.Name}());"); } - writer.DecreaseIndent(); - writer.WriteLine("};"); + + writer.WriteLine("return commands.ToArray();"); } } else { @@ -80,7 +92,10 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req if (codeReturnType != null) { - var targetClass = conventions.GetTypeString(codeReturnType, codeElement); + // Include namespace to avoid type ambiguity on similarly named classes. Currently, if we have namespaces A and A.B where both namespaces have type T, + // Trying to use type A.B.T in namespace A without using the fully qualified name will break the build. + // TODO: Fix this in the refiner. + var targetClass = string.Join(".", codeReturnType.TypeDefinition.Parent.Name, conventions.GetTypeString(codeReturnType, codeElement)); var builderMethods = codeReturnType.TypeDefinition.GetChildElements(true).OfType().Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); @@ -157,7 +172,7 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req writer.WriteLine($"command.AddOption({optionBuilder});"); } - var paramTypes = parametersList.Any() ? parametersList.Select(x => + var paramTypes = parametersList.Select(x => { var codeType = x.Type as CodeType; if (x.ParameterKind == CodeParameterKind.RequestBody && codeType.TypeDefinition is CodeClass) @@ -169,17 +184,17 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req } return conventions.GetTypeString(x.Type, x); - }).Aggregate((x, y) => $"{x}, {y}") : ""; - var paramNames = parametersList.Any() ? parametersList.Select(x => NormalizeToIdentifier(x.Name)).Aggregate((x, y) => $"{x}, {y}") : ""; + }).Aggregate(string.Empty, (x, y) => string.IsNullOrEmpty(x) ? y : $"{x}, {y}"); + var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).Aggregate(string.Empty, (x, y) => string.IsNullOrEmpty(x) ? y : $"{x}, {y}"); var isHandlerVoid = conventions.VoidTypeName.Equals(originalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod); if (conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) { writer.WriteLine("command.AddOption(new Option(\"--output\"));"); - paramTypes = String.Join(paramTypes, "FileInfo"); - paramNames = String.Join(paramNames, "output"); + paramTypes = string.IsNullOrWhiteSpace(paramTypes) ? fileParamType : string.Join(", ", paramTypes, fileParamType); + paramNames = string.IsNullOrWhiteSpace(paramNames) ? fileParamName : string.Join(", ", paramNames, fileParamName); } - var genericParameter = paramTypes.Length > 0 ? String.Join("", "<", paramTypes, ">") : ""; + var genericParameter = paramTypes.Length > 0 ? string.Join("", "<", paramTypes, ">") : ""; writer.WriteLine($"command.Handler = CommandHandler.Create{genericParameter}(async ({paramNames}) => {{"); writer.IncreaseIndent(); WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer); @@ -231,8 +246,8 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req writer.CloseBlock(); writer.WriteLine("else {"); writer.IncreaseIndent(); - writer.WriteLine("using var stream = output.OpenWrite();"); - writer.WriteLine("await result.CopyToAsync(stream);"); + writer.WriteLine("using var writeStream = output.OpenWrite();"); + writer.WriteLine("await result.CopyToAsync(writeStream);"); writer.WriteLine("Console.WriteLine($\"Content written to {output.FullName}.\");"); writer.CloseBlock(); } From 8772b4a46483f095bc7fe3bdefd5ffa6628553cc Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Mon, 6 Dec 2021 16:29:15 +0300 Subject: [PATCH 21/69] Order methods for deterministic output --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 2 +- src/Kiota.Builder/Writers/Php/PhpWriter.cs | 10 +++++----- .../Writers/Shell/ShellCodeMethodWriter.cs | 13 +++++++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index d891aaa232..24cc8097b6 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -115,7 +115,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) }; method.ReturnType = CreateCommandType(method); - method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Complex; currentClass.AddMethod(method); currentClass.RemoveChildElement(indexer); } diff --git a/src/Kiota.Builder/Writers/Php/PhpWriter.cs b/src/Kiota.Builder/Writers/Php/PhpWriter.cs index eabdd71465..5e241dff8f 100644 --- a/src/Kiota.Builder/Writers/Php/PhpWriter.cs +++ b/src/Kiota.Builder/Writers/Php/PhpWriter.cs @@ -6,11 +6,11 @@ public PhpWriter(string rootPath, string clientNamespaceName, bool useBackingSto { PathSegmenter = new PhpPathSegmenter(rootPath, clientNamespaceName); var conventionService = new PhpConventionService(); - AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); - AddCodeElementWriter(new CodePropertyWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService)); - AddCodeElementWriter(new CodeClassEndWriter()); - AddCodeElementWriter(new CodeEnumWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeClassEndWriter()); + AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); } } } diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 86389c24fa..8f875610d2 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -51,7 +51,7 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req // BuildCommand function if (codeElement.OriginalMethod?.MethodKind == CodeMethodKind.ClientConstructor) { - var commandBuilderMethods = classMethods.Where(m => m.MethodKind == CodeMethodKind.CommandBuilder && m != codeElement); + var commandBuilderMethods = classMethods.Where(m => m.MethodKind == CodeMethodKind.CommandBuilder && m != codeElement).OrderBy(m => m.Name); writer.WriteLine($"var command = new RootCommand();"); foreach (var method in commandBuilderMethods) { @@ -63,7 +63,9 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req else if (codeElement.OriginalIndexer != null) { var targetClass = conventions.GetTypeString(codeElement.OriginalIndexer.ReturnType, codeElement); - var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType().Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)); + var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType() + .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) + .OrderBy(m => m.Name); var listBuilderMethods = builderMethods.Where(m => m.ReturnType.IsCollection).ToList(); var itemBuilderMethods = builderMethods.Where(m => !m.ReturnType.IsCollection).ToList(); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); @@ -82,7 +84,7 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req writer.WriteLine($"commands.AddRange({method.Name}());"); } - writer.WriteLine("return commands.ToArray();"); + writer.WriteLine("return commands;"); } } else { @@ -96,7 +98,10 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req // Trying to use type A.B.T in namespace A without using the fully qualified name will break the build. // TODO: Fix this in the refiner. var targetClass = string.Join(".", codeReturnType.TypeDefinition.Parent.Name, conventions.GetTypeString(codeReturnType, codeElement)); - var builderMethods = codeReturnType.TypeDefinition.GetChildElements(true).OfType().Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)); + var builderMethods = codeReturnType.TypeDefinition.GetChildElements(true).OfType() + .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) + .OrderBy(m => m.Name) + .ThenBy(m => m.ReturnType.IsCollection); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); foreach (var method in builderMethods) From 0ba0e0906f6556f56e4e9a75df6f81d24112d4d8 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Mon, 6 Dec 2021 18:16:36 +0300 Subject: [PATCH 22/69] Add missing import --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 24cc8097b6..50149c5837 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -225,6 +225,8 @@ parentClass.StartBlock is CodeClass.Declaration parentDeclaration ? "System.Collections.Generic", "List", "Dictionary"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model, CodeClassKind.RequestBuilder), "System.IO", "Stream"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), + "System.Threading", "CancellationToken"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.Threading.Tasks", "Task"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model, CodeClassKind.RequestBuilder), From b23dc85d168536202c5045bf2e0fc42602791bdf Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Mon, 6 Dec 2021 18:17:31 +0300 Subject: [PATCH 23/69] Throw exception for attempts to use CommandBuilders in CSharp --- src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index d0cd468247..c0194e0dcb 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -72,7 +72,7 @@ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter w case CodeMethodKind.RequestBuilderBackwardCompatibility: throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties."); case CodeMethodKind.CommandBuilder: - break; + throw new InvalidOperationException("CommandBuilder methods are not implemented in this SDK. They're currently only supported in the shell language."); default: writer.WriteLine("return null;"); break; From 1d0f3c3a6f286fc87001b46f57a7d74b42618087 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Tue, 7 Dec 2021 14:16:03 +0300 Subject: [PATCH 24/69] Fix bug with command builder writer Add command descriptions to generated code --- .../Writers/CSharp/CodeMethodWriter.cs | 11 +++++++++- .../Writers/Shell/ShellCodeMethodWriter.cs | 20 ++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index c0194e0dcb..f34f6f8188 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -72,7 +72,11 @@ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter w case CodeMethodKind.RequestBuilderBackwardCompatibility: throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties."); case CodeMethodKind.CommandBuilder: - throw new InvalidOperationException("CommandBuilder methods are not implemented in this SDK. They're currently only supported in the shell language."); + var origParams = codeElement.OriginalMethod?.Parameters ?? codeElement.Parameters; + requestBodyParam = origParams.OfKind(CodeParameterKind.RequestBody); + requestParams = new RequestParams(requestBodyParam, null, null, null); + WriteCommandBuilderBody(codeElement, requestParams, isVoid, returnType, writer); + break; default: writer.WriteLine("return null;"); break; @@ -238,6 +242,11 @@ private void WriteSerializerBody(bool shouldHide, CodeMethod method, CodeClass p if(additionalDataProperty != null) writer.WriteLine($"writer.WriteAdditionalData({additionalDataProperty.Name});"); } + + protected virtual void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) + { + throw new InvalidOperationException("CommandBuilder methods are not implemented in this SDK. They're currently only supported in the shell language."); + } protected string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) { if(isVoid) return "SendNoContentAsync"; else if(isStream || conventions.IsPrimitiveType(returnType)) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 8f875610d2..85532c4778 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -21,20 +21,7 @@ public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(c { } - protected override void HandleMethodKind(CodeMethod codeElement, LanguageWriter writer, bool inherits, CodeClass parentClass, bool isVoid) - { - base.HandleMethodKind(codeElement, writer, inherits, parentClass, isVoid); - if (codeElement.MethodKind == CodeMethodKind.CommandBuilder) - { - var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); - var origParams = codeElement.OriginalMethod?.Parameters ?? codeElement.Parameters; - var requestBodyParam = origParams.OfKind(CodeParameterKind.RequestBody); - var requestParams = new RequestParams(requestBodyParam, null, null, null); - WriteCommandBuilderBody(codeElement, requestParams, isVoid, returnType, writer); - } - } - - protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) + protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) { var parent = codeElement.Parent as CodeClass; var classMethods = parent.Methods; @@ -53,6 +40,7 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req { var commandBuilderMethods = classMethods.Where(m => m.MethodKind == CodeMethodKind.CommandBuilder && m != codeElement).OrderBy(m => m.Name); writer.WriteLine($"var command = new RootCommand();"); + writer.WriteLine($"command.Description = \"{codeElement.OriginalMethod.Description}\";"); foreach (var method in commandBuilderMethods) { writer.WriteLine($"command.AddCommand({method.Name}());"); @@ -91,6 +79,8 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req var codeReturnType = (codeElement.AccessedProperty?.Type) as CodeType; writer.WriteLine($"var command = new Command(\"{name}\");"); + if (codeElement.Description != null || codeElement?.OriginalMethod?.Description != null) + writer.WriteLine($"command.Description = \"{codeElement.Description ?? codeElement?.OriginalMethod?.Description}\";"); if (codeReturnType != null) { @@ -138,6 +128,8 @@ protected void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams req parametersList.Add(origParams.OfKind(CodeParameterKind.RequestBody)); } writer.WriteLine($"var command = new Command(\"{name}\");"); + if (codeElement.Description != null || codeElement?.OriginalMethod?.Description != null) + writer.WriteLine($"command.Description = \"{codeElement.Description ?? codeElement?.OriginalMethod?.Description}\";"); writer.WriteLine("// Create options for all the parameters"); // investigate exploding query params // Check the possible formatting options for headers in a cli. // -h A=b -h From a3ea49b0224a2ec5bbbcccfa9aae67ff989a397b Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Tue, 7 Dec 2021 16:10:47 +0300 Subject: [PATCH 25/69] Fix stack overflow exception in builder methods --- .../Writers/Shell/ShellCodeMethodWriter.cs | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 85532c4778..96a1816433 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -54,8 +54,8 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType() .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) .OrderBy(m => m.Name); - var listBuilderMethods = builderMethods.Where(m => m.ReturnType.IsCollection).ToList(); - var itemBuilderMethods = builderMethods.Where(m => !m.ReturnType.IsCollection).ToList(); + // Filter out list builder commands. They contain the item builder commands already + var itemBuilderMethods = builderMethods.Where(m => !m.ReturnType.IsCollection); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); writer.WriteLine("var commands = new List { "); writer.IncreaseIndent(); @@ -67,11 +67,6 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP writer.CloseBlock("};"); - foreach (var method in listBuilderMethods) - { - writer.WriteLine($"commands.AddRange({method.Name}());"); - } - writer.WriteLine("return commands;"); } } else @@ -92,21 +87,13 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) .OrderBy(m => m.Name) .ThenBy(m => m.ReturnType.IsCollection); + // Filter out list builder commands. They contain the item builder commands already + var itemBuilderMethods = builderMethods.Where(m => !m.ReturnType.IsCollection); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); - foreach (var method in builderMethods) + foreach (var method in itemBuilderMethods) { - if (method.ReturnType.IsCollection) - { - writer.WriteLine($"foreach (var cmd in builder.{method.Name}()) {{"); - writer.IncreaseIndent(); - writer.WriteLine("command.AddCommand(cmd);"); - writer.DecreaseIndent(); - writer.WriteLine("}"); - } else - { - writer.WriteLine($"command.AddCommand(builder.{method.Name}());"); - } + writer.WriteLine($"command.AddCommand(builder.{method.Name}());"); } // SubCommands } From 7d6745fcd67195a6b9ae458e7fb4355bd3cdcd22 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 8 Dec 2021 14:32:04 -0500 Subject: [PATCH 26/69] - fixes a bug where path and query parameters of type collection would not be sdescribed accurately Signed-off-by: Vincent Biret --- src/Kiota.Builder/KiotaBuilder.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 79628065d5..16a75b9682 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -612,7 +612,7 @@ private static void SetPathAndQueryParameters(CodeMethod target, OpenApiUrlTreeN .Where(x => x.In == ParameterLocation.Path || x.In == ParameterLocation.Query) .Select(x => new CodeParameter{ Name = x.Name.TrimStart('$').SanitizePathParameterName(), - Type = GetPrimitiveType(x.Schema), + Type = GetQueryParameterType(x.Schema), Description = x.Description, ParameterKind = x.In == ParameterLocation.Path ? CodeParameterKind.Path : CodeParameterKind.QueryParameter, Optional = !x.Required @@ -622,7 +622,7 @@ private static void SetPathAndQueryParameters(CodeMethod target, OpenApiUrlTreeN .Where(x => x.In == ParameterLocation.Path || x.In == ParameterLocation.Query) .Select(x => new CodeParameter{ Name = x.Name.TrimStart('$').SanitizePathParameterName(), - Type = GetPrimitiveType(x.Schema), + Type = GetQueryParameterType(x.Schema), Description = x.Description, ParameterKind = x.In == ParameterLocation.Path ? CodeParameterKind.Path : CodeParameterKind.QueryParameter, Optional = !x.Required @@ -958,12 +958,7 @@ private CodeClass CreateOperationParameter(OpenApiUrlTreeNode node, OperationTyp { Name = FixQueryParameterIdentifier(parameter), Description = parameter.Description, - Type = new CodeType - { - IsExternal = true, - Name = parameter.Schema.Items?.Type ?? parameter.Schema.Type, - CollectionKind = parameter.Schema.IsArray() ? CodeType.CodeTypeCollectionKind.Array : default, - }, + Type = GetQueryParameterType(parameter.Schema), }; if (!parameterClass.ContainsMember(parameter.Name)) @@ -979,6 +974,13 @@ private CodeClass CreateOperationParameter(OpenApiUrlTreeNode node, OperationTyp return parameterClass; } else return null; } + private static CodeType GetQueryParameterType(OpenApiSchema schema) => + new () + { + IsExternal = true, + Name = schema.Items?.Type ?? schema.Type, + CollectionKind = schema.IsArray() ? CodeType.CodeTypeCollectionKind.Array : default, + }; private static string FixQueryParameterIdentifier(OpenApiParameter parameter) { From db176bee0b700410d3f5aca447e0f04914453b9c Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 9 Dec 2021 03:35:30 +0300 Subject: [PATCH 27/69] Update src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs Co-authored-by: Vincent Biret --- src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 96a1816433..48cdae9243 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -74,7 +74,7 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP var codeReturnType = (codeElement.AccessedProperty?.Type) as CodeType; writer.WriteLine($"var command = new Command(\"{name}\");"); - if (codeElement.Description != null || codeElement?.OriginalMethod?.Description != null) + if (!string.IsNullOrEmpty(codeElement.Description) || !string.IsNullOrEmpty(codeElement?.OriginalMethod?.Description)) writer.WriteLine($"command.Description = \"{codeElement.Description ?? codeElement?.OriginalMethod?.Description}\";"); if (codeReturnType != null) From 8e7236ffafc307c5969ef9e0059e90b3093b7dfb Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 9 Dec 2021 03:37:28 +0300 Subject: [PATCH 28/69] Add required flag to options --- .../Writers/Shell/ShellCodeMethodWriter.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 96a1816433..7afc44b27e 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -153,7 +153,10 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP } optionBuilder.Append(')'); - writer.WriteLine($"command.AddOption({optionBuilder});"); + writer.WriteLine($"var {NormalizeToIdentifier(option.Name)}Option = {optionBuilder};"); + var isRequired = $"{!option.Optional || option.IsOfKind(CodeParameterKind.Path)}".ToFirstCharacterLowerCase(); + writer.WriteLine($"{NormalizeToIdentifier(option.Name)}Option.IsRequired = {isRequired};"); + writer.WriteLine($"command.AddOption({NormalizeToIdentifier(option.Name)}Option);"); } var paramTypes = parametersList.Select(x => @@ -293,14 +296,20 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa { var paramName = NormalizeToIdentifier(param.Name); bool isStringParam = param.Type.Name?.ToLower() == "string"; - if (isStringParam) writer.Write($"if (!String.IsNullOrEmpty({paramName})) "); + bool indentParam = true; + if (isStringParam) + { + writer.Write($"if (!String.IsNullOrEmpty({paramName})) "); + indentParam = false; + } + if (param.IsOfKind(CodeParameterKind.Path)) { - writer.Write($"requestInfo.PathParameters.Add(\"{param.Name}\", {paramName});", !isStringParam); + writer.Write($"requestInfo.PathParameters.Add(\"{param.Name}\", {paramName});", indentParam); } else if (param.IsOfKind(CodeParameterKind.QueryParameter)) { - writer.Write($"requestInfo.QueryParameters.Add(\"{param.Name}\", {paramName});", !isStringParam); + writer.Write($"requestInfo.QueryParameters.Add(\"{param.Name}\", {paramName});", indentParam); } writer.WriteLine(); From af989d12033d996bd7ec89c83ea44bcc6808b4fa Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 9 Dec 2021 15:28:28 +0300 Subject: [PATCH 29/69] Use generator method to add query parameters to request --- .../Writers/Shell/ShellCodeMethodWriter.cs | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index b39a2b69c9..83ab3c8a51 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -154,8 +154,14 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP optionBuilder.Append(')'); writer.WriteLine($"var {NormalizeToIdentifier(option.Name)}Option = {optionBuilder};"); - var isRequired = $"{!option.Optional || option.IsOfKind(CodeParameterKind.Path)}".ToFirstCharacterLowerCase(); - writer.WriteLine($"{NormalizeToIdentifier(option.Name)}Option.IsRequired = {isRequired};"); + var isRequired = !option.Optional || option.IsOfKind(CodeParameterKind.Path); + writer.WriteLine($"{NormalizeToIdentifier(option.Name)}Option.IsRequired = {isRequired.ToString().ToFirstCharacterLowerCase()};"); + + if (option.Type.IsCollection) + { + var arity = isRequired ? "OneOrMore" : "ZeroOrMore"; + writer.WriteLine($"{NormalizeToIdentifier(option.Name)}Option.Arity = ArgumentArity.{arity};"); + } writer.WriteLine($"command.AddOption({NormalizeToIdentifier(option.Name)}Option);"); } @@ -289,13 +295,15 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa } var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } .Select(x => x?.Name).Where(x => x != null).DefaultIfEmpty().Aggregate((x, y) => $"{x}, {y}"); - writer.WriteLine($"var requestInfo = {generatorMethod?.Name}({parametersList});"); + var separator = string.IsNullOrWhiteSpace(parametersList) ? "" : ", "; + writer.WriteLine($"var requestInfo = {generatorMethod?.Name}({parametersList}{separator}q => {{"); if (generatorMethod.PathAndQueryParameters != null) { - foreach (var param in generatorMethod.PathAndQueryParameters) + writer.IncreaseIndent(); + foreach (var param in generatorMethod.PathAndQueryParameters.Where(p => p.IsOfKind(CodeParameterKind.QueryParameter))) { var paramName = NormalizeToIdentifier(param.Name); - bool isStringParam = param.Type.Name?.ToLower() == "string"; + bool isStringParam = param.Type.Name?.ToLower() == "string" && !param.Type.IsCollection; bool indentParam = true; if (isStringParam) { @@ -303,19 +311,22 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa indentParam = false; } - if (param.IsOfKind(CodeParameterKind.Path)) - { - writer.Write($"requestInfo.PathParameters.Add(\"{param.Name}\", {paramName});", indentParam); - } - else if (param.IsOfKind(CodeParameterKind.QueryParameter)) - { - writer.Write($"requestInfo.QueryParameters.Add(\"{param.Name}\", {paramName});", indentParam); - } + writer.Write($"q.{param.Name.ToFirstCharacterUpperCase()} = {paramName};", indentParam); writer.WriteLine(); } - } + writer.CloseBlock("});"); + foreach (var param in generatorMethod.PathAndQueryParameters.Where(p => p.IsOfKind(CodeParameterKind.PathParameters))) + { + var paramName = NormalizeToIdentifier(param.Name); + writer.WriteLine($"requestInfo.PathParameters.Add(\"{param.Name}\", {paramName});"); + } + } + else + { + writer.WriteLine("});"); + } writer.WriteLine($"{(isVoid ? string.Empty : "var result = ")}await RequestAdapter.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo);"); } From a0b4c3ad1e09a834b986571a5cdaf62e8dba36a2 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 19 Jan 2022 18:15:59 +0300 Subject: [PATCH 30/69] Update shell writer to accomodate breaking changes in System.Commandline. See https://github.com/dotnet/command-line-api/issues/1537 --- .../Writers/Shell/ShellCodeMethodWriter.cs | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 83ab3c8a51..fe0ce34f70 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -122,10 +122,11 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP // -h A=b -h // -h A:B,B:C // -h {"A": "B"} - + var availableOptions = new List(); foreach (var option in parametersList) { var type = option.Type as CodeType; + var optionName = $"{NormalizeToIdentifier(option.Name)}Option"; var optionType = conventions.GetTypeString(option.Type, option); if (option.ParameterKind == CodeParameterKind.RequestBody && type.TypeDefinition is CodeClass) optionType = "string"; @@ -152,17 +153,23 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP optionBuilder.Append($", description: \"{option.Description}\""); } - optionBuilder.Append(')'); - writer.WriteLine($"var {NormalizeToIdentifier(option.Name)}Option = {optionBuilder};"); + optionBuilder.Append(") {"); + var strValue = $"{optionBuilder}"; + writer.WriteLine($"var {optionName} = {strValue}"); + writer.IncreaseIndent(); var isRequired = !option.Optional || option.IsOfKind(CodeParameterKind.Path); - writer.WriteLine($"{NormalizeToIdentifier(option.Name)}Option.IsRequired = {isRequired.ToString().ToFirstCharacterLowerCase()};"); if (option.Type.IsCollection) { var arity = isRequired ? "OneOrMore" : "ZeroOrMore"; - writer.WriteLine($"{NormalizeToIdentifier(option.Name)}Option.Arity = ArgumentArity.{arity};"); + writer.WriteLine($"Arity = ArgumentArity.{arity}"); } - writer.WriteLine($"command.AddOption({NormalizeToIdentifier(option.Name)}Option);"); + + writer.DecreaseIndent(); + writer.WriteLine("};"); + writer.WriteLine($"{optionName}.IsRequired = {isRequired.ToString().ToFirstCharacterLowerCase()};"); + writer.WriteLine($"command.AddOption({optionName});"); + availableOptions.Add(optionName); } var paramTypes = parametersList.Select(x => @@ -177,18 +184,23 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP } return conventions.GetTypeString(x.Type, x); - }).Aggregate(string.Empty, (x, y) => string.IsNullOrEmpty(x) ? y : $"{x}, {y}"); - var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).Aggregate(string.Empty, (x, y) => string.IsNullOrEmpty(x) ? y : $"{x}, {y}"); + }).ToList(); + var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).ToList(); var isHandlerVoid = conventions.VoidTypeName.Equals(originalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod); if (conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) { - writer.WriteLine("command.AddOption(new Option(\"--output\"));"); - paramTypes = string.IsNullOrWhiteSpace(paramTypes) ? fileParamType : string.Join(", ", paramTypes, fileParamType); - paramNames = string.IsNullOrWhiteSpace(paramNames) ? fileParamName : string.Join(", ", paramNames, fileParamName); + var fileOptionName = "outputOption"; + writer.WriteLine($"var {fileOptionName} = new Option(\"--output\");"); + writer.WriteLine($"command.AddOption({fileOptionName});"); + paramTypes.Add(fileParamType); + paramNames.Add(fileParamName); + availableOptions.Add(fileOptionName); } - var genericParameter = paramTypes.Length > 0 ? string.Join("", "<", paramTypes, ">") : ""; - writer.WriteLine($"command.Handler = CommandHandler.Create{genericParameter}(async ({paramNames}) => {{"); + var zipped = paramTypes.Zip(paramNames); + var projected = zipped.Select((x, y) => $"{x.First} {x.Second}"); + var handlerParams = string.Join(", ", projected); + writer.WriteLine($"command.SetHandler(async ({handlerParams}) => {{"); writer.IncreaseIndent(); WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer); // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp @@ -249,7 +261,11 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP } writer.DecreaseIndent(); - writer.WriteLine("});"); + var delimiter = ""; + if (availableOptions.Any()) { + delimiter = ", "; + } + writer.WriteLine($"}}{delimiter}{string.Join(", ", availableOptions)});"); writer.WriteLine("return command;"); } } From 7f19244014e329080f641dd751ed0a3125afccef Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Tue, 25 Jan 2022 20:57:43 +0300 Subject: [PATCH 31/69] Update method writer to use output formatter --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 4 +- .../Writers/Shell/ShellCodeMethodWriter.cs | 79 ++++++++++--------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 50149c5837..cc2db28f6f 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -239,7 +239,9 @@ parentClass.StartBlock is CodeClass.Declaration parentDeclaration ? new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.CommandLine", "Command", "RootCommand"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), - "System.CommandLine.Invocation", "CommandHandler"), + "Microsoft.Graph.Cli.Core.IO", "OutputFormatterFactory"), + new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), + "System.Net.Http", "HttpResponseMessage"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.Text", "Encoding"), }; diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index fe0ce34f70..dd768a67e1 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -14,8 +14,12 @@ class ShellCodeMethodWriter : CodeMethodWriter private static Regex camelCaseRegex = new Regex("(?<=[a-z])([A-Z])", RegexOptions.Compiled); private static Regex identifierRegex = new Regex("(?:[-_\\.]([a-zA-Z]))", RegexOptions.Compiled); private static Regex uppercaseRegex = new Regex("([A-Z])", RegexOptions.Compiled); + private const string consoleParamType = "IConsole"; + private const string consoleParamName = "console"; private const string fileParamType = "FileInfo"; - private const string fileParamName = "output"; + private const string fileParamName = "file"; + private const string outputFormatParamType = "FormatterType"; + private const string outputFormatParamName = "output"; public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(conventionService) { @@ -190,70 +194,71 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod); if (conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) { - var fileOptionName = "outputOption"; - writer.WriteLine($"var {fileOptionName} = new Option(\"--output\");"); + var fileOptionName = "fileOption"; + writer.WriteLine($"var {fileOptionName} = new Option<{fileParamType}>(\"--{fileParamName}\");"); writer.WriteLine($"command.AddOption({fileOptionName});"); paramTypes.Add(fileParamType); paramNames.Add(fileParamName); availableOptions.Add(fileOptionName); } + + // Add output type param + if (!isHandlerVoid) + { + var outputOptionName = "outputOption"; + writer.WriteLine($"var {outputOptionName} = new Option<{outputFormatParamType}>(\"--{outputFormatParamName}\", () => FormatterType.JSON){{"); + writer.IncreaseIndent(); + writer.WriteLine("IsRequired = true"); + writer.CloseBlock("};"); + writer.WriteLine($"command.AddOption({outputOptionName});"); + paramTypes.Add(outputFormatParamType); + paramNames.Add(outputFormatParamName); + availableOptions.Add(outputOptionName); + } + + // Add console param + paramTypes.Add(consoleParamType); + paramNames.Add(consoleParamName); var zipped = paramTypes.Zip(paramNames); var projected = zipped.Select((x, y) => $"{x.First} {x.Second}"); var handlerParams = string.Join(", ", projected); writer.WriteLine($"command.SetHandler(async ({handlerParams}) => {{"); writer.IncreaseIndent(); + writer.WriteLine("var responseHandler = new NativeResponseHandler();"); WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer); // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp writer.WriteLine("// Print request output. What if the request has no return?"); if (isHandlerVoid) { - writer.WriteLine("Console.WriteLine(\"Success\");"); + writer.WriteLine($"{consoleParamName}.WriteLine(\"Success\");"); } else { var type = originalMethod.ReturnType as CodeType; var typeString = conventions.GetTypeString(type, originalMethod); var contentType = originalMethod.ContentType ?? "application/json"; + writer.WriteLine("var response = responseHandler.Value as HttpResponseMessage;"); + writer.WriteLine($"var formatter = OutputFormatterFactory.Instance.GetFormatter({outputFormatParamName});"); if (typeString != "Stream") - writer.WriteLine($"using var serializer = RequestAdapter.SerializationWriterFactory.GetSerializationWriter(\"{contentType}\");"); - - if (type.TypeDefinition is CodeEnum) - { - if (type.IsCollection) - writer.WriteLine($"serializer.WriteCollectionOfEnumValues(null, result);"); - else - writer.WriteLine($"serializer.WriteEnumValue(null, result);"); - } - else if (conventions.IsPrimitiveType(typeString)) { - if (type.IsCollection) - writer.WriteLine($"serializer.WriteCollectionOfPrimitiveValues(null, result);"); - else - writer.WriteLine($"serializer.Write{typeString.ToFirstCharacterUpperCase().Replace("?", "")}Value(null, result);"); - } - else + writer.WriteLine("var content = await response.Content.ReadAsStringAsync();"); + } else { - if (type.IsCollection) - writer.WriteLine($"serializer.WriteCollectionOfObjectValues(null, result);"); - else if (typeString == "Stream") { } - else - writer.WriteLine($"serializer.WriteObjectValue(null, result);"); + writer.WriteLine("var content = await response.Content.ReadAsStreamAsync();"); } - if (typeString != "Stream") - { - writer.WriteLine("using var content = serializer.GetSerializedContent();"); - WriteResponseToConsole(writer, "content"); - } else + writer.WriteLine($"formatter.WriteOutput(content, {consoleParamName});"); + + if (typeString == "Stream") { - writer.WriteLine("if (output == null) {"); + writer.WriteLine($"if ({fileParamName} == null) {{"); writer.IncreaseIndent(); - WriteResponseToConsole(writer, "result"); + WriteResponseToConsole(writer, "content"); writer.CloseBlock(); writer.WriteLine("else {"); writer.IncreaseIndent(); - writer.WriteLine("using var writeStream = output.OpenWrite();"); - writer.WriteLine("await result.CopyToAsync(writeStream);"); - writer.WriteLine("Console.WriteLine($\"Content written to {output.FullName}.\");"); + writer.WriteLine($"using var writeStream = {fileParamName}.OpenWrite();"); + writer.WriteLine("await content.CopyToAsync(writeStream);"); + writer.WriteLine($"{consoleParamName}.WriteLine($\"Content written to {{{fileParamName}.FullName}}.\");"); writer.CloseBlock(); } @@ -274,7 +279,7 @@ private void WriteResponseToConsole(LanguageWriter writer, string argName) { writer.WriteLine($"using var reader = new StreamReader({argName});"); writer.WriteLine("var strContent = await reader.ReadToEndAsync();"); - writer.WriteLine("Console.Write(strContent + \"\\n\");"); + writer.WriteLine($"{consoleParamName}.WriteLine(strContent);"); } protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) @@ -344,7 +349,7 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa writer.WriteLine("});"); } - writer.WriteLine($"{(isVoid ? string.Empty : "var result = ")}await RequestAdapter.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo);"); + writer.WriteLine($"await RequestAdapter.SendNoContentAsync(requestInfo, responseHandler);"); } /// From 0610a1fca42362196db44ba00d89bb5fb5fc8069 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 26 Jan 2022 19:45:55 +0300 Subject: [PATCH 32/69] Fix duplicated output in generated code for streams --- .../Writers/Shell/ShellCodeMethodWriter.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index dd768a67e1..826d98262b 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -241,18 +241,13 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP if (typeString != "Stream") { writer.WriteLine("var content = await response.Content.ReadAsStringAsync();"); + writer.WriteLine($"formatter.WriteOutput(content, {consoleParamName});"); } else { writer.WriteLine("var content = await response.Content.ReadAsStreamAsync();"); - } - - writer.WriteLine($"formatter.WriteOutput(content, {consoleParamName});"); - - if (typeString == "Stream") - { writer.WriteLine($"if ({fileParamName} == null) {{"); writer.IncreaseIndent(); - WriteResponseToConsole(writer, "content"); + writer.WriteLine($"formatter.WriteOutput(content, {consoleParamName});"); writer.CloseBlock(); writer.WriteLine("else {"); writer.IncreaseIndent(); @@ -275,13 +270,6 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP } } - private void WriteResponseToConsole(LanguageWriter writer, string argName) - { - writer.WriteLine($"using var reader = new StreamReader({argName});"); - writer.WriteLine("var strContent = await reader.ReadToEndAsync();"); - writer.WriteLine($"{consoleParamName}.WriteLine(strContent);"); - } - protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) { if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); From 839c8c0d82cf4d36eebfced69e0597f2dffed7ee Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 27 Jan 2022 04:25:47 +0300 Subject: [PATCH 33/69] Fix issue with some commands being skipped. Reuse code in CSharpRefiner. --- src/Kiota.Builder/Refiners/CSharpRefiner.cs | 8 +- src/Kiota.Builder/Refiners/ShellRefiner.cs | 110 +++--------------- .../Writers/Shell/ShellCodeMethodWriter.cs | 27 +++-- 3 files changed, 36 insertions(+), 109 deletions(-) diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs index 1f5d0c3a00..d168583768 100644 --- a/src/Kiota.Builder/Refiners/CSharpRefiner.cs +++ b/src/Kiota.Builder/Refiners/CSharpRefiner.cs @@ -33,7 +33,7 @@ public override void Refine(CodeNamespace generatedCode) AddConstructorsForDefaultValues(generatedCode, false); AddSerializationModulesImport(generatedCode); } - private static void DisambiguatePropertiesWithClassNames(CodeElement currentElement) { + protected static void DisambiguatePropertiesWithClassNames(CodeElement currentElement) { if(currentElement is CodeClass currentClass) { var sameNameProperty = currentClass.Properties .FirstOrDefault(x => x.Name.Equals(currentClass.Name, StringComparison.OrdinalIgnoreCase)); @@ -46,7 +46,7 @@ private static void DisambiguatePropertiesWithClassNames(CodeElement currentElem } CrawlTree(currentElement, DisambiguatePropertiesWithClassNames); } - private static void MakeEnumPropertiesNullable(CodeElement currentElement) { + protected static void MakeEnumPropertiesNullable(CodeElement currentElement) { if(currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model)) currentClass.Properties .Where(x => x.Type is CodeType propType && propType.TypeDefinition is CodeEnum) @@ -88,12 +88,12 @@ private static void MakeEnumPropertiesNullable(CodeElement currentElement) { new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore), "Microsoft.Kiota.Abstractions.Store", "IBackingStore", "IBackedModel", "BackingStoreFactorySingleton" ), }; - private static void CapitalizeNamespacesFirstLetters(CodeElement current) { + protected static void CapitalizeNamespacesFirstLetters(CodeElement current) { if(current is CodeNamespace currentNamespace) currentNamespace.Name = currentNamespace.Name?.Split('.')?.Select(x => x.ToFirstCharacterUpperCase())?.Aggregate((x, y) => $"{x}.{y}"); CrawlTree(current, CapitalizeNamespacesFirstLetters); } - private static void AddAsyncSuffix(CodeElement currentElement) { + protected static void AddAsyncSuffix(CodeElement currentElement) { if(currentElement is CodeMethod currentMethod && currentMethod.IsAsync) currentMethod.Name += "Async"; CrawlTree(currentElement, AddAsyncSuffix); diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 50149c5837..46e8b59020 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -7,86 +7,36 @@ namespace Kiota.Builder.Refiners { - public class ShellRefiner : CommonLanguageRefiner, ILanguageRefiner + public class ShellRefiner : CSharpRefiner, ILanguageRefiner { public ShellRefiner(GenerationConfiguration configuration) : base(configuration) { } public override void Refine(CodeNamespace generatedCode) { - // Remove PathSegment field - // Convert Properties to AddCommand AddDefaultImports(generatedCode, defaultUsingEvaluators); MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore); + AddRawUrlConstructorOverload(generatedCode); AddPropertiesAndMethodTypesImports(generatedCode, false, false, false); CreateCommandBuilders(generatedCode); AddAsyncSuffix(generatedCode); AddInnerClasses(generatedCode, false); - AddParsableInheritanceForModelClasses(generatedCode); + AddParsableInheritanceForModelClasses(generatedCode, "IParsable"); CapitalizeNamespacesFirstLetters(generatedCode); ReplaceBinaryByNativeType(generatedCode, "Stream", "System.IO"); MakeEnumPropertiesNullable(generatedCode); - ReplaceReservedNames(generatedCode, new CSharpReservedNamesProvider(), x => $"@{x.ToFirstCharacterUpperCase()}"); + /* Exclude the following as their names will be capitalized making the change unnecessary in this case sensitive language + * code classes, class declarations, property names, using declarations, namespace names + * Exclude CodeMethod as the return type will also be capitalized (excluding the CodeType is not enough since this is evaluated at the code method level) + */ + ReplaceReservedNames( + generatedCode, + new CSharpReservedNamesProvider(), x => $"@{x.ToFirstCharacterUpperCase()}", + new HashSet { typeof(CodeClass), typeof(CodeClass.Declaration), typeof(CodeProperty), typeof(CodeUsing), typeof(CodeNamespace), typeof(CodeMethod) } + ); DisambiguatePropertiesWithClassNames(generatedCode); AddConstructorsForDefaultValues(generatedCode, false); AddSerializationModulesImport(generatedCode); } - private static void DisambiguatePropertiesWithClassNames(CodeElement currentElement) - { - if (currentElement is CodeClass currentClass) - { - var sameNameProperty = currentClass - .GetChildElements(true) - .OfType() - .FirstOrDefault(x => x.Name.Equals(currentClass.Name, StringComparison.OrdinalIgnoreCase)); - if (sameNameProperty != null) - { - currentClass.RemoveChildElement(sameNameProperty); - sameNameProperty.SerializationName ??= sameNameProperty.Name; - sameNameProperty.Name = $"{sameNameProperty.Name}_prop"; - currentClass.AddProperty(sameNameProperty); - } - } - CrawlTree(currentElement, DisambiguatePropertiesWithClassNames); - } - private static void MakeEnumPropertiesNullable(CodeElement currentElement) - { - if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model)) - currentClass.GetChildElements(true) - .OfType() - .Where(x => x.Type is CodeType propType && propType.TypeDefinition is CodeEnum) - .ToList() - .ForEach(x => x.Type.IsNullable = true); - CrawlTree(currentElement, MakeEnumPropertiesNullable); - } - private static void RemoveModelClasses(CodeElement currentElement) - { - if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model)) - { - var codeNamespace = currentClass.Parent as CodeNamespace; - codeNamespace.RemoveChildElement(currentClass); - } - CrawlTree(currentElement, RemoveModelClasses); - } - - private static void RemoveEnums(CodeElement currentElement) - { - if (currentElement is CodeEnum currentEnum) - { - var codeNamespace = currentElement.Parent as CodeNamespace; - codeNamespace.RemoveChildElement(currentEnum); - } - CrawlTree(currentElement, RemoveEnums); - } - - private static void RemoveConstructors(CodeElement currentElement) - { - if (currentElement is CodeMethod currentMethod && currentMethod.IsOfKind(CodeMethodKind.Constructor)) - { - var codeClass = currentElement.Parent as CodeClass; - codeClass.RemoveChildElement(currentMethod); - } - CrawlTree(currentElement, RemoveConstructors); - } private static void CreateCommandBuilders(CodeElement currentElement) { @@ -181,29 +131,6 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod return codeMethod; } - private static void AddParsableInheritanceForModelClasses(CodeElement currentElement) - { - if (currentElement is CodeClass currentClass && - currentClass.IsOfKind(CodeClassKind.Model) && - currentClass.StartBlock is CodeClass.Declaration declaration) - { - declaration.AddImplements(new CodeType - { - IsExternal = true, - Name = $"IParsable", - }); - (currentClass.Parent is CodeClass parentClass && - parentClass.StartBlock is CodeClass.Declaration parentDeclaration ? - parentDeclaration : - declaration) - .AddUsings(new CodeUsing - { - Name = "Microsoft.Kiota.Abstractions.Serialization" - }); - } - CrawlTree(currentElement, AddParsableInheritanceForModelClasses); - } - private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = new AdditionalUsingEvaluator[] { new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter), "Microsoft.Kiota.Abstractions", "IRequestAdapter"), @@ -244,17 +171,6 @@ parentClass.StartBlock is CodeClass.Declaration parentDeclaration ? "System.Text", "Encoding"), }; - private static void CapitalizeNamespacesFirstLetters(CodeElement current) - { - if (current is CodeNamespace currentNamespace) - currentNamespace.Name = currentNamespace.Name?.Split('.')?.Select(x => x.ToFirstCharacterUpperCase())?.Aggregate((x, y) => $"{x}.{y}"); - CrawlTree(current, CapitalizeNamespacesFirstLetters); - } - private static void AddAsyncSuffix(CodeElement currentElement) - { - if (currentElement is CodeMethod currentMethod && currentMethod.IsAsync) - currentMethod.Name += "Async"; - CrawlTree(currentElement, AddAsyncSuffix); - } + } } diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index fe0ce34f70..bf2cfe1338 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -54,19 +54,22 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType() .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) .OrderBy(m => m.Name); - // Filter out list builder commands. They contain the item builder commands already - var itemBuilderMethods = builderMethods.Where(m => !m.ReturnType.IsCollection); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); - writer.WriteLine("var commands = new List { "); + writer.WriteLine("var commands = new List {"); writer.IncreaseIndent(); - foreach (var method in itemBuilderMethods) + foreach (var method in builderMethods.Where(m => !m.ReturnType.IsCollection)) { writer.WriteLine($"builder.{method.Name}(),"); } writer.CloseBlock("};"); + foreach (var method in builderMethods.Where(m => m.ReturnType.IsCollection)) + { + writer.WriteLine($"commands.AddRange(builder.{method.Name}());"); + } + writer.WriteLine("return commands;"); } } else @@ -87,13 +90,21 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) .OrderBy(m => m.Name) .ThenBy(m => m.ReturnType.IsCollection); - // Filter out list builder commands. They contain the item builder commands already - var itemBuilderMethods = builderMethods.Where(m => !m.ReturnType.IsCollection); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); - foreach (var method in itemBuilderMethods) + foreach (var method in builderMethods) { - writer.WriteLine($"command.AddCommand(builder.{method.Name}());"); + if (method.ReturnType.IsCollection) + { + writer.WriteLine($"foreach (var cmd in builder.{method.Name}()) {{"); + writer.IncreaseIndent(); + writer.WriteLine($"command.AddCommand(cmd);"); + writer.CloseBlock(); + } + else + { + writer.WriteLine($"command.AddCommand(builder.{method.Name}());"); + } } // SubCommands } From f835f83358f6288a0b66b9a8f021c3b0b0b0f84c Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 27 Jan 2022 16:29:01 +0300 Subject: [PATCH 34/69] Print server response if an error occurs in the request --- .../Writers/Shell/ShellCodeMethodWriter.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 43ea00a5ee..e2f3c7f38b 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -249,6 +249,8 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP var contentType = originalMethod.ContentType ?? "application/json"; writer.WriteLine("var response = responseHandler.Value as HttpResponseMessage;"); writer.WriteLine($"var formatter = OutputFormatterFactory.Instance.GetFormatter({outputFormatParamName});"); + writer.WriteLine("if (response.IsSuccessStatusCode) {"); + writer.IncreaseIndent(); if (typeString != "Stream") { writer.WriteLine("var content = await response.Content.ReadAsStringAsync();"); @@ -267,9 +269,15 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP writer.WriteLine($"{consoleParamName}.WriteLine($\"Content written to {{{fileParamName}.FullName}}.\");"); writer.CloseBlock(); } + writer.CloseBlock(); + writer.WriteLine("else {"); + writer.IncreaseIndent(); + writer.WriteLine("var content = await response.Content.ReadAsStringAsync();"); + writer.WriteLine("console.WriteLine(content);"); + writer.CloseBlock(); // Assume string content as stream here - + } writer.DecreaseIndent(); var delimiter = ""; From 1a23e2542419e791337932557a0dd5c4e1f9791f Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 28 Jan 2022 08:02:58 +0300 Subject: [PATCH 35/69] Refactor for generation performance --- .../Writers/Shell/ShellCodeMethodWriter.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index e2f3c7f38b..d8624d93be 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -59,19 +59,18 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) .OrderBy(m => m.Name); conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); - writer.WriteLine("var commands = new List {"); - writer.IncreaseIndent(); - - foreach (var method in builderMethods.Where(m => !m.ReturnType.IsCollection)) - { - writer.WriteLine($"builder.{method.Name}(),"); - } - - writer.CloseBlock("};"); + writer.WriteLine("var commands = new List();"); - foreach (var method in builderMethods.Where(m => m.ReturnType.IsCollection)) + foreach (var method in builderMethods) { - writer.WriteLine($"commands.AddRange(builder.{method.Name}());"); + if (method.ReturnType.IsCollection) + { + writer.WriteLine($"commands.AddRange(builder.{method.Name}());"); + } + else + { + writer.WriteLine($"commands.Add(builder.{method.Name}());"); + } } writer.WriteLine("return commands;"); From e2f825f12d1874fdcd4a0228c99786c99727e125 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 4 Feb 2022 16:18:04 +0300 Subject: [PATCH 36/69] Move from using response handler and use SendPrimitiveAsync --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 4 +- .../Writers/Shell/ShellCodeMethodWriter.cs | 40 +++++++++---------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 227a22394e..a209bd19df 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -166,9 +166,7 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.CommandLine", "Command", "RootCommand"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), - "Microsoft.Graph.Cli.Core.IO", "OutputFormatterFactory"), - new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), - "System.Net.Http", "HttpResponseMessage"), + "Microsoft.Graph.Cli.Core.IO", "IOutputFormatterFactory"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.Text", "Encoding"), }; diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index d8624d93be..7fb35a22a4 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -20,6 +20,8 @@ class ShellCodeMethodWriter : CodeMethodWriter private const string fileParamName = "file"; private const string outputFormatParamType = "FormatterType"; private const string outputFormatParamName = "output"; + private const string outputFormatterFactoryParamType = "IOutputFormatterFactory"; + private const string outputFormatterFactoryParamName = "outputFormatterFactory"; public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(conventionService) { @@ -226,6 +228,10 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP availableOptions.Add(outputOptionName); } + // Add output formatter factory param + paramTypes.Add(outputFormatterFactoryParamType); + paramNames.Add(outputFormatterFactoryParamName); + // Add console param paramTypes.Add(consoleParamType); paramNames.Add(consoleParamName); @@ -234,10 +240,8 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP var handlerParams = string.Join(", ", projected); writer.WriteLine($"command.SetHandler(async ({handlerParams}) => {{"); writer.IncreaseIndent(); - writer.WriteLine("var responseHandler = new NativeResponseHandler();"); WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer); // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp - writer.WriteLine("// Print request output. What if the request has no return?"); if (isHandlerVoid) { writer.WriteLine($"{consoleParamName}.WriteLine(\"Success\");"); @@ -246,37 +250,25 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP var type = originalMethod.ReturnType as CodeType; var typeString = conventions.GetTypeString(type, originalMethod); var contentType = originalMethod.ContentType ?? "application/json"; - writer.WriteLine("var response = responseHandler.Value as HttpResponseMessage;"); - writer.WriteLine($"var formatter = OutputFormatterFactory.Instance.GetFormatter({outputFormatParamName});"); - writer.WriteLine("if (response.IsSuccessStatusCode) {"); - writer.IncreaseIndent(); + var formatterVar = "formatter"; + + writer.WriteLine($"var {formatterVar} = {outputFormatterFactoryParamName}.GetFormatter({outputFormatParamName});"); if (typeString != "Stream") { - writer.WriteLine("var content = await response.Content.ReadAsStringAsync();"); - writer.WriteLine($"formatter.WriteOutput(content, {consoleParamName});"); + writer.WriteLine($"{formatterVar}.WriteOutput(response, {consoleParamName});"); } else { - writer.WriteLine("var content = await response.Content.ReadAsStreamAsync();"); writer.WriteLine($"if ({fileParamName} == null) {{"); writer.IncreaseIndent(); - writer.WriteLine($"formatter.WriteOutput(content, {consoleParamName});"); + writer.WriteLine($"{formatterVar}.WriteOutput(response, {consoleParamName});"); writer.CloseBlock(); writer.WriteLine("else {"); writer.IncreaseIndent(); writer.WriteLine($"using var writeStream = {fileParamName}.OpenWrite();"); - writer.WriteLine("await content.CopyToAsync(writeStream);"); + writer.WriteLine("await response.CopyToAsync(writeStream);"); writer.WriteLine($"{consoleParamName}.WriteLine($\"Content written to {{{fileParamName}.FullName}}.\");"); writer.CloseBlock(); } - writer.CloseBlock(); - writer.WriteLine("else {"); - writer.IncreaseIndent(); - writer.WriteLine("var content = await response.Content.ReadAsStringAsync();"); - writer.WriteLine("console.WriteLine(content);"); - writer.CloseBlock(); - - // Assume string content as stream here - } writer.DecreaseIndent(); var delimiter = ""; @@ -355,7 +347,13 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa writer.WriteLine("});"); } - writer.WriteLine($"await RequestAdapter.SendNoContentAsync(requestInfo, responseHandler);"); + var requestMethod = "SendPrimitiveAsync"; + if (isVoid) + { + requestMethod = "SendNoContentAsync"; + } + + writer.WriteLine($"{(isVoid ? string.Empty : "var response = ")}await RequestAdapter.{requestMethod}(requestInfo);"); } /// From cfba6ef19e35fb3cc7219c0057d5aa2c6ab4146f Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 9 Feb 2022 19:23:48 +0300 Subject: [PATCH 37/69] Add CLI commons package --- .github/workflows/cli-commons.yml | 64 +++++++++++++++++++ cli/commons/Microsoft.Kiota.Cli.Commons.sln | 34 ++++++++++ .../Microsoft.Kiota.Cli.Commons.Tests.csproj | 23 +++++++ .../IO/IOutputFormatterFactory.cs | 6 ++ .../Microsoft.Kiota.Cli.Commons.csproj | 10 +++ src/Kiota.Builder/Refiners/ShellRefiner.cs | 2 +- 6 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/cli-commons.yml create mode 100644 cli/commons/Microsoft.Kiota.Cli.Commons.sln create mode 100644 cli/commons/src/Microsoft.Kiota.Cli.Commons.Tests/Microsoft.Kiota.Cli.Commons.Tests.csproj create mode 100644 cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatterFactory.cs create mode 100644 cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj diff --git a/.github/workflows/cli-commons.yml b/.github/workflows/cli-commons.yml new file mode 100644 index 0000000000..99f5269d08 --- /dev/null +++ b/.github/workflows/cli-commons.yml @@ -0,0 +1,64 @@ +name: CLI commons + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: ['cli/commons/**', '.github/workflows/**'] + pull_request: + paths: ['cli/commons/**', '.github/workflows/**'] + +jobs: + build: + runs-on: ubuntu-latest + env: + relativePath: ./cli/commons + solutionName: Microsoft.Kiota.Cli.Commons.sln + steps: + - uses: actions/checkout@v2.4.0 + - name: Setup .NET + uses: actions/setup-dotnet@v1.9.0 + with: + dotnet-version: 6.0.x + - name: Restore dependencies + run: dotnet restore ${{ env.solutionName }} + working-directory: ${{ env.relativePath }} + - name: Build + run: dotnet build ${{ env.solutionName }} --no-restore -c Release + working-directory: ${{ env.relativePath }} + - name: Test + run: dotnet test ${{ env.solutionName }} --no-build --verbosity normal -c Release /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=opencover + working-directory: ${{ env.relativePath }} + - name: Publish + run: dotnet publish ${{ env.solutionName }} --no-restore --no-build --verbosity normal -c Release + working-directory: ${{ env.relativePath }} + - name: Pack + run: dotnet pack ${{ env.solutionName }} --no-restore --no-build --verbosity normal -c Release + working-directory: ${{ env.relativePath }} + - name: Upload Coverage Results + uses: actions/upload-artifact@v2 + with: + name: codeCoverage + path: | + ${{ env.relativePath }}src/Microsoft.Kiota.Cli.Commons.Tests/TestResults + - name: Upload Nuget Package + uses: actions/upload-artifact@v2 + with: + name: drop + path: | + ${{ env.relativePath }}/src/bin/Release/*.nupkg + deploy: + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + environment: + name: staging_feeds + runs-on: ubuntu-latest + needs: [build] + steps: + - name: Setup .NET + uses: actions/setup-dotnet@v1.9.0 + with: + dotnet-version: 6.0.x + - uses: actions/download-artifact@v2 + with: + name: drop + - run: dotnet nuget push "*.nupkg" --skip-duplicate -s https://nuget.pkg.github.com/microsoft/index.json -k ${{ secrets.PUBLISH_GH_TOKEN }} diff --git a/cli/commons/Microsoft.Kiota.Cli.Commons.sln b/cli/commons/Microsoft.Kiota.Cli.Commons.sln new file mode 100644 index 0000000000..1608855473 --- /dev/null +++ b/cli/commons/Microsoft.Kiota.Cli.Commons.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8DEB9AF3-BEA6-4E73-BB5E-EBC1DFE6AF22}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kiota.Cli.Commons", "src\Microsoft.Kiota.Cli.Commons\Microsoft.Kiota.Cli.Commons.csproj", "{23DD14C5-3060-4498-B2F9-85B68770AE0B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kiota.Cli.Commons.Tests", "src\Microsoft.Kiota.Cli.Commons.Tests\Microsoft.Kiota.Cli.Commons.Tests.csproj", "{D1228DD9-C98F-46C1-911A-65AE2D34DBE5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {23DD14C5-3060-4498-B2F9-85B68770AE0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23DD14C5-3060-4498-B2F9-85B68770AE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23DD14C5-3060-4498-B2F9-85B68770AE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23DD14C5-3060-4498-B2F9-85B68770AE0B}.Release|Any CPU.Build.0 = Release|Any CPU + {D1228DD9-C98F-46C1-911A-65AE2D34DBE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1228DD9-C98F-46C1-911A-65AE2D34DBE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1228DD9-C98F-46C1-911A-65AE2D34DBE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1228DD9-C98F-46C1-911A-65AE2D34DBE5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {23DD14C5-3060-4498-B2F9-85B68770AE0B} = {8DEB9AF3-BEA6-4E73-BB5E-EBC1DFE6AF22} + {D1228DD9-C98F-46C1-911A-65AE2D34DBE5} = {8DEB9AF3-BEA6-4E73-BB5E-EBC1DFE6AF22} + EndGlobalSection +EndGlobal diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons.Tests/Microsoft.Kiota.Cli.Commons.Tests.csproj b/cli/commons/src/Microsoft.Kiota.Cli.Commons.Tests/Microsoft.Kiota.Cli.Commons.Tests.csproj new file mode 100644 index 0000000000..5f88768850 --- /dev/null +++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons.Tests/Microsoft.Kiota.Cli.Commons.Tests.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatterFactory.cs b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatterFactory.cs new file mode 100644 index 0000000000..4d8026a99a --- /dev/null +++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatterFactory.cs @@ -0,0 +1,6 @@ +namespace Microsoft.Kiota.Cli.Commons.IO; + +public interface IOutputFormatterFactory +{ + IOutputFormatter GetFormatter(FormatterType formatterType); +} diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj b/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj new file mode 100644 index 0000000000..145fc50653 --- /dev/null +++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj @@ -0,0 +1,10 @@ + + + + net6.0 + enable + enable + 0.1.0 + + + diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index a209bd19df..deb03b33b1 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -166,7 +166,7 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.CommandLine", "Command", "RootCommand"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), - "Microsoft.Graph.Cli.Core.IO", "IOutputFormatterFactory"), + "Microsoft.Kiota.Cli.Commons.IO", "IOutputFormatterFactory"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.Text", "Encoding"), }; From 9a2fdf704d257e6473dc413cc543fd1704092403 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 9 Feb 2022 20:13:59 +0300 Subject: [PATCH 38/69] Add missing types --- .../Microsoft.Kiota.Cli.Commons/IO/FormatterType.cs | 8 ++++++++ .../Microsoft.Kiota.Cli.Commons/IO/IOutputFormatter.cs | 10 ++++++++++ .../Microsoft.Kiota.Cli.Commons.csproj | 4 ++++ 3 files changed, 22 insertions(+) create mode 100644 cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/FormatterType.cs create mode 100644 cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatter.cs diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/FormatterType.cs b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/FormatterType.cs new file mode 100644 index 0000000000..96618b0a82 --- /dev/null +++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/FormatterType.cs @@ -0,0 +1,8 @@ +namespace Microsoft.Kiota.Cli.Commons.IO; + +public enum FormatterType +{ + JSON, + TABLE, + NONE +} diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatter.cs b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatter.cs new file mode 100644 index 0000000000..924b8a754a --- /dev/null +++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatter.cs @@ -0,0 +1,10 @@ +using System.CommandLine; + +namespace Microsoft.Kiota.Cli.Commons.IO; + +public interface IOutputFormatter +{ + void WriteOutput(string content, IConsole console); + + void WriteOutput(Stream content, IConsole console); +} diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj b/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj index 145fc50653..062e460d3f 100644 --- a/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj +++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj @@ -7,4 +7,8 @@ 0.1.0 + + + + From fa2bec3b0fd46cf31abb3c71aa7840a744df56ec Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 10 Feb 2022 10:42:49 +0300 Subject: [PATCH 39/69] Update dependabot config Update code analysis workflows Add readme file --- .github/dependabot.yml | 7 +++++++ .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/sonarcloud.yml | 4 ++-- cli/commons/README.md | 3 +++ 4 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 cli/commons/README.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 67ffac46d6..6db24e8ef7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -116,3 +116,10 @@ updates: open-pull-requests-limit: 10 labels: - TypeScript +- package-ecosystem: nuget + directory: "/cli/commons" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - CLI diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fc908e7e9e..927dbd2b35 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -15,10 +15,10 @@ on: workflow_dispatch: push: branches: [ main ] - path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', '**.md', '.vscode/**', '**.svg'] + path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', 'cli/**', '**.md', '.vscode/**', '**.svg'] pull_request: # The branches below must be a subset of the branches above - path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', '**.md', '.vscode/**', '**.svg'] + path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', 'cli/**', '**.md', '.vscode/**', '**.svg'] schedule: - cron: '20 9 * * 5' diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index e7790b5fb2..5d95adc35b 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -4,10 +4,10 @@ on: push: branches: - main - path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', '**.md', '.vscode/**', '**.svg'] + path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', 'cli/**', '**.md', '.vscode/**', '**.svg'] pull_request: types: [opened, synchronize, reopened] - path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', '**.md', '.vscode/**', '**.svg'] + path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', 'cli/**', '**.md', '.vscode/**', '**.svg'] env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/cli/commons/README.md b/cli/commons/README.md new file mode 100644 index 0000000000..a2ee63c92a --- /dev/null +++ b/cli/commons/README.md @@ -0,0 +1,3 @@ +# Kiota CLI Commons Package + +Contains CLI specific types that are referenced in code generated by the shell language. \ No newline at end of file From 00471e9484e01b57f80280db7799d55039e6570c Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 10 Feb 2022 13:03:45 +0300 Subject: [PATCH 40/69] Add shell language to main readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8dce2ac4b6..a1c56e17f9 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ The following table provides an overview of the languages supported by Kiota and | Python | [▶](https://github.com/microsoft/kiota/projects/3) | [✔](./abstractions/python) | ❌ | [Anonymous](./abstractions/python/kiota/abstractions/authentication/anonymous_authentication_provider.py), [Azure](./authentication/python/azure) | ❌ | | | Ruby | [✔](https://github.com/microsoft/kiota/projects/6) | [✔](./abstractions/ruby) | [JSON](./serialization/ruby/json/microsoft_kiota_serialization) | [Anonymous](./abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/authentication/anonymous_authentication_provider.rb), [❌ Azure](https://github.com/microsoft/kiota/issues/421) | [✔](./http/ruby/nethttp/microsoft_kiota_nethttplibrary)| [link](https://microsoft.github.io/kiota/get-started/ruby) | | TypeScript/JavaScript | [✔](https://github.com/microsoft/kiota/projects/2) | [✔](./abstractions/typescript) | [JSON](./serialization/typescript/json) | [Anonymous](./abstractions/typescript/src/authentication/anonymousAuthenticationProvider.ts), [Azure](./authentication/typescript/azure) | [✔](./http/typescript/fetch) | [link](https://microsoft.github.io/kiota/get-started/typescript) | +| Shell | [✔](https://github.com/microsoft/kiota/projects/10) | [✔](./abstractions/dotnet), [✔](./cli/commonc) | [JSON](./serialization/dotnet/json) | [Anonymous](./abstractions/dotnet/src/authentication/AnonymousAuthenticationProvider.cs), [Azure](./authentication/dotnet/azure) | [✔](./http/dotnet/httpclient) | [link](https://microsoft.github.io/kiota/get-started/dotnet) | > Legend: ✔ -> in preview, ❌ -> not started, ▶ -> in progress. From ef8c275435df7dd36decf71849cead199b644557 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 10 Feb 2022 18:48:53 +0300 Subject: [PATCH 41/69] Fix possible null pointer exception --- .../Writers/Shell/ShellCodeMethodWriter.cs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 7fb35a22a4..8e8af1e187 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -289,34 +289,37 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa .Methods .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); var requestBodyParam = requestParams.requestBody; - var requestBodyParamType = requestBodyParam?.Type as CodeType; - if (requestBodyParamType?.TypeDefinition is CodeClass) - { - writer.WriteLine($"using var stream = new MemoryStream(Encoding.UTF8.GetBytes({requestBodyParam.Name}));"); - writer.WriteLine("var parseNode = ParseNodeFactoryRegistry.DefaultInstance.GetRootParseNode(\"application/json\", stream);"); + if (requestBodyParam != null) { + var requestBodyParamType = requestBodyParam?.Type as CodeType; + if (requestBodyParamType?.TypeDefinition is CodeClass) + { + writer.WriteLine($"using var stream = new MemoryStream(Encoding.UTF8.GetBytes({requestBodyParam.Name}));"); + writer.WriteLine("var parseNode = ParseNodeFactoryRegistry.DefaultInstance.GetRootParseNode(\"application/json\", stream);"); - var typeString = conventions.GetTypeString(requestBodyParamType, requestBodyParam, false); + var typeString = conventions.GetTypeString(requestBodyParamType, requestBodyParam, false); - if (requestBodyParamType.IsCollection) - { - writer.WriteLine($"var model = parseNode.GetCollectionOfObjectValues<{typeString}>();"); - } else + if (requestBodyParamType.IsCollection) + { + writer.WriteLine($"var model = parseNode.GetCollectionOfObjectValues<{typeString}>();"); + } else + { + writer.WriteLine($"var model = parseNode.GetObjectValue<{typeString}>();"); + } + + requestBodyParam.Name = "model"; + } else if (conventions.StreamTypeName.Equals(requestBodyParamType?.Name, StringComparison.OrdinalIgnoreCase)) { - writer.WriteLine($"var model = parseNode.GetObjectValue<{typeString}>();"); + var name = requestBodyParam.Name; + requestBodyParam.Name = "stream"; + writer.WriteLine($"using var {requestBodyParam.Name} = {name}.OpenRead();"); } - - requestBodyParam.Name = "model"; - } else if (conventions.StreamTypeName.Equals(requestBodyParamType?.Name, StringComparison.OrdinalIgnoreCase)) - { - var name = requestBodyParam.Name; - requestBodyParam.Name = "stream"; - writer.WriteLine($"using var {requestBodyParam.Name} = {name}.OpenRead();"); } + var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } .Select(x => x?.Name).Where(x => x != null).DefaultIfEmpty().Aggregate((x, y) => $"{x}, {y}"); var separator = string.IsNullOrWhiteSpace(parametersList) ? "" : ", "; writer.WriteLine($"var requestInfo = {generatorMethod?.Name}({parametersList}{separator}q => {{"); - if (generatorMethod.PathAndQueryParameters != null) + if (generatorMethod?.PathAndQueryParameters != null) { writer.IncreaseIndent(); foreach (var param in generatorMethod.PathAndQueryParameters.Where(p => p.IsOfKind(CodeParameterKind.QueryParameter))) From d562acc8637f0c927ffc26245a3b2c5095598245 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 10 Feb 2022 19:36:03 +0300 Subject: [PATCH 42/69] Fix code smells --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 14 ++++++++----- .../Writers/Shell/ShellCodeMethodWriter.cs | 20 +++++++------------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index deb03b33b1..9c465777aa 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -115,19 +115,23 @@ private static CodeType CreateCommandType(CodeElement parent) return new CodeType { Name = "Command", - IsExternal = true + IsExternal = true, + Parent = parent, }; } private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, CodeClass parent) { - var codeMethod = new CodeMethod(); - codeMethod.IsAsync = false; - codeMethod.Name = $"Build{navProperty.Name.ToFirstCharacterUpperCase()}Command"; - codeMethod.MethodKind = CodeMethodKind.CommandBuilder; + var codeMethod = new CodeMethod + { + IsAsync = false, + Name = $"Build{navProperty.Name.ToFirstCharacterUpperCase()}Command", + MethodKind = CodeMethodKind.CommandBuilder + }; codeMethod.ReturnType = CreateCommandType(codeMethod); codeMethod.AccessedProperty = navProperty; codeMethod.SimpleName = navProperty.Name; + codeMethod.Parent = parent; return codeMethod; } diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 8e8af1e187..6602413e65 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -10,7 +10,7 @@ namespace Kiota.Builder.Writers.Shell { class ShellCodeMethodWriter : CodeMethodWriter { - private static Regex delimitedRegex = new Regex("(?<=[a-z])[-_\\.]([A-Za-z])", RegexOptions.Compiled); + private static Regex delimitedRegex = new Regex("(?<=[a-z])[-_\\.]+([A-Za-z])", RegexOptions.Compiled); private static Regex camelCaseRegex = new Regex("(?<=[a-z])([A-Z])", RegexOptions.Compiled); private static Regex identifierRegex = new Regex("(?:[-_\\.]([a-zA-Z]))", RegexOptions.Compiled); private static Regex uppercaseRegex = new Regex("([A-Z])", RegexOptions.Compiled); @@ -89,7 +89,6 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP { // Include namespace to avoid type ambiguity on similarly named classes. Currently, if we have namespaces A and A.B where both namespaces have type T, // Trying to use type A.B.T in namespace A without using the fully qualified name will break the build. - // TODO: Fix this in the refiner. var targetClass = string.Join(".", codeReturnType.TypeDefinition.Parent.Name, conventions.GetTypeString(codeReturnType, codeElement)); var builderMethods = codeReturnType.TypeDefinition.GetChildElements(true).OfType() .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) @@ -118,7 +117,6 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP } } else { - var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); var generatorMethod = (codeElement.Parent as CodeClass) .Methods .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); @@ -249,7 +247,6 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP { var type = originalMethod.ReturnType as CodeType; var typeString = conventions.GetTypeString(type, originalMethod); - var contentType = originalMethod.ContentType ?? "application/json"; var formatterVar = "formatter"; writer.WriteLine($"var {formatterVar} = {outputFormatterFactoryParamName}.GetFormatter({outputFormatParamName});"); @@ -284,7 +281,6 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa { if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); - var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); var generatorMethod = (codeElement.Parent as CodeClass) .Methods .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); @@ -339,10 +335,9 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa } writer.CloseBlock("});"); - foreach (var param in generatorMethod.PathAndQueryParameters.Where(p => p.IsOfKind(CodeParameterKind.PathParameters))) + foreach (var paramName in generatorMethod.PathAndQueryParameters.Where(p => p.IsOfKind(CodeParameterKind.PathParameters)).Select(p => p.Name)) { - var paramName = NormalizeToIdentifier(param.Name); - writer.WriteLine($"requestInfo.PathParameters.Add(\"{param.Name}\", {paramName});"); + writer.WriteLine($"requestInfo.PathParameters.Add(\"{paramName}\", {NormalizeToIdentifier(paramName)});"); } } else @@ -364,7 +359,7 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa /// /// /// - private string NormalizeToIdentifier(string input) + private static string NormalizeToIdentifier(string input) { return identifierRegex.Replace(input, m => m.Groups[1].Value.ToUpper()); } @@ -374,12 +369,11 @@ private string NormalizeToIdentifier(string input) /// /// /// - private string NormalizeToOption(string input) + private static string NormalizeToOption(string input) { - var result = input; - result = camelCaseRegex.Replace(input, "-$1"); + var result = camelCaseRegex.Replace(input, "-$1"); // 2 passes for cases like "singleValueLegacyExtendedProperty_id" - result = delimitedRegex.Replace(input, "-$1"); + result = delimitedRegex.Replace(result, "-$1"); return result.ToLower(); } From d2e6be94a9aa51259f7d522f792b7663f6e3b137 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Thu, 10 Feb 2022 20:01:36 +0300 Subject: [PATCH 43/69] Simplify WriteCommandBuilderBody function --- .../Writers/Shell/ShellCodeMethodWriter.cs | 428 +++++++++--------- 1 file changed, 224 insertions(+), 204 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 6602413e65..459ed16135 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -39,242 +39,262 @@ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestP // Build method // Puts together the BuildXXCommand objects. Needs a nav property name e.g. users // Command("users") -> Command("get") - if (String.IsNullOrWhiteSpace(name)) + if (string.IsNullOrWhiteSpace(name)) { // BuildCommand function - if (codeElement.OriginalMethod?.MethodKind == CodeMethodKind.ClientConstructor) - { - var commandBuilderMethods = classMethods.Where(m => m.MethodKind == CodeMethodKind.CommandBuilder && m != codeElement).OrderBy(m => m.Name); - writer.WriteLine($"var command = new RootCommand();"); - writer.WriteLine($"command.Description = \"{codeElement.OriginalMethod.Description}\";"); - foreach (var method in commandBuilderMethods) - { - writer.WriteLine($"command.AddCommand({method.Name}());"); - } - - writer.WriteLine("return command;"); - } - else if (codeElement.OriginalIndexer != null) - { - var targetClass = conventions.GetTypeString(codeElement.OriginalIndexer.ReturnType, codeElement); - var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType() - .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) - .OrderBy(m => m.Name); - conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); - writer.WriteLine("var commands = new List();"); - - foreach (var method in builderMethods) - { - if (method.ReturnType.IsCollection) - { - writer.WriteLine($"commands.AddRange(builder.{method.Name}());"); - } - else - { - writer.WriteLine($"commands.Add(builder.{method.Name}());"); - } - } - - writer.WriteLine("return commands;"); - } - } else + WriteUnnamedBuildCommand(codeElement, writer, parent, classMethods); + } + else { - var codeReturnType = (codeElement.AccessedProperty?.Type) as CodeType; - - writer.WriteLine($"var command = new Command(\"{name}\");"); - if (!string.IsNullOrEmpty(codeElement.Description) || !string.IsNullOrEmpty(codeElement?.OriginalMethod?.Description)) - writer.WriteLine($"command.Description = \"{codeElement.Description ?? codeElement?.OriginalMethod?.Description}\";"); - - if (codeReturnType != null) - { - // Include namespace to avoid type ambiguity on similarly named classes. Currently, if we have namespaces A and A.B where both namespaces have type T, - // Trying to use type A.B.T in namespace A without using the fully qualified name will break the build. - var targetClass = string.Join(".", codeReturnType.TypeDefinition.Parent.Name, conventions.GetTypeString(codeReturnType, codeElement)); - var builderMethods = codeReturnType.TypeDefinition.GetChildElements(true).OfType() - .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) - .OrderBy(m => m.Name) - .ThenBy(m => m.ReturnType.IsCollection); - conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); - - foreach (var method in builderMethods) - { - if (method.ReturnType.IsCollection) - { - writer.WriteLine($"foreach (var cmd in builder.{method.Name}()) {{"); - writer.IncreaseIndent(); - writer.WriteLine($"command.AddCommand(cmd);"); - writer.CloseBlock(); - } - else - { - writer.WriteLine($"command.AddCommand(builder.{method.Name}());"); - } - } - // SubCommands - } - - writer.WriteLine("return command;"); + WriteContainerCommand(codeElement, writer, parent, name); } } else { - var generatorMethod = (codeElement.Parent as CodeClass) - .Methods - .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); - var pathAndQueryParams = generatorMethod.PathAndQueryParameters; - var originalMethod = codeElement.OriginalMethod; - var origParams = originalMethod.Parameters; - var parametersList = pathAndQueryParams?.Where(p => p.Name != null)?.ToList() ?? new List(); - if (origParams.Any(p => p.IsOfKind(CodeParameterKind.RequestBody))) + WriteExecutableCommand(codeElement, requestParams, writer, name); + } + } + + private void WriteExecutableCommand(CodeMethod codeElement, RequestParams requestParams, LanguageWriter writer, string name) + { + var generatorMethod = (codeElement.Parent as CodeClass) + .Methods + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); + var pathAndQueryParams = generatorMethod.PathAndQueryParameters; + var originalMethod = codeElement.OriginalMethod; + var origParams = originalMethod.Parameters; + var parametersList = pathAndQueryParams?.Where(p => p.Name != null)?.ToList() ?? new List(); + if (origParams.Any(p => p.IsOfKind(CodeParameterKind.RequestBody))) + { + parametersList.Add(origParams.OfKind(CodeParameterKind.RequestBody)); + } + writer.WriteLine($"var command = new Command(\"{name}\");"); + if (codeElement.Description != null || codeElement?.OriginalMethod?.Description != null) + writer.WriteLine($"command.Description = \"{codeElement.Description ?? codeElement?.OriginalMethod?.Description}\";"); + writer.WriteLine("// Create options for all the parameters"); + // investigate exploding query params + // Check the possible formatting options for headers in a cli. + // -h A=b -h + // -h A:B,B:C + // -h {"A": "B"} + var availableOptions = new List(); + foreach (var option in parametersList) + { + var type = option.Type as CodeType; + var optionName = $"{NormalizeToIdentifier(option.Name)}Option"; + var optionType = conventions.GetTypeString(option.Type, option); + if (option.ParameterKind == CodeParameterKind.RequestBody && type.TypeDefinition is CodeClass) optionType = "string"; + + // Binary body handling + if (option.ParameterKind == CodeParameterKind.RequestBody && conventions.StreamTypeName.Equals(option.Type?.Name, StringComparison.OrdinalIgnoreCase)) { - parametersList.Add(origParams.OfKind(CodeParameterKind.RequestBody)); + option.Name = "file"; } - writer.WriteLine($"var command = new Command(\"{name}\");"); - if (codeElement.Description != null || codeElement?.OriginalMethod?.Description != null) - writer.WriteLine($"command.Description = \"{codeElement.Description ?? codeElement?.OriginalMethod?.Description}\";"); - writer.WriteLine("// Create options for all the parameters"); // investigate exploding query params - // Check the possible formatting options for headers in a cli. - // -h A=b -h - // -h A:B,B:C - // -h {"A": "B"} - var availableOptions = new List(); - foreach (var option in parametersList) - { - var type = option.Type as CodeType; - var optionName = $"{NormalizeToIdentifier(option.Name)}Option"; - var optionType = conventions.GetTypeString(option.Type, option); - if (option.ParameterKind == CodeParameterKind.RequestBody && type.TypeDefinition is CodeClass) optionType = "string"; - - // Binary body handling - if (option.ParameterKind == CodeParameterKind.RequestBody && conventions.StreamTypeName.Equals(option.Type?.Name, StringComparison.OrdinalIgnoreCase)) { - option.Name = "file"; - } - var optionBuilder = new StringBuilder("new Option"); - if (!String.IsNullOrEmpty(optionType)) - { - optionBuilder.Append($"<{optionType}>"); - } - optionBuilder.Append("(\""); - if (option.Name.Length > 1) optionBuilder.Append('-'); - optionBuilder.Append($"-{NormalizeToOption(option.Name)}\""); - if (option.DefaultValue != null) - { - optionBuilder.Append($", getDefaultValue: ()=> {option.DefaultValue}"); - } - - if (!String.IsNullOrEmpty(option.Description)) - { - optionBuilder.Append($", description: \"{option.Description}\""); - } + var optionBuilder = new StringBuilder("new Option"); + if (!String.IsNullOrEmpty(optionType)) + { + optionBuilder.Append($"<{optionType}>"); + } + optionBuilder.Append("(\""); + if (option.Name.Length > 1) optionBuilder.Append('-'); + optionBuilder.Append($"-{NormalizeToOption(option.Name)}\""); + if (option.DefaultValue != null) + { + optionBuilder.Append($", getDefaultValue: ()=> {option.DefaultValue}"); + } - optionBuilder.Append(") {"); - var strValue = $"{optionBuilder}"; - writer.WriteLine($"var {optionName} = {strValue}"); - writer.IncreaseIndent(); - var isRequired = !option.Optional || option.IsOfKind(CodeParameterKind.Path); + if (!String.IsNullOrEmpty(option.Description)) + { + optionBuilder.Append($", description: \"{option.Description}\""); + } - if (option.Type.IsCollection) - { - var arity = isRequired ? "OneOrMore" : "ZeroOrMore"; - writer.WriteLine($"Arity = ArgumentArity.{arity}"); - } + optionBuilder.Append(") {"); + var strValue = $"{optionBuilder}"; + writer.WriteLine($"var {optionName} = {strValue}"); + writer.IncreaseIndent(); + var isRequired = !option.Optional || option.IsOfKind(CodeParameterKind.Path); - writer.DecreaseIndent(); - writer.WriteLine("};"); - writer.WriteLine($"{optionName}.IsRequired = {isRequired.ToString().ToFirstCharacterLowerCase()};"); - writer.WriteLine($"command.AddOption({optionName});"); - availableOptions.Add(optionName); + if (option.Type.IsCollection) + { + var arity = isRequired ? "OneOrMore" : "ZeroOrMore"; + writer.WriteLine($"Arity = ArgumentArity.{arity}"); } - var paramTypes = parametersList.Select(x => - { - var codeType = x.Type as CodeType; - if (x.ParameterKind == CodeParameterKind.RequestBody && codeType.TypeDefinition is CodeClass) - { - return "string"; - } else if (conventions.StreamTypeName.Equals(x.Type?.Name, StringComparison.OrdinalIgnoreCase)) - { - return "FileInfo"; - } + writer.DecreaseIndent(); + writer.WriteLine("};"); + writer.WriteLine($"{optionName}.IsRequired = {isRequired.ToString().ToFirstCharacterLowerCase()};"); + writer.WriteLine($"command.AddOption({optionName});"); + availableOptions.Add(optionName); + } - return conventions.GetTypeString(x.Type, x); - }).ToList(); - var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).ToList(); - var isHandlerVoid = conventions.VoidTypeName.Equals(originalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); - returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod); - if (conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) + var paramTypes = parametersList.Select(x => + { + var codeType = x.Type as CodeType; + if (x.ParameterKind == CodeParameterKind.RequestBody && codeType.TypeDefinition is CodeClass) { - var fileOptionName = "fileOption"; - writer.WriteLine($"var {fileOptionName} = new Option<{fileParamType}>(\"--{fileParamName}\");"); - writer.WriteLine($"command.AddOption({fileOptionName});"); - paramTypes.Add(fileParamType); - paramNames.Add(fileParamName); - availableOptions.Add(fileOptionName); + return "string"; } - - // Add output type param - if (!isHandlerVoid) + else if (conventions.StreamTypeName.Equals(x.Type?.Name, StringComparison.OrdinalIgnoreCase)) { - var outputOptionName = "outputOption"; - writer.WriteLine($"var {outputOptionName} = new Option<{outputFormatParamType}>(\"--{outputFormatParamName}\", () => FormatterType.JSON){{"); - writer.IncreaseIndent(); - writer.WriteLine("IsRequired = true"); - writer.CloseBlock("};"); - writer.WriteLine($"command.AddOption({outputOptionName});"); - paramTypes.Add(outputFormatParamType); - paramNames.Add(outputFormatParamName); - availableOptions.Add(outputOptionName); + return "FileInfo"; } - // Add output formatter factory param - paramTypes.Add(outputFormatterFactoryParamType); - paramNames.Add(outputFormatterFactoryParamName); - - // Add console param - paramTypes.Add(consoleParamType); - paramNames.Add(consoleParamName); - var zipped = paramTypes.Zip(paramNames); - var projected = zipped.Select((x, y) => $"{x.First} {x.Second}"); - var handlerParams = string.Join(", ", projected); - writer.WriteLine($"command.SetHandler(async ({handlerParams}) => {{"); + return conventions.GetTypeString(x.Type, x); + }).ToList(); + var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).ToList(); + var isHandlerVoid = conventions.VoidTypeName.Equals(originalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase); + var returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod); + if (conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) + { + var fileOptionName = "fileOption"; + writer.WriteLine($"var {fileOptionName} = new Option<{fileParamType}>(\"--{fileParamName}\");"); + writer.WriteLine($"command.AddOption({fileOptionName});"); + paramTypes.Add(fileParamType); + paramNames.Add(fileParamName); + availableOptions.Add(fileOptionName); + } + + // Add output type param + if (!isHandlerVoid) + { + var outputOptionName = "outputOption"; + writer.WriteLine($"var {outputOptionName} = new Option<{outputFormatParamType}>(\"--{outputFormatParamName}\", () => FormatterType.JSON){{"); writer.IncreaseIndent(); - WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer); - // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp - if (isHandlerVoid) + writer.WriteLine("IsRequired = true"); + writer.CloseBlock("};"); + writer.WriteLine($"command.AddOption({outputOptionName});"); + paramTypes.Add(outputFormatParamType); + paramNames.Add(outputFormatParamName); + availableOptions.Add(outputOptionName); + } + + // Add output formatter factory param + paramTypes.Add(outputFormatterFactoryParamType); + paramNames.Add(outputFormatterFactoryParamName); + + // Add console param + paramTypes.Add(consoleParamType); + paramNames.Add(consoleParamName); + var zipped = paramTypes.Zip(paramNames); + var projected = zipped.Select((x, y) => $"{x.First} {x.Second}"); + var handlerParams = string.Join(", ", projected); + writer.WriteLine($"command.SetHandler(async ({handlerParams}) => {{"); + writer.IncreaseIndent(); + WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer); + // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp + if (isHandlerVoid) + { + writer.WriteLine($"{consoleParamName}.WriteLine(\"Success\");"); + } + else + { + var type = originalMethod.ReturnType as CodeType; + var typeString = conventions.GetTypeString(type, originalMethod); + var formatterVar = "formatter"; + + writer.WriteLine($"var {formatterVar} = {outputFormatterFactoryParamName}.GetFormatter({outputFormatParamName});"); + if (typeString != "Stream") { - writer.WriteLine($"{consoleParamName}.WriteLine(\"Success\");"); - } else + writer.WriteLine($"{formatterVar}.WriteOutput(response, {consoleParamName});"); + } + else { - var type = originalMethod.ReturnType as CodeType; - var typeString = conventions.GetTypeString(type, originalMethod); - var formatterVar = "formatter"; + writer.WriteLine($"if ({fileParamName} == null) {{"); + writer.IncreaseIndent(); + writer.WriteLine($"{formatterVar}.WriteOutput(response, {consoleParamName});"); + writer.CloseBlock(); + writer.WriteLine("else {"); + writer.IncreaseIndent(); + writer.WriteLine($"using var writeStream = {fileParamName}.OpenWrite();"); + writer.WriteLine("await response.CopyToAsync(writeStream);"); + writer.WriteLine($"{consoleParamName}.WriteLine($\"Content written to {{{fileParamName}.FullName}}.\");"); + writer.CloseBlock(); + } + } + writer.DecreaseIndent(); + var delimiter = ""; + if (availableOptions.Any()) + { + delimiter = ", "; + } + writer.WriteLine($"}}{delimiter}{string.Join(", ", availableOptions)});"); + writer.WriteLine("return command;"); + } - writer.WriteLine($"var {formatterVar} = {outputFormatterFactoryParamName}.GetFormatter({outputFormatParamName});"); - if (typeString != "Stream") - { - writer.WriteLine($"{formatterVar}.WriteOutput(response, {consoleParamName});"); - } else + private void WriteContainerCommand(CodeMethod codeElement, LanguageWriter writer, CodeClass parent, string name) + { + writer.WriteLine($"var command = new Command(\"{name}\");"); + if (!string.IsNullOrEmpty(codeElement.Description) || !string.IsNullOrEmpty(codeElement?.OriginalMethod?.Description)) + writer.WriteLine($"command.Description = \"{codeElement.Description ?? codeElement?.OriginalMethod?.Description}\";"); + + if ((codeElement.AccessedProperty?.Type) is CodeType codeReturnType) + { + // Include namespace to avoid type ambiguity on similarly named classes. Currently, if we have namespaces A and A.B where both namespaces have type T, + // Trying to use type A.B.T in namespace A without using the fully qualified name will break the build. + var targetClass = string.Join(".", codeReturnType.TypeDefinition.Parent.Name, conventions.GetTypeString(codeReturnType, codeElement)); + var builderMethods = codeReturnType.TypeDefinition.GetChildElements(true).OfType() + .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) + .OrderBy(m => m.Name) + .ThenBy(m => m.ReturnType.IsCollection); + conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); + + foreach (var method in builderMethods) + { + if (method.ReturnType.IsCollection) { - writer.WriteLine($"if ({fileParamName} == null) {{"); - writer.IncreaseIndent(); - writer.WriteLine($"{formatterVar}.WriteOutput(response, {consoleParamName});"); - writer.CloseBlock(); - writer.WriteLine("else {"); + writer.WriteLine($"foreach (var cmd in builder.{method.Name}()) {{"); writer.IncreaseIndent(); - writer.WriteLine($"using var writeStream = {fileParamName}.OpenWrite();"); - writer.WriteLine("await response.CopyToAsync(writeStream);"); - writer.WriteLine($"{consoleParamName}.WriteLine($\"Content written to {{{fileParamName}.FullName}}.\");"); + writer.WriteLine($"command.AddCommand(cmd);"); writer.CloseBlock(); } + else + { + writer.WriteLine($"command.AddCommand(builder.{method.Name}());"); + } } - writer.DecreaseIndent(); - var delimiter = ""; - if (availableOptions.Any()) { - delimiter = ", "; + // SubCommands + } + + writer.WriteLine("return command;"); + } + + private void WriteUnnamedBuildCommand(CodeMethod codeElement, LanguageWriter writer, CodeClass parent, IEnumerable classMethods) + { + if (codeElement.OriginalMethod?.MethodKind == CodeMethodKind.ClientConstructor) + { + var commandBuilderMethods = classMethods.Where(m => m.MethodKind == CodeMethodKind.CommandBuilder && m != codeElement).OrderBy(m => m.Name); + writer.WriteLine($"var command = new RootCommand();"); + writer.WriteLine($"command.Description = \"{codeElement.OriginalMethod.Description}\";"); + foreach (var method in commandBuilderMethods) + { + writer.WriteLine($"command.AddCommand({method.Name}());"); } - writer.WriteLine($"}}{delimiter}{string.Join(", ", availableOptions)});"); + writer.WriteLine("return command;"); } + else if (codeElement.OriginalIndexer != null) + { + var targetClass = conventions.GetTypeString(codeElement.OriginalIndexer.ReturnType, codeElement); + var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType() + .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder)) + .OrderBy(m => m.Name); + conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); + writer.WriteLine("var commands = new List();"); + + foreach (var method in builderMethods) + { + if (method.ReturnType.IsCollection) + { + writer.WriteLine($"commands.AddRange(builder.{method.Name}());"); + } + else + { + writer.WriteLine($"commands.Add(builder.{method.Name}());"); + } + } + + writer.WriteLine("return commands;"); + } } protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) From bf6614c4d89e8040f7c30cf915b3e5555f089fa4 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 09:26:53 +0300 Subject: [PATCH 44/69] Correct core types in shell refiner --- src/Kiota.Builder/Refiners/CSharpRefiner.cs | 4 ++-- src/Kiota.Builder/Refiners/ShellRefiner.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs index abdee0041c..63a99500df 100644 --- a/src/Kiota.Builder/Refiners/CSharpRefiner.cs +++ b/src/Kiota.Builder/Refiners/CSharpRefiner.cs @@ -99,11 +99,11 @@ protected static void AddAsyncSuffix(CodeElement currentElement) { currentMethod.Name += "Async"; CrawlTree(currentElement, AddAsyncSuffix); } - private static void CorrectPropertyType(CodeProperty currentProperty) + protected static void CorrectPropertyType(CodeProperty currentProperty) { CorrectDateTypes(currentProperty.Parent as CodeClass, DateTypesReplacements, currentProperty.Type); } - private static void CorrectMethodType(CodeMethod currentMethod) + protected static void CorrectMethodType(CodeMethod currentMethod) { CorrectDateTypes(currentMethod.Parent as CodeClass, DateTypesReplacements, currentMethod.Parameters .Select(x => x.Type) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 9c465777aa..d84095470a 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -13,6 +13,7 @@ public ShellRefiner(GenerationConfiguration configuration) : base(configuration) public override void Refine(CodeNamespace generatedCode) { AddDefaultImports(generatedCode, defaultUsingEvaluators); + CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType); MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore); AddRawUrlConstructorOverload(generatedCode); From 1f61f7eef308678d4fdd2b8e3ab6d308a0eb96d9 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 09:32:02 +0300 Subject: [PATCH 45/69] Remove raw url constructor overload --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index d84095470a..be1c714cbe 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -16,7 +16,6 @@ public override void Refine(CodeNamespace generatedCode) CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType); MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore); - AddRawUrlConstructorOverload(generatedCode); AddPropertiesAndMethodTypesImports(generatedCode, false, false, false); CreateCommandBuilders(generatedCode); AddAsyncSuffix(generatedCode); From 9aed74981938e8cc8160fa4315399c14470272f1 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 09:37:18 +0300 Subject: [PATCH 46/69] Simplify CreateCommandType method --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index be1c714cbe..ebc3b9b433 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -64,7 +64,8 @@ private static void CreateCommandBuilders(CodeElement currentElement) OriginalIndexer = indexer }; - method.ReturnType = CreateCommandType(method); + // ReturnType setter assigns the parent + method.ReturnType = CreateCommandType(); method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Complex; currentClass.AddMethod(method); currentClass.RemoveChildElement(indexer); @@ -84,7 +85,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) clone.IsAsync = false; clone.Name = $"Build{cmdName}Command"; - clone.ReturnType = CreateCommandType(clone); + clone.ReturnType = CreateCommandType(); clone.MethodKind = CodeMethodKind.CommandBuilder; clone.OriginalMethod = requestMethod; clone.SimpleName = cmdName; @@ -110,13 +111,12 @@ private static void CreateCommandBuilders(CodeElement currentElement) CrawlTree(currentElement, CreateCommandBuilders); } - private static CodeType CreateCommandType(CodeElement parent) + private static CodeType CreateCommandType() { return new CodeType { Name = "Command", IsExternal = true, - Parent = parent, }; } @@ -128,7 +128,7 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod Name = $"Build{navProperty.Name.ToFirstCharacterUpperCase()}Command", MethodKind = CodeMethodKind.CommandBuilder }; - codeMethod.ReturnType = CreateCommandType(codeMethod); + codeMethod.ReturnType = CreateCommandType(); codeMethod.AccessedProperty = navProperty; codeMethod.SimpleName = navProperty.Name; codeMethod.Parent = parent; From 8cc0438271bac81eafaa0d153d985d479ba85d9c Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 11:27:51 +0300 Subject: [PATCH 47/69] Remove unused request methods --- src/Kiota.Builder/Refiners/CSharpRefiner.cs | 2 +- src/Kiota.Builder/Refiners/ShellRefiner.cs | 42 +++------------------ 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs index 63a99500df..321b6e825a 100644 --- a/src/Kiota.Builder/Refiners/CSharpRefiner.cs +++ b/src/Kiota.Builder/Refiners/CSharpRefiner.cs @@ -56,7 +56,7 @@ protected static void MakeEnumPropertiesNullable(CodeElement currentElement) { CrawlTree(currentElement, MakeEnumPropertiesNullable); } - private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = new AdditionalUsingEvaluator[] { + protected static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = new AdditionalUsingEvaluator[] { new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter), "Microsoft.Kiota.Abstractions", "IRequestAdapter"), new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index ebc3b9b433..ecb316bdb9 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -13,17 +13,17 @@ public ShellRefiner(GenerationConfiguration configuration) : base(configuration) public override void Refine(CodeNamespace generatedCode) { AddDefaultImports(generatedCode, defaultUsingEvaluators); + AddDefaultImports(generatedCode, additionalUsingEvaluators); CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType); MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore); AddPropertiesAndMethodTypesImports(generatedCode, false, false, false); - CreateCommandBuilders(generatedCode); - AddAsyncSuffix(generatedCode); AddInnerClasses(generatedCode, false); AddParsableInheritanceForModelClasses(generatedCode, "IParsable"); CapitalizeNamespacesFirstLetters(generatedCode); ReplaceBinaryByNativeType(generatedCode, "Stream", "System.IO"); MakeEnumPropertiesNullable(generatedCode); + CreateCommandBuilders(generatedCode); /* Exclude the following as their names will be capitalized making the change unnecessary in this case sensitive language * code classes, class declarations, property names, using declarations, namespace names * Exclude CodeMethod as the return type will also be capitalized (excluding the CodeType is not enough since this is evaluated at the code method level) @@ -91,6 +91,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) clone.SimpleName = cmdName; clone.ClearParameters(); currentClass.AddMethod(clone); + currentClass.RemoveChildElement(requestMethod); } // Build root command @@ -135,42 +136,11 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod return codeMethod; } - private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = new AdditionalUsingEvaluator[] { - new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter), - "Microsoft.Kiota.Abstractions", "IRequestAdapter"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), - "Microsoft.Kiota.Abstractions", "HttpMethod", "RequestInformation", "IRequestOption"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), - "Microsoft.Kiota.Abstractions", "IResponseHandler"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Serializer), - "Microsoft.Kiota.Abstractions.Serialization", "ISerializationWriter"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer), - "Microsoft.Kiota.Abstractions.Serialization", "IParseNode"), - new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model), - "Microsoft.Kiota.Abstractions.Serialization", "IParsable"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), - "Microsoft.Kiota.Abstractions.Serialization", "IParsable"), - new (x => x is CodeClass || x is CodeEnum, - "System", "String"), - new (x => x is CodeClass, - "System.Collections.Generic", "List", "Dictionary"), - new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model, CodeClassKind.RequestBuilder), - "System.IO", "Stream"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), - "System.Threading", "CancellationToken"), - new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), - "System.Threading.Tasks", "Task"), - new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model, CodeClassKind.RequestBuilder), - "System.Linq", "Enumerable"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.ClientConstructor) && - method.Parameters.Any(y => y.IsOfKind(CodeParameterKind.BackingStore)), - "Microsoft.Kiota.Abstractions.Store", "IBackingStoreFactory", "IBackingStoreFactorySingleton"), - new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore), - "Microsoft.Kiota.Abstractions.Store", "IBackingStore", "IBackedModel", "BackingStoreFactorySingleton" ), + private static readonly AdditionalUsingEvaluator[] additionalUsingEvaluators = new AdditionalUsingEvaluator[] { new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), - "System.CommandLine", "Command", "RootCommand"), + "System.CommandLine", "Command", "RootCommand", "IConsole"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), - "Microsoft.Kiota.Cli.Commons.IO", "IOutputFormatterFactory"), + "Microsoft.Kiota.Cli.Commons.IO", "IOutputFormatter", "IOutputFormatterFactory", "FormatterType"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.Text", "Encoding"), }; From 61d63113cde6008e86c89b5a9bdf1ee12bc2cb56 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 11:35:22 +0300 Subject: [PATCH 48/69] Check for whitespace as well as null on string values --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 2 ++ src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index ecb316bdb9..3bd6878313 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -85,6 +85,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) clone.IsAsync = false; clone.Name = $"Build{cmdName}Command"; + clone.Description = requestMethod.Description; clone.ReturnType = CreateCommandType(); clone.MethodKind = CodeMethodKind.CommandBuilder; clone.OriginalMethod = requestMethod; @@ -101,6 +102,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) var rootMethod = new CodeMethod { Name = "BuildCommand", + Description = clientConstructor.Description, IsAsync = false, MethodKind = CodeMethodKind.CommandBuilder, ReturnType = new CodeType { Name = "Command", IsExternal = true }, diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 459ed16135..a063c90bac 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -62,14 +62,14 @@ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams reques var pathAndQueryParams = generatorMethod.PathAndQueryParameters; var originalMethod = codeElement.OriginalMethod; var origParams = originalMethod.Parameters; - var parametersList = pathAndQueryParams?.Where(p => p.Name != null)?.ToList() ?? new List(); + var parametersList = pathAndQueryParams?.Where(p => !string.IsNullOrWhiteSpace(p.Name))?.ToList() ?? new List(); if (origParams.Any(p => p.IsOfKind(CodeParameterKind.RequestBody))) { parametersList.Add(origParams.OfKind(CodeParameterKind.RequestBody)); } writer.WriteLine($"var command = new Command(\"{name}\");"); - if (codeElement.Description != null || codeElement?.OriginalMethod?.Description != null) - writer.WriteLine($"command.Description = \"{codeElement.Description ?? codeElement?.OriginalMethod?.Description}\";"); + if (!string.IsNullOrWhiteSpace(codeElement.Description)) + writer.WriteLine($"command.Description = \"{codeElement.Description}\";"); writer.WriteLine("// Create options for all the parameters"); // investigate exploding query params // Check the possible formatting options for headers in a cli. @@ -103,7 +103,7 @@ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams reques optionBuilder.Append($", getDefaultValue: ()=> {option.DefaultValue}"); } - if (!String.IsNullOrEmpty(option.Description)) + if (!string.IsNullOrEmpty(option.Description)) { optionBuilder.Append($", description: \"{option.Description}\""); } From a6f9d7dd3fe976fd1d52a1db20248abffdc9eb48 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 11:39:26 +0300 Subject: [PATCH 49/69] Refactor duplicated code --- .../Writers/Shell/ShellCodeMethodWriter.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index a063c90bac..a26bf1d199 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -68,8 +68,7 @@ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams reques parametersList.Add(origParams.OfKind(CodeParameterKind.RequestBody)); } writer.WriteLine($"var command = new Command(\"{name}\");"); - if (!string.IsNullOrWhiteSpace(codeElement.Description)) - writer.WriteLine($"command.Description = \"{codeElement.Description}\";"); + WriteCommandDescription(codeElement, writer); writer.WriteLine("// Create options for all the parameters"); // investigate exploding query params // Check the possible formatting options for headers in a cli. @@ -221,11 +220,16 @@ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams reques writer.WriteLine("return command;"); } + private static void WriteCommandDescription(CodeMethod codeElement, LanguageWriter writer) + { + if (!string.IsNullOrWhiteSpace(codeElement.Description)) + writer.WriteLine($"command.Description = \"{codeElement.Description}\";"); + } + private void WriteContainerCommand(CodeMethod codeElement, LanguageWriter writer, CodeClass parent, string name) { writer.WriteLine($"var command = new Command(\"{name}\");"); - if (!string.IsNullOrEmpty(codeElement.Description) || !string.IsNullOrEmpty(codeElement?.OriginalMethod?.Description)) - writer.WriteLine($"command.Description = \"{codeElement.Description ?? codeElement?.OriginalMethod?.Description}\";"); + WriteCommandDescription(codeElement, writer); if ((codeElement.AccessedProperty?.Type) is CodeType codeReturnType) { @@ -264,7 +268,7 @@ private void WriteUnnamedBuildCommand(CodeMethod codeElement, LanguageWriter wri { var commandBuilderMethods = classMethods.Where(m => m.MethodKind == CodeMethodKind.CommandBuilder && m != codeElement).OrderBy(m => m.Name); writer.WriteLine($"var command = new RootCommand();"); - writer.WriteLine($"command.Description = \"{codeElement.OriginalMethod.Description}\";"); + WriteCommandDescription(codeElement, writer); foreach (var method in commandBuilderMethods) { writer.WriteLine($"command.AddCommand({method.Name}());"); From 3eaa36153cd89023acd5b415378053433dfda0f1 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 11:47:32 +0300 Subject: [PATCH 50/69] Get serialization contect type from generator method --- src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index a26bf1d199..6d70a0d2f4 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -314,7 +314,7 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa if (requestBodyParamType?.TypeDefinition is CodeClass) { writer.WriteLine($"using var stream = new MemoryStream(Encoding.UTF8.GetBytes({requestBodyParam.Name}));"); - writer.WriteLine("var parseNode = ParseNodeFactoryRegistry.DefaultInstance.GetRootParseNode(\"application/json\", stream);"); + writer.WriteLine($"var parseNode = ParseNodeFactoryRegistry.DefaultInstance.GetRootParseNode(\"{generatorMethod.ContentType}\", stream);"); var typeString = conventions.GetTypeString(requestBodyParamType, requestBodyParam, false); From 5cfb3a10e09fa30bb1f11e5173940f1c057fa37f Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 11:54:45 +0300 Subject: [PATCH 51/69] Add IsOfKind extension method for CodeParameter --- .../Extensions/CodeParametersEnumerableExtensions.cs | 7 ++++++- src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs b/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs index a7904ce4e9..ddaba2a045 100644 --- a/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs +++ b/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Collections.Generic; namespace Kiota.Builder { @@ -6,5 +6,10 @@ public static class CodeParametersEnumerableExtensions { public static CodeParameter OfKind(this IEnumerable parameters, CodeParameterKind kind) { return parameters.FirstOrDefault(x => x.IsOfKind(kind)); } + + public static bool IsOfKind(this CodeParameter parameter, CodeParameterKind kind) + { + return parameter.ParameterKind == kind; + } } } diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 6d70a0d2f4..4fce9e06cf 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -84,7 +84,7 @@ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams reques if (option.ParameterKind == CodeParameterKind.RequestBody && type.TypeDefinition is CodeClass) optionType = "string"; // Binary body handling - if (option.ParameterKind == CodeParameterKind.RequestBody && conventions.StreamTypeName.Equals(option.Type?.Name, StringComparison.OrdinalIgnoreCase)) + if (option.IsOfKind(CodeParameterKind.RequestBody) && conventions.StreamTypeName.Equals(option.Type?.Name, StringComparison.OrdinalIgnoreCase)) { option.Name = "file"; } @@ -129,7 +129,7 @@ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams reques var paramTypes = parametersList.Select(x => { var codeType = x.Type as CodeType; - if (x.ParameterKind == CodeParameterKind.RequestBody && codeType.TypeDefinition is CodeClass) + if (x.IsOfKind(CodeParameterKind.RequestBody) && codeType.TypeDefinition is CodeClass) { return "string"; } From e06f262f7d9396e24555cf9125cee5cd2e224d3f Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 15:13:32 +0300 Subject: [PATCH 52/69] Fix build error --- src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index c9167b314a..7576d7e548 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -53,7 +53,7 @@ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter w WriteRequestExecutorBody(codeElement, requestParams, isVoid, returnType, writer); break; case CodeMethodKind.Deserializer: - WriteDeserializerBody(codeElement, parentClass, writer); + WriteDeserializerBody(inherits, codeElement, parentClass, writer); break; case CodeMethodKind.ClientConstructor: WriteConstructorBody(parentClass, codeElement, writer); From 0ac97f376b76b265bcad85b7775df036578e2ee0 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 16:42:15 +0300 Subject: [PATCH 53/69] Remove unnecessary extension method --- .../Extensions/CodeParametersEnumerableExtensions.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs b/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs index ddaba2a045..a336c9e48b 100644 --- a/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs +++ b/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs @@ -6,10 +6,5 @@ public static class CodeParametersEnumerableExtensions { public static CodeParameter OfKind(this IEnumerable parameters, CodeParameterKind kind) { return parameters.FirstOrDefault(x => x.IsOfKind(kind)); } - - public static bool IsOfKind(this CodeParameter parameter, CodeParameterKind kind) - { - return parameter.ParameterKind == kind; - } } } From 1fe18c4611f25c0cf99979c7e5b9ee9bb40be8ee Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Fri, 11 Feb 2022 16:45:31 +0300 Subject: [PATCH 54/69] Fix unintended revert in CodeMethodWriter after conflict resolution --- src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index 7576d7e548..a4933014d8 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -16,7 +16,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); var parentClass = codeElement.Parent as CodeClass; - var inherits = (parentClass.StartBlock as CodeClass.Declaration).Inherits != null; + var inherits = parentClass.StartBlock is CodeClass.Declaration declaration && declaration.Inherits != null && !parentClass.IsErrorDefinition; var isVoid = conventions.VoidTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); WriteMethodDocumentation(codeElement, writer); WriteMethodPrototype(codeElement, writer, returnType, inherits, isVoid); From c9352e7692c7cbd2c7776c8f6b5942309503b998 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Mon, 14 Feb 2022 14:41:30 +0300 Subject: [PATCH 55/69] Refactor code --- .../Writers/Shell/ShellCodeMethodWriter.cs | 107 ++++++++++-------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 4fce9e06cf..87db3d8bb4 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -75,56 +75,7 @@ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams reques // -h A=b -h // -h A:B,B:C // -h {"A": "B"} - var availableOptions = new List(); - foreach (var option in parametersList) - { - var type = option.Type as CodeType; - var optionName = $"{NormalizeToIdentifier(option.Name)}Option"; - var optionType = conventions.GetTypeString(option.Type, option); - if (option.ParameterKind == CodeParameterKind.RequestBody && type.TypeDefinition is CodeClass) optionType = "string"; - - // Binary body handling - if (option.IsOfKind(CodeParameterKind.RequestBody) && conventions.StreamTypeName.Equals(option.Type?.Name, StringComparison.OrdinalIgnoreCase)) - { - option.Name = "file"; - } - - var optionBuilder = new StringBuilder("new Option"); - if (!String.IsNullOrEmpty(optionType)) - { - optionBuilder.Append($"<{optionType}>"); - } - optionBuilder.Append("(\""); - if (option.Name.Length > 1) optionBuilder.Append('-'); - optionBuilder.Append($"-{NormalizeToOption(option.Name)}\""); - if (option.DefaultValue != null) - { - optionBuilder.Append($", getDefaultValue: ()=> {option.DefaultValue}"); - } - - if (!string.IsNullOrEmpty(option.Description)) - { - optionBuilder.Append($", description: \"{option.Description}\""); - } - - optionBuilder.Append(") {"); - var strValue = $"{optionBuilder}"; - writer.WriteLine($"var {optionName} = {strValue}"); - writer.IncreaseIndent(); - var isRequired = !option.Optional || option.IsOfKind(CodeParameterKind.Path); - - if (option.Type.IsCollection) - { - var arity = isRequired ? "OneOrMore" : "ZeroOrMore"; - writer.WriteLine($"Arity = ArgumentArity.{arity}"); - } - - writer.DecreaseIndent(); - writer.WriteLine("};"); - writer.WriteLine($"{optionName}.IsRequired = {isRequired.ToString().ToFirstCharacterLowerCase()};"); - writer.WriteLine($"command.AddOption({optionName});"); - availableOptions.Add(optionName); - } + var availableOptions = WriteExecutableCommandOptions(writer, parametersList); var paramTypes = parametersList.Select(x => { @@ -220,6 +171,62 @@ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams reques writer.WriteLine("return command;"); } + private List WriteExecutableCommandOptions(LanguageWriter writer, List parametersList) + { + var availableOptions = new List(); + foreach (var option in parametersList) + { + var type = option.Type as CodeType; + var optionName = $"{NormalizeToIdentifier(option.Name)}Option"; + var optionType = conventions.GetTypeString(option.Type, option); + if (option.ParameterKind == CodeParameterKind.RequestBody && type.TypeDefinition is CodeClass) optionType = "string"; + + // Binary body handling + if (option.IsOfKind(CodeParameterKind.RequestBody) && conventions.StreamTypeName.Equals(option.Type?.Name, StringComparison.OrdinalIgnoreCase)) + { + option.Name = "file"; + } + + var optionBuilder = new StringBuilder("new Option"); + if (!String.IsNullOrEmpty(optionType)) + { + optionBuilder.Append($"<{optionType}>"); + } + optionBuilder.Append("(\""); + if (option.Name.Length > 1) optionBuilder.Append('-'); + optionBuilder.Append($"-{NormalizeToOption(option.Name)}\""); + if (option.DefaultValue != null) + { + optionBuilder.Append($", getDefaultValue: ()=> {option.DefaultValue}"); + } + + if (!string.IsNullOrEmpty(option.Description)) + { + optionBuilder.Append($", description: \"{option.Description}\""); + } + + optionBuilder.Append(") {"); + var strValue = $"{optionBuilder}"; + writer.WriteLine($"var {optionName} = {strValue}"); + writer.IncreaseIndent(); + var isRequired = !option.Optional || option.IsOfKind(CodeParameterKind.Path); + + if (option.Type.IsCollection) + { + var arity = isRequired ? "OneOrMore" : "ZeroOrMore"; + writer.WriteLine($"Arity = ArgumentArity.{arity}"); + } + + writer.DecreaseIndent(); + writer.WriteLine("};"); + writer.WriteLine($"{optionName}.IsRequired = {isRequired.ToString().ToFirstCharacterLowerCase()};"); + writer.WriteLine($"command.AddOption({optionName});"); + availableOptions.Add(optionName); + } + + return availableOptions; + } + private static void WriteCommandDescription(CodeMethod codeElement, LanguageWriter writer) { if (!string.IsNullOrWhiteSpace(codeElement.Description)) From 81670b13dd1bc2d8ef419cbf6da90b5a57f66896 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Mon, 14 Feb 2022 16:30:16 +0300 Subject: [PATCH 56/69] Add shell code method writer tests --- .../Shell/ShellCodeMethodWriterTests.cs | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs diff --git a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs new file mode 100644 index 0000000000..f09dc859d1 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kiota.Builder.Writers; +using Xunit; + +namespace Kiota.Builder.Tests.Writers.Shell; + +public class ShellCodeMethodWriterTests : IDisposable +{ + private const string DefaultPath = "./"; + private const string DefaultName = "name"; + private readonly StringWriter tw; + private readonly LanguageWriter writer; + private readonly CodeMethod method; + private readonly CodeClass parentClass; + private readonly CodeNamespace root; + private const string MethodName = "methodName"; + private const string ReturnTypeName = "Somecustomtype"; + private const string MethodDescription = "some description"; + private const string ParamDescription = "some parameter description"; + private const string ParamName = "paramName"; + + public ShellCodeMethodWriterTests() + { + writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.Shell, DefaultPath, DefaultName); + tw = new StringWriter(); + writer.SetTextWriter(tw); + root = CodeNamespace.InitRootNamespace(); + parentClass = new CodeClass + { + Name = "parentClass" + }; + root.AddClass(parentClass); + method = new CodeMethod + { + Name = MethodName + }; + method.ReturnType = new CodeType + { + Name = ReturnTypeName + }; + parentClass.AddMethod(method); + } + + public void Dispose() + { + tw?.Dispose(); + GC.SuppressFinalize(this); + } + + private void AddRequestProperties() { + parentClass.AddProperty(new CodeProperty { + Name = "RequestAdapter", + PropertyKind = CodePropertyKind.RequestAdapter, + }); + parentClass.AddProperty(new CodeProperty { + Name = "pathParameters", + PropertyKind = CodePropertyKind.PathParameters, + }); + parentClass.AddProperty(new CodeProperty { + Name = "urlTemplate", + PropertyKind = CodePropertyKind.UrlTemplate, + }); + } + + private void AddRequestBodyParameters() { + var stringType = new CodeType { + Name = "string", + }; + method.AddParameter(new CodeParameter { + Name = "h", + ParameterKind = CodeParameterKind.Headers, + Type = stringType, + }); + method.AddParameter(new CodeParameter{ + Name = "q", + ParameterKind = CodeParameterKind.QueryParameter, + Type = stringType, + }); + method.AddParameter(new CodeParameter{ + Name = "b", + ParameterKind = CodeParameterKind.RequestBody, + Type = stringType, + }); + method.AddParameter(new CodeParameter{ + Name = "r", + ParameterKind = CodeParameterKind.ResponseHandler, + Type = stringType, + }); + method.AddParameter(new CodeParameter { + Name = "o", + ParameterKind = CodeParameterKind.Options, + Type = stringType, + }); + method.AddParameter(new CodeParameter + { + Name = "c", + ParameterKind = CodeParameterKind.Cancellation, + Type = stringType, + }); + } + + [Fact] + public void WritesRootCommand() + { + method.MethodKind = CodeMethodKind.CommandBuilder; + method.OriginalMethod = new CodeMethod + { + MethodKind = CodeMethodKind.ClientConstructor + }; + + writer.Write(method); + + var result = tw.ToString(); + + Assert.Contains("var command = new RootCommand();", result); + + Assert.Contains("return command;", result); + } + + [Fact] + public void WritesRootCommandWithCommandBuilderMethods() + { + method.MethodKind = CodeMethodKind.CommandBuilder; + method.OriginalMethod = new CodeMethod + { + MethodKind = CodeMethodKind.ClientConstructor + }; + parentClass.AddMethod(new CodeMethod { + Name = "BuildUserCommand", + MethodKind = CodeMethodKind.CommandBuilder + }); + + writer.Write(method); + + var result = tw.ToString(); + + Assert.Contains("var command = new RootCommand();", result); + Assert.Contains("command.AddCommand(BuildUserCommand());", result); + Assert.Contains("return command;", result); + } + + [Fact] + public void WritesIndexerCommands() { + method.MethodKind = CodeMethodKind.CommandBuilder; + var type = new CodeClass { Name = "TestClass", ClassKind = CodeClassKind.RequestBuilder }; + type.AddMethod(new CodeMethod { MethodKind = CodeMethodKind.CommandBuilder, Name = "BuildTestMethod1", ReturnType = new CodeType() }); + type.AddMethod(new CodeMethod { MethodKind = CodeMethodKind.CommandBuilder, Name = "BuildTestMethod2", ReturnType = new CodeType {CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array} }); + method.OriginalIndexer = new CodeIndexer { + ReturnType = new CodeType { + Name = "TestRequestBuilder", + TypeDefinition = type + } + }; + + AddRequestProperties(); + + writer.Write(method); + var result = tw.ToString(); + + Assert.Contains("var builder = new TestRequestBuilder", result); + Assert.Contains("var commands = new List();", result); + Assert.Contains("commands.Add(builder.BuildTestMethod1());", result); + Assert.Contains("commands.AddRange(builder.BuildTestMethod2());", result); + Assert.Contains("return commands;", result); + } + + [Fact] + public void WritesContainerCommands() { + method.MethodKind = CodeMethodKind.CommandBuilder; + method.SimpleName = "User"; + var type = new CodeClass { Name = "TestClass", ClassKind = CodeClassKind.RequestBuilder }; + type.AddMethod(new CodeMethod { MethodKind = CodeMethodKind.CommandBuilder, Name = "BuildTestMethod1", ReturnType = new CodeType() }); + type.AddMethod(new CodeMethod { MethodKind = CodeMethodKind.CommandBuilder, Name = "BuildTestMethod2", ReturnType = new CodeType {CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array} }); + type.Parent = new CodeType { + Name = "Test.Name" + }; + method.AccessedProperty = new CodeProperty { + Type = new CodeType { + Name = "TestRequestBuilder", + TypeDefinition = type + } + }; + + AddRequestProperties(); + + writer.Write(method); + var result = tw.ToString(); + + Assert.Contains("var command = new Command(\"user\");", result); + Assert.Contains("var builder = new Test.Name.TestRequestBuilder", result); + Assert.Contains("command.AddCommand(builder.BuildTestMethod1());", result); + Assert.Contains("foreach (var cmd in builder.BuildTestMethod2()) {", result); + Assert.Contains("command.AddCommand(cmd);", result); + Assert.Contains("return command;", result); + } + + [Fact] + public void WritesExecutableCommandForGetRequest() { + + method.MethodKind = CodeMethodKind.CommandBuilder; + method.SimpleName = "User"; + method.HttpMethod = HttpMethod.Get; + var stringType = new CodeType { + Name = "string", + }; + var generatorMethod = new CodeMethod { + MethodKind = CodeMethodKind.RequestGenerator, + Name = "CreateGetRequestInformation", + HttpMethod = method.HttpMethod + }; + method.OriginalMethod = new CodeMethod { + MethodKind = CodeMethodKind.RequestExecutor, + HttpMethod = method.HttpMethod, + ReturnType = stringType, + Parent = method.Parent + }; + generatorMethod.AddParameter(new CodeParameter{ + Name = "body", + ParameterKind = CodeParameterKind.RequestBody, + Type = stringType, + }); + var codeClass = method.Parent as CodeClass; + codeClass.AddMethod(generatorMethod); + + AddRequestProperties(); + + writer.Write(method); + var result = tw.ToString(); + + Assert.Contains("var command = new Command(\"user\");", result); + Assert.Contains("command.AddOption(outputOption);", result); + Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo);", result); + Assert.Contains("return command;", result); + } +} From b166e23a97c3ee0ca94dd4045b9d5a41ae656b79 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Mon, 14 Feb 2022 16:41:20 +0300 Subject: [PATCH 57/69] Replace if...else with switch expression --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 3bd6878313..ac23e0124d 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -79,8 +79,14 @@ private static void CreateCommandBuilders(CodeElement currentElement) var cmdName = clone.Name; if (classHasIndexers) { - if (clone.HttpMethod == HttpMethod.Get) cmdName = "List"; - if (clone.HttpMethod == HttpMethod.Post) cmdName = "Create"; + switch (clone.HttpMethod) { + case HttpMethod.Get: + cmdName = "List"; + break; + case HttpMethod.Post: + cmdName = "Create"; + break; + } } clone.IsAsync = false; From 791578d996a2f7d9a07314e2f0db1ee5ff2b8a32 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Mon, 14 Feb 2022 16:47:37 +0300 Subject: [PATCH 58/69] Use string.Equals function --- src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 87db3d8bb4..395cd3db5f 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -352,7 +352,7 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa foreach (var param in generatorMethod.PathAndQueryParameters.Where(p => p.IsOfKind(CodeParameterKind.QueryParameter))) { var paramName = NormalizeToIdentifier(param.Name); - bool isStringParam = param.Type.Name?.ToLower() == "string" && !param.Type.IsCollection; + bool isStringParam = "string".Equals(param.Type.Name, StringComparison.OrdinalIgnoreCase) && !param.Type.IsCollection; bool indentParam = true; if (isStringParam) { From bf6d4b969056bc750e637b8e2a2722588335a2e7 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Tue, 15 Feb 2022 16:21:14 +0300 Subject: [PATCH 59/69] Add additional unit tests --- .../Shell/ShellCodeMethodWriterTests.cs | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs index f09dc859d1..5ae342d0aa 100644 --- a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs @@ -67,7 +67,7 @@ private void AddRequestProperties() { }); } - private void AddRequestBodyParameters() { + private static void AddRequestBodyParameters(CodeMethod method) { var stringType = new CodeType { Name = "string", }; @@ -104,6 +104,22 @@ private void AddRequestBodyParameters() { }); } + private static void AddPathAndQueryParameters(CodeMethod method) { + var stringType = new CodeType { + Name = "string", + }; + method.AddPathOrQueryParameter(new CodeParameter{ + Name = "q", + ParameterKind = CodeParameterKind.QueryParameter, + Type = stringType, + }); + method.AddPathOrQueryParameter(new CodeParameter { + Name = "p", + ParameterKind = CodeParameterKind.Path, + Type = stringType + }); + } + [Fact] public void WritesRootCommand() { @@ -219,7 +235,47 @@ public void WritesExecutableCommandForGetRequest() { ReturnType = stringType, Parent = method.Parent }; - generatorMethod.AddParameter(new CodeParameter{ + var codeClass = method.Parent as CodeClass; + codeClass.AddMethod(generatorMethod); + + AddRequestProperties(); + AddRequestBodyParameters(method.OriginalMethod); + AddPathAndQueryParameters(generatorMethod); + + writer.Write(method); + var result = tw.ToString(); + + Assert.Contains("var command = new Command(\"user\");", result); + Assert.Contains("var qOption = new Option(\"-q\")", result); + Assert.Contains("qOption.IsRequired = true;", result); + Assert.Contains("command.AddOption(qOption);", result); + Assert.Contains("command.AddOption(outputOption);", result); + Assert.Contains("var requestInfo = CreateGetRequestInformation", result); + Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo);", result); + Assert.Contains("return command;", result); + } + + [Fact] + public void WritesExecutableCommandForPostRequest() { + + method.MethodKind = CodeMethodKind.CommandBuilder; + method.SimpleName = "User"; + method.HttpMethod = HttpMethod.Post; + var stringType = new CodeType { + Name = "string", + }; + var generatorMethod = new CodeMethod { + MethodKind = CodeMethodKind.RequestGenerator, + Name = "CreatePostRequestInformation", + HttpMethod = method.HttpMethod + }; + method.OriginalMethod = new CodeMethod { + MethodKind = CodeMethodKind.RequestExecutor, + HttpMethod = method.HttpMethod, + ReturnType = stringType, + Parent = method.Parent + }; + method.OriginalMethod.AddParameter(new CodeParameter{ Name = "body", ParameterKind = CodeParameterKind.RequestBody, Type = stringType, @@ -228,12 +284,20 @@ public void WritesExecutableCommandForGetRequest() { codeClass.AddMethod(generatorMethod); AddRequestProperties(); + AddPathAndQueryParameters(generatorMethod); writer.Write(method); var result = tw.ToString(); Assert.Contains("var command = new Command(\"user\");", result); + Assert.Contains("var qOption = new Option(\"-q\")", result); + Assert.Contains("qOption.IsRequired = true;", result); + Assert.Contains("command.AddOption(qOption);", result); + Assert.Contains("var bodyOption = new Option(\"--body\")", result); + Assert.Contains("bodyOption.IsRequired = true;", result); + Assert.Contains("command.AddOption(bodyOption);", result); Assert.Contains("command.AddOption(outputOption);", result); + Assert.Contains("var requestInfo = CreatePostRequestInformation", result); Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo);", result); Assert.Contains("return command;", result); } From e27e15a297a0be982b5fe5281fcfdded078f8b18 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 10:45:53 +0300 Subject: [PATCH 60/69] Add unit tests --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 2 - .../Refiners/ShellRefinerTests.cs | 72 ++++++++++++++ .../Shell/ShellCodeMethodWriterTests.cs | 96 +++++++++++++++++++ 3 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index ac23e0124d..17fc498c5d 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -152,7 +152,5 @@ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, Cod new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder), "System.Text", "Encoding"), }; - - } } diff --git a/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs new file mode 100644 index 0000000000..7077c61109 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs @@ -0,0 +1,72 @@ +using System; +using System.Linq; +using Xunit; + +namespace Kiota.Builder.Refiners.Tests; + +public class ShellRefinerTests { + private readonly CodeNamespace root = CodeNamespace.InitRootNamespace(); + + [Fact] + public void AddsUsingsForCommandTypesUsedInCommandBuilder() { + var requestBuilder = root.AddClass(new CodeClass { + Name = "somerequestbuilder", + ClassKind = CodeClassKind.RequestBuilder, + }).First(); + var subNS = root.AddNamespace($"{root.Name}.subns"); // otherwise the import gets trimmed + var commandBuilder = requestBuilder.AddMethod(new CodeMethod { + Name = "GetCommand", + MethodKind = CodeMethodKind.CommandBuilder, + ReturnType = new CodeType { + Name = "Command", + IsExternal = true + } + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Shell }, root); + + var declaration = requestBuilder.StartBlock as CodeClass.Declaration; + + Assert.Contains("System.CommandLine", declaration.Usings.Select(x => x.Declaration?.Name)); + } + + [Fact] + public void CreatesCommandBuilders() { + var requestBuilder = root.AddClass(new CodeClass { + Name = "somerequestbuilder", + ClassKind = CodeClassKind.RequestBuilder, + }).First(); + var subNS = root.AddNamespace($"{root.Name}.subns"); // otherwise the import gets trimmed + // Add nav props + requestBuilder.AddProperty(new CodeProperty { + Name = "User", + PropertyKind = CodePropertyKind.RequestBuilder + }); + + // Add indexer + requestBuilder.SetIndexer(new CodeIndexer { + Name = "Users", + ReturnType = new CodeType { + Name = "Address" + } + }); + + // Add request executor + requestBuilder.AddMethod(new CodeMethod { + Name = "GetExecutor", + ReturnType = new CodeType { + Name = "User" + }, + MethodKind = CodeMethodKind.RequestExecutor, + HttpMethod = HttpMethod.Get + }); + + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Shell }, root); + + var methods = root.GetChildElements().OfType().SelectMany(c => c.GetChildElements().OfType()); + var methodNames = methods.Select(m => m.Name); + + Assert.Contains("BuildCommand", methodNames); + Assert.Contains("BuildUserCommand", methodNames); + Assert.Contains("BuildListCommand", methodNames); + } +} diff --git a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs index 5ae342d0aa..62efc06dcf 100644 --- a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs @@ -301,4 +301,100 @@ public void WritesExecutableCommandForPostRequest() { Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo);", result); Assert.Contains("return command;", result); } + + [Fact] + public void WritesExecutableCommandForGetStreamRequest() + { + + method.MethodKind = CodeMethodKind.CommandBuilder; + method.SimpleName = "User"; + method.HttpMethod = HttpMethod.Get; + var streamType = new CodeType + { + Name = "stream", + }; + var generatorMethod = new CodeMethod + { + MethodKind = CodeMethodKind.RequestGenerator, + Name = "CreateGetRequestInformation", + HttpMethod = method.HttpMethod + }; + method.OriginalMethod = new CodeMethod + { + MethodKind = CodeMethodKind.RequestExecutor, + HttpMethod = method.HttpMethod, + ReturnType = streamType, + Parent = method.Parent + }; + var codeClass = method.Parent as CodeClass; + codeClass.AddMethod(generatorMethod); + + AddRequestProperties(); + AddRequestBodyParameters(method.OriginalMethod); + AddPathAndQueryParameters(generatorMethod); + + writer.Write(method); + var result = tw.ToString(); + + Assert.Contains("var command = new Command(\"user\");", result); + Assert.Contains("var qOption = new Option(\"-q\")", result); + Assert.Contains("qOption.IsRequired = true;", result); + Assert.Contains("command.AddOption(qOption);", result); + Assert.Contains("command.AddOption(outputOption);", result); + Assert.Contains("var fileOption = new Option(\"--file\");", result); + Assert.Contains("command.AddOption(fileOption);", result); + Assert.Contains("var requestInfo = CreateGetRequestInformation", result); + Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo);", result); + Assert.Contains("return command;", result); + } + + [Fact] + public void WritesExecutableCommandForPostVoidRequest() { + + method.MethodKind = CodeMethodKind.CommandBuilder; + method.SimpleName = "User"; + method.HttpMethod = HttpMethod.Post; + var stringType = new CodeType { + Name = "string", + }; + var voidType = new CodeType { + Name = "void", + }; + var generatorMethod = new CodeMethod { + MethodKind = CodeMethodKind.RequestGenerator, + Name = "CreatePostRequestInformation", + HttpMethod = method.HttpMethod + }; + method.OriginalMethod = new CodeMethod { + MethodKind = CodeMethodKind.RequestExecutor, + HttpMethod = method.HttpMethod, + ReturnType = voidType, + Parent = method.Parent + }; + method.OriginalMethod.AddParameter(new CodeParameter{ + Name = "body", + ParameterKind = CodeParameterKind.RequestBody, + Type = stringType, + }); + var codeClass = method.Parent as CodeClass; + codeClass.AddMethod(generatorMethod); + + AddRequestProperties(); + AddPathAndQueryParameters(generatorMethod); + + writer.Write(method); + var result = tw.ToString(); + + Assert.Contains("var command = new Command(\"user\");", result); + Assert.Contains("var qOption = new Option(\"-q\")", result); + Assert.Contains("qOption.IsRequired = true;", result); + Assert.Contains("command.AddOption(qOption);", result); + Assert.Contains("var bodyOption = new Option(\"--body\")", result); + Assert.Contains("bodyOption.IsRequired = true;", result); + Assert.Contains("command.AddOption(bodyOption);", result); + Assert.Contains("var requestInfo = CreatePostRequestInformation", result); + Assert.Contains("await RequestAdapter.SendNoContentAsync(requestInfo);", result); + Assert.Contains("console.WriteLine(\"Success\");", result); + Assert.Contains("return command;", result); + } } From 11d6e82ade729c5a39d57451f86ceecf40b197ff Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 10:49:36 +0300 Subject: [PATCH 61/69] Refactor code --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 17fc498c5d..36c2bf1611 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -76,18 +76,11 @@ private static void CreateCommandBuilders(CodeElement currentElement) foreach (var requestMethod in requestMethods) { CodeMethod clone = requestMethod.Clone() as CodeMethod; - var cmdName = clone.Name; - if (classHasIndexers) - { - switch (clone.HttpMethod) { - case HttpMethod.Get: - cmdName = "List"; - break; - case HttpMethod.Post: - cmdName = "Create"; - break; - } - } + var cmdName = clone.HttpMethod switch { + HttpMethod.Get when classHasIndexers =>"List", + HttpMethod.Post when classHasIndexers => "Create", + _ => clone.Name, + }; clone.IsAsync = false; clone.Name = $"Build{cmdName}Command"; From 65e245c85fcb3b87c7b84f3643c4b7e800f58e26 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 11:04:22 +0300 Subject: [PATCH 62/69] Update default value to handle string option types --- src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs | 3 ++- .../Writers/Shell/ShellCodeMethodWriterTests.cs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index 395cd3db5f..a91183150b 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -197,7 +197,8 @@ private List WriteExecutableCommandOptions(LanguageWriter writer, List {option.DefaultValue}"); + var defaultValue = optionType == "string" ? $"\"{option.DefaultValue}\"" : option.DefaultValue; + optionBuilder.Append($", getDefaultValue: ()=> {defaultValue}"); } if (!string.IsNullOrEmpty(option.Description)) diff --git a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs index 62efc06dcf..f8df44826e 100644 --- a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs @@ -112,6 +112,8 @@ private static void AddPathAndQueryParameters(CodeMethod method) { Name = "q", ParameterKind = CodeParameterKind.QueryParameter, Type = stringType, + DefaultValue = "test", + Description = "The q option" }); method.AddPathOrQueryParameter(new CodeParameter { Name = "p", @@ -246,7 +248,7 @@ public void WritesExecutableCommandForGetRequest() { var result = tw.ToString(); Assert.Contains("var command = new Command(\"user\");", result); - Assert.Contains("var qOption = new Option(\"-q\")", result); + Assert.Contains("var qOption = new Option(\"-q\", getDefaultValue: ()=> \"test\", description: \"The q option\")", result); Assert.Contains("qOption.IsRequired = true;", result); Assert.Contains("command.AddOption(qOption);", result); Assert.Contains("command.AddOption(outputOption);", result); From f4aaa20ea7be9ef4a0c1e48d06e78286bbffa0c6 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 11:08:26 +0300 Subject: [PATCH 63/69] Add assertion for optional parameters --- .../Writers/Shell/ShellCodeMethodWriterTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs index f8df44826e..a38d4c3bbe 100644 --- a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs @@ -113,7 +113,8 @@ private static void AddPathAndQueryParameters(CodeMethod method) { ParameterKind = CodeParameterKind.QueryParameter, Type = stringType, DefaultValue = "test", - Description = "The q option" + Description = "The q option", + Optional = true }); method.AddPathOrQueryParameter(new CodeParameter { Name = "p", @@ -249,7 +250,7 @@ public void WritesExecutableCommandForGetRequest() { Assert.Contains("var command = new Command(\"user\");", result); Assert.Contains("var qOption = new Option(\"-q\", getDefaultValue: ()=> \"test\", description: \"The q option\")", result); - Assert.Contains("qOption.IsRequired = true;", result); + Assert.Contains("qOption.IsRequired = false;", result); Assert.Contains("command.AddOption(qOption);", result); Assert.Contains("command.AddOption(outputOption);", result); Assert.Contains("var requestInfo = CreateGetRequestInformation", result); From bfff3a1e4835dae0787b45f369c0b4f2e06d721d Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 14:30:11 +0300 Subject: [PATCH 64/69] Rename root build command --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 2 +- .../Refiners/ShellRefinerTests.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 36c2bf1611..1cee434625 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -100,7 +100,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) { var rootMethod = new CodeMethod { - Name = "BuildCommand", + Name = "BuildRootCommand", Description = clientConstructor.Description, IsAsync = false, MethodKind = CodeMethodKind.CommandBuilder, diff --git a/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs index 7077c61109..b9cfa4dead 100644 --- a/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs @@ -60,6 +60,17 @@ public void CreatesCommandBuilders() { HttpMethod = HttpMethod.Get }); + // Add client constructor + requestBuilder.AddMethod(new CodeMethod { + Name = "constructor", + MethodKind = CodeMethodKind.ClientConstructor, + ReturnType = new CodeType { + Name = "void" + }, + DeserializerModules = new() {"com.microsoft.kiota.serialization.Deserializer"}, + SerializerModules = new() {"com.microsoft.kiota.serialization.Serializer"} + }); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Shell }, root); var methods = root.GetChildElements().OfType().SelectMany(c => c.GetChildElements().OfType()); @@ -68,5 +79,6 @@ public void CreatesCommandBuilders() { Assert.Contains("BuildCommand", methodNames); Assert.Contains("BuildUserCommand", methodNames); Assert.Contains("BuildListCommand", methodNames); + Assert.Contains("BuildRootCommand", methodNames); } } From f5597b8e7c1953fd39851c7b8bca3c0db6fa6fd7 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 14:43:19 +0300 Subject: [PATCH 65/69] Fix failing tests --- .../Writers/Shell/ShellCodeMethodWriterTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs index a38d4c3bbe..c118a4af74 100644 --- a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs @@ -293,8 +293,8 @@ public void WritesExecutableCommandForPostRequest() { var result = tw.ToString(); Assert.Contains("var command = new Command(\"user\");", result); - Assert.Contains("var qOption = new Option(\"-q\")", result); - Assert.Contains("qOption.IsRequired = true;", result); + Assert.Contains("var qOption = new Option(\"-q\", getDefaultValue: ()=> \"test\", description: \"The q option\")", result); + Assert.Contains("qOption.IsRequired = false;", result); Assert.Contains("command.AddOption(qOption);", result); Assert.Contains("var bodyOption = new Option(\"--body\")", result); Assert.Contains("bodyOption.IsRequired = true;", result); @@ -340,8 +340,8 @@ public void WritesExecutableCommandForGetStreamRequest() var result = tw.ToString(); Assert.Contains("var command = new Command(\"user\");", result); - Assert.Contains("var qOption = new Option(\"-q\")", result); - Assert.Contains("qOption.IsRequired = true;", result); + Assert.Contains("var qOption = new Option(\"-q\", getDefaultValue: ()=> \"test\", description: \"The q option\")", result); + Assert.Contains("qOption.IsRequired = false;", result); Assert.Contains("command.AddOption(qOption);", result); Assert.Contains("command.AddOption(outputOption);", result); Assert.Contains("var fileOption = new Option(\"--file\");", result); @@ -389,8 +389,8 @@ public void WritesExecutableCommandForPostVoidRequest() { var result = tw.ToString(); Assert.Contains("var command = new Command(\"user\");", result); - Assert.Contains("var qOption = new Option(\"-q\")", result); - Assert.Contains("qOption.IsRequired = true;", result); + Assert.Contains("var qOption = new Option(\"-q\", getDefaultValue: ()=> \"test\", description: \"The q option\")", result); + Assert.Contains("qOption.IsRequired = false;", result); Assert.Contains("command.AddOption(qOption);", result); Assert.Contains("var bodyOption = new Option(\"--body\")", result); Assert.Contains("bodyOption.IsRequired = true;", result); From 97671bd3df919fc584e3a5fd0d07f9e457931501 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 15:27:33 +0300 Subject: [PATCH 66/69] Simplify code complexity --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 85 +++++++++++-------- .../Writers/Shell/ShellCodeMethodWriter.cs | 32 ++++--- 2 files changed, 68 insertions(+), 49 deletions(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 1cee434625..0e3407afd7 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -54,45 +54,11 @@ private static void CreateCommandBuilders(CodeElement currentElement) // Build command for indexers var indexers = currentClass.GetChildElements().OfType(); var classHasIndexers = indexers.Any(); - foreach (var indexer in indexers) - { - var method = new CodeMethod - { - Name = "BuildCommand", - IsAsync = false, - MethodKind = CodeMethodKind.CommandBuilder, - OriginalIndexer = indexer - }; - - // ReturnType setter assigns the parent - method.ReturnType = CreateCommandType(); - method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Complex; - currentClass.AddMethod(method); - currentClass.RemoveChildElement(indexer); - } + CreateCommandBuildersFromIndexers(currentClass, indexers); // Clone executors & convert to build command - var requestMethods = currentClass.GetChildElements().OfType().Where(e => e.IsOfKind(CodeMethodKind.RequestExecutor)); - foreach (var requestMethod in requestMethods) - { - CodeMethod clone = requestMethod.Clone() as CodeMethod; - var cmdName = clone.HttpMethod switch { - HttpMethod.Get when classHasIndexers =>"List", - HttpMethod.Post when classHasIndexers => "Create", - _ => clone.Name, - }; - - clone.IsAsync = false; - clone.Name = $"Build{cmdName}Command"; - clone.Description = requestMethod.Description; - clone.ReturnType = CreateCommandType(); - clone.MethodKind = CodeMethodKind.CommandBuilder; - clone.OriginalMethod = requestMethod; - clone.SimpleName = cmdName; - clone.ClearParameters(); - currentClass.AddMethod(clone); - currentClass.RemoveChildElement(requestMethod); - } + var requestExecutors = currentClass.GetChildElements().OfType().Where(e => e.IsOfKind(CodeMethodKind.RequestExecutor)); + CreateCommandBuildersFromRequestExecutors(currentClass, classHasIndexers, requestExecutors); // Build root command var clientConstructor = currentClass.GetChildElements().OfType().FirstOrDefault(m => m.MethodKind == CodeMethodKind.ClientConstructor); @@ -113,6 +79,51 @@ private static void CreateCommandBuilders(CodeElement currentElement) CrawlTree(currentElement, CreateCommandBuilders); } + private static void CreateCommandBuildersFromRequestExecutors(CodeClass currentClass, bool classHasIndexers, IEnumerable requestMethods) + { + foreach (var requestMethod in requestMethods) + { + CodeMethod clone = requestMethod.Clone() as CodeMethod; + var cmdName = clone.HttpMethod switch + { + HttpMethod.Get when classHasIndexers => "List", + HttpMethod.Post when classHasIndexers => "Create", + _ => clone.Name, + }; + + clone.IsAsync = false; + clone.Name = $"Build{cmdName}Command"; + clone.Description = requestMethod.Description; + clone.ReturnType = CreateCommandType(); + clone.MethodKind = CodeMethodKind.CommandBuilder; + clone.OriginalMethod = requestMethod; + clone.SimpleName = cmdName; + clone.ClearParameters(); + currentClass.AddMethod(clone); + currentClass.RemoveChildElement(requestMethod); + } + } + + private static void CreateCommandBuildersFromIndexers(CodeClass currentClass, IEnumerable indexers) + { + foreach (var indexer in indexers) + { + var method = new CodeMethod + { + Name = "BuildCommand", + IsAsync = false, + MethodKind = CodeMethodKind.CommandBuilder, + OriginalIndexer = indexer + }; + + // ReturnType setter assigns the parent + method.ReturnType = CreateCommandType(); + method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Complex; + currentClass.AddMethod(method); + currentClass.RemoveChildElement(indexer); + } + } + private static CodeType CreateCommandType() { return new CodeType diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index a91183150b..fc3443d281 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -317,7 +317,8 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa .Methods .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod); var requestBodyParam = requestParams.requestBody; - if (requestBodyParam != null) { + if (requestBodyParam != null) + { var requestBodyParamType = requestBodyParam?.Type as CodeType; if (requestBodyParamType?.TypeDefinition is CodeClass) { @@ -329,23 +330,38 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa if (requestBodyParamType.IsCollection) { writer.WriteLine($"var model = parseNode.GetCollectionOfObjectValues<{typeString}>();"); - } else + } + else { writer.WriteLine($"var model = parseNode.GetObjectValue<{typeString}>();"); } requestBodyParam.Name = "model"; - } else if (conventions.StreamTypeName.Equals(requestBodyParamType?.Name, StringComparison.OrdinalIgnoreCase)) + } + else if (conventions.StreamTypeName.Equals(requestBodyParamType?.Name, StringComparison.OrdinalIgnoreCase)) { var name = requestBodyParam.Name; requestBodyParam.Name = "stream"; writer.WriteLine($"using var {requestBodyParam.Name} = {name}.OpenRead();"); } } - + var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } .Select(x => x?.Name).Where(x => x != null).DefaultIfEmpty().Aggregate((x, y) => $"{x}, {y}"); var separator = string.IsNullOrWhiteSpace(parametersList) ? "" : ", "; + WriteRequestInformation(writer, generatorMethod, parametersList, separator); + + var requestMethod = "SendPrimitiveAsync"; + if (isVoid) + { + requestMethod = "SendNoContentAsync"; + } + + writer.WriteLine($"{(isVoid ? string.Empty : "var response = ")}await RequestAdapter.{requestMethod}(requestInfo);"); + } + + private static void WriteRequestInformation(LanguageWriter writer, CodeMethod generatorMethod, string parametersList, string separator) + { writer.WriteLine($"var requestInfo = {generatorMethod?.Name}({parametersList}{separator}q => {{"); if (generatorMethod?.PathAndQueryParameters != null) { @@ -376,14 +392,6 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa { writer.WriteLine("});"); } - - var requestMethod = "SendPrimitiveAsync"; - if (isVoid) - { - requestMethod = "SendNoContentAsync"; - } - - writer.WriteLine($"{(isVoid ? string.Empty : "var response = ")}await RequestAdapter.{requestMethod}(requestInfo);"); } /// From 5c0347cff4458b6a5e244a38d2fd8309ada6bb1c Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 15:37:04 +0300 Subject: [PATCH 67/69] Use OfKind extension method --- src/Kiota.Builder/Refiners/ShellRefiner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs index 0e3407afd7..4da99c323f 100644 --- a/src/Kiota.Builder/Refiners/ShellRefiner.cs +++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs @@ -61,7 +61,7 @@ private static void CreateCommandBuilders(CodeElement currentElement) CreateCommandBuildersFromRequestExecutors(currentClass, classHasIndexers, requestExecutors); // Build root command - var clientConstructor = currentClass.GetChildElements().OfType().FirstOrDefault(m => m.MethodKind == CodeMethodKind.ClientConstructor); + var clientConstructor = currentClass.GetChildElements().OfType().FirstOrDefault(m => m.IsOfKind(CodeMethodKind.ClientConstructor)); if (clientConstructor != null) { var rootMethod = new CodeMethod From a7cf4753e35fec2c797a557b7fe1bd6d60d4e720 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 17:38:57 +0300 Subject: [PATCH 68/69] Add error handling support to shell generation --- .../Writers/Shell/ShellCodeMethodWriter.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs index fc3443d281..3fb24e70e0 100644 --- a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs @@ -14,6 +14,8 @@ class ShellCodeMethodWriter : CodeMethodWriter private static Regex camelCaseRegex = new Regex("(?<=[a-z])([A-Z])", RegexOptions.Compiled); private static Regex identifierRegex = new Regex("(?:[-_\\.]([a-zA-Z]))", RegexOptions.Compiled); private static Regex uppercaseRegex = new Regex("([A-Z])", RegexOptions.Compiled); + private const string cancellationTokenParamType = "CancellationToken"; + private const string cancellationTokenParamName = "cancellationToken"; private const string consoleParamType = "IConsole"; private const string consoleParamName = "console"; private const string fileParamType = "FileInfo"; @@ -125,6 +127,11 @@ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams reques // Add console param paramTypes.Add(consoleParamType); paramNames.Add(consoleParamName); + + // Add CancellationToken param + paramTypes.Add(cancellationTokenParamType); + paramNames.Add(cancellationTokenParamName); + var zipped = paramTypes.Zip(paramNames); var projected = zipped.Select((x, y) => $"{x.First} {x.Second}"); var handlerParams = string.Join(", ", projected); @@ -351,13 +358,26 @@ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestPa var separator = string.IsNullOrWhiteSpace(parametersList) ? "" : ", "; WriteRequestInformation(writer, generatorMethod, parametersList, separator); + var errorMappingVarName = "default"; + if (codeElement.ErrorMappings.Any()) + { + errorMappingVarName = "errorMapping"; + writer.WriteLine($"var {errorMappingVarName} = new Dictionary> {{"); + writer.IncreaseIndent(); + foreach (var errorMapping in codeElement.ErrorMappings) + { + writer.WriteLine($"{{\"{errorMapping.Key.ToUpperInvariant()}\", () => new {errorMapping.Value.Name.ToFirstCharacterUpperCase()}()}},"); + } + writer.CloseBlock("};"); + } + var requestMethod = "SendPrimitiveAsync"; if (isVoid) { requestMethod = "SendNoContentAsync"; } - writer.WriteLine($"{(isVoid ? string.Empty : "var response = ")}await RequestAdapter.{requestMethod}(requestInfo);"); + writer.WriteLine($"{(isVoid ? string.Empty : "var response = ")}await RequestAdapter.{requestMethod}(requestInfo, errorMapping: {errorMappingVarName}, cancellationToken: {cancellationTokenParamName});"); } private static void WriteRequestInformation(LanguageWriter writer, CodeMethod generatorMethod, string parametersList, string separator) From d5d3c02ba57092ce46ba6b1fc1b32f6710a02340 Mon Sep 17 00:00:00 2001 From: Caleb Kiage Date: Wed, 16 Feb 2022 18:17:59 +0300 Subject: [PATCH 69/69] Fix failing tests --- .../Writers/Shell/ShellCodeMethodWriterTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs index c118a4af74..3c3d19b003 100644 --- a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs @@ -254,7 +254,7 @@ public void WritesExecutableCommandForGetRequest() { Assert.Contains("command.AddOption(qOption);", result); Assert.Contains("command.AddOption(outputOption);", result); Assert.Contains("var requestInfo = CreateGetRequestInformation", result); - Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo);", result); + Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo, errorMapping: default, cancellationToken: cancellationToken);", result); Assert.Contains("return command;", result); } @@ -301,7 +301,7 @@ public void WritesExecutableCommandForPostRequest() { Assert.Contains("command.AddOption(bodyOption);", result); Assert.Contains("command.AddOption(outputOption);", result); Assert.Contains("var requestInfo = CreatePostRequestInformation", result); - Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo);", result); + Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo, errorMapping: default, cancellationToken: cancellationToken);", result); Assert.Contains("return command;", result); } @@ -347,7 +347,7 @@ public void WritesExecutableCommandForGetStreamRequest() Assert.Contains("var fileOption = new Option(\"--file\");", result); Assert.Contains("command.AddOption(fileOption);", result); Assert.Contains("var requestInfo = CreateGetRequestInformation", result); - Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo);", result); + Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo, errorMapping: default, cancellationToken: cancellationToken);", result); Assert.Contains("return command;", result); } @@ -396,7 +396,7 @@ public void WritesExecutableCommandForPostVoidRequest() { Assert.Contains("bodyOption.IsRequired = true;", result); Assert.Contains("command.AddOption(bodyOption);", result); Assert.Contains("var requestInfo = CreatePostRequestInformation", result); - Assert.Contains("await RequestAdapter.SendNoContentAsync(requestInfo);", result); + Assert.Contains("await RequestAdapter.SendNoContentAsync(requestInfo, errorMapping: default, cancellationToken: cancellationToken);", result); Assert.Contains("console.WriteLine(\"Success\");", result); Assert.Contains("return command;", result); }